Что такое Python-эквивалент статических переменных внутри функции?

631

Каков идиоматический эквивалент Python этого кода C / C ++?

void foo()
{
    static int counter = 0;
    counter++;
    printf("counter is %d\n", counter);
}

в частности, как реализовать статический член на уровне функций, а не на уровне классов? И что-то меняет размещение функции в классе?

andrewdotnich
источник
22
Существует NO не эквивалентности Я боюсь. Даже если вы взломаете декоратор с помощью атрибутов функции, вы сможете получить доступ к переменной снаружи, что, к сожалению, немного опровергает эту точку. Кроме того, вам придется жестко закодировать имя функции в функции, что очень раздражает. Я бы предложил вместо этого использовать глобальные переменные класса или модуля с обычным _префиксом.
lpapp
8
Для не-C-программистов, [ stackoverflow.com/questions/5033627/… статическая переменная внутри функции видна только внутри области действия этой функции, но ее время жизни - это весь жизненный цикл программы, и она инициализируется только один раз). По сути, постоянный счетчик или переменная хранения, которая живет между вызовами функций.
августа
2
@lpapp: есть добрейший из есть, это член класса . Вы правы в том, что мы не можем предотвратить просмотр или изменение другого кода.
августа

Ответы:

681

Немного наоборот, но это должно работать:

def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter
foo.counter = 0

Если вы хотите, чтобы код инициализации счетчика находился сверху, а не снизу, вы можете создать декоратор:

def static_vars(**kwargs):
    def decorate(func):
        for k in kwargs:
            setattr(func, k, kwargs[k])
        return func
    return decorate

Затем используйте код как это:

@static_vars(counter=0)
def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter

К foo.сожалению, вам все равно потребуется использовать префикс.

(Кредит: @ony )

Клаудиу
источник
23
есть только один экземпляр foo - это одна функция. все вызовы обращаются к одной и той же переменной.
Клавдиу
121
Извините, что выкопал это, но я бы предпочел if "counter" not in foo.__dict__: foo.counter = 0в качестве первых строк foo(). Это поможет избежать кода вне функции. Не уверен, что это было возможно еще в 2008 году. PS Нашел этот ответ при поиске возможности создания статических переменных функции, поэтому этот поток все еще "жив" :)
binaryLV
8
@binaryLV: Я бы предпочел это первому подходу. Проблема с первым подходом заключается в том, что не сразу видно , что fooи foo.counter = тесно связаны между собой . тем не менее, я в конечном итоге предпочитаю подход декоратора, так как декоратор не будет вызван, и семантически более очевидно, что он делает ( @static_var("counter", 0)это проще для меня и имеет больше смысла для моих глаз, чем if "counter" not in foo.__dict__: foo.counter = 0, особенно в последнем случае, когда вы должны использовать имя функции (дважды), которое может измениться).
Клавдиу
6
@lpapp: Это зависит от того, в чем смысл статических переменных. Я всегда думал, что это будет одно и то же значение при нескольких вызовах функций, что это действительно удовлетворяет. Я никогда не воспринимал это как скрытие переменных, чего, как вы сказали, нет.
Клавдиу
3
def foo(): if not hasattr(foo,"counter"): foo.counter=0 foo.counter += 1
Эрик Аронесты
222

Вы можете добавить атрибуты в функцию и использовать ее в качестве статической переменной.

def myfunc():
  myfunc.counter += 1
  print myfunc.counter

# attribute must be initialized
myfunc.counter = 0

В качестве альтернативы, если вы не хотите устанавливать переменную вне функции, вы можете использовать, hasattr()чтобы избежать AttributeErrorисключения:

def myfunc():
  if not hasattr(myfunc, "counter"):
     myfunc.counter = 0  # it doesn't exist yet, so initialize it
  myfunc.counter += 1

В любом случае статические переменные довольно редки, и вы должны найти лучшее место для этой переменной, скорее всего, внутри класса.

Винсент
источник
6
Почему бы не попробовать вместо заявления if?
ravwojdyla
12
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1следует сделать то же самое, используя вместо этого исключения.
Sleblanc
Исключения следует использовать для исключительных ситуаций, то есть тех, которые, как ожидает программист, не произойдут, например, входной файл, который он успешно открыл, внезапно стал недоступен. Это ожидаемая ситуация, утверждение if имеет больше смысла.
Hack Saw
11
@Hack_Saw: Ну, это Pythonic (лучше попросить прощения, чем разрешения). Это на самом деле рекомендуется в методах оптимизации Python, так как это экономит стоимость if (хотя я не рекомендую преждевременную оптимизацию). Ваше правило об исключительных случаях: 1. В каком-то смысле отказ является исключительным случаем. Это происходит только один раз. 2. Я думаю, что правило касается использования (то есть повышения) исключений. Это ловит исключение для того, что вы ожидаете работать, но у вас есть план резервного копирования, что часто встречается в большинстве языков.
leewz
@leewangzhong: Добавляет ли вложение блок, который не вызывает исключения в пределах tryстоимости? Просто любопытно.
trss
202

Можно также рассмотреть:

def foo():
    try:
        foo.counter += 1
    except AttributeError:
        foo.counter = 1

Обоснование:

  • очень питонно («прошу прощения, а не разрешения»)
  • использовать исключение (выбрасывается только один раз) вместо ifответвления (подумайте об исключении StopIteration )
ravwojdyla
источник
11
Я не занимался Python долго, но это удовлетворяет одному из неявных условий языка: если это не (довольно) легко, вы делаете это неправильно .
ZX9
Не работает сразу с методами класса, «self.foo.counter = 1» снова вызывает AttributeError.
villasv
16
Это правильное решение, и оно должно быть принятым ответом, потому что код инициализации будет запускаться при вызове функции, а не при выполнении модуля или когда что-то из него импортируется, что имеет место, если вы используете подход декоратора из текущий принятый ответ. Смотрите выполнение функции декоратора Python . Если у вас огромный библиотечный модуль, то будет запущен каждый декоратор, включая функции, которые вы не импортируете.
Нильс Линдеманн
3
Более простой подход: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
5
@MANU Использование hasattr()для этого не проще, а также менее эффективно.
moooeeeep
48

Другие ответы продемонстрировали, как вы должны это сделать. Вот способ, которым вы не должны:

>>> def foo(counter=[0]):
...   counter[0] += 1
...   print("Counter is %i." % counter[0]);
... 
>>> foo()
Counter is 1.
>>> foo()
Counter is 2.
>>> 

Значения по умолчанию инициализируются только при первой оценке функции, а не при каждом ее выполнении, поэтому вы можете использовать список или любой другой изменяемый объект для хранения статических значений.

Джереми Бэнкс
источник
Я попробовал это, но по какой-то причине параметр функции инициализировал себя 140, а не 0. Почему это было бы?
andrewdotnich
1
@bouvard Для рекурсивных функций, которым нужна статическая переменная, это единственная, которая действительно хорошо читает.
Lifebalance
1
Я перепробовал несколько подходов, и мне бы хотелось, чтобы этот подход стал общепринятым С некоторыми значимыми именами, как def foo(arg1, arg2, _localstorage=DataClass(counter=0))я нахожу это хорошо читаемым. Еще один хороший момент - легкая функция переименования.
VPfB
2
Почему вы говорите, что не должны делать это таким образом? Выглядит вполне разумно для меня!
Константин
1
@VPfB: Для общего хранения вы можете использовать types.SimpleNamespaceего, def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)):не задавая специальный класс.
ShadowRanger
43

Многие уже предложили протестировать hasattr, но есть более простой ответ:

def func():
    func.counter = getattr(func, 'counter', 0) + 1

Нет попытки / кроме, нет тестирования hasattr, просто getattr по умолчанию.

Джонатан
источник
2
обратите внимание на третий параметр getattr, когда вы помещаете туда функцию, например: def func (): def foo (): return 1112 func.counter = getattr (func, 'counter', foo ()) + 1 при вызове func, foo всегда будет называться!
Codefor
1
Просто вызов getattr каждый раз, когда вызывается func. Это хорошо, если производительность не является проблемой, если это попытка / кроме того, выиграет руки вниз.
Марк Лоуренс
2
@MarkLawrence: На самом деле, по крайней мере, в моей установке Windows x64 3.8.0 разница в производительности между этим ответом и эквивалентным try/ exceptоснованным подходом ravwojdyla довольно бессмысленна. Простой ipython %%timeitмикробенчмарк дал стоимость try/ exceptпри 255 нс на вызов против 263 нс для getattrрешения на основе. Да, try/ exceptбыстрее, но это не совсем "выигрышные комбинации"; это крошечная микрооптимизация. Пишите любой код, который кажется более понятным, не беспокойтесь о разнице в производительности.
ShadowRanger
@ShadowRanger спасибо за сравнение этого. Я размышлял над заявлением МаркЛоуренса в течение 2 лет, и я очень рад, что вы провели исследование. Я определенно согласен с вашим последним предложением - «напишите любой код, который кажется более понятным» - именно поэтому я написал этот ответ.
Джонатан
28

Вот полностью инкапсулированная версия, которая не требует внешнего вызова инициализации:

def fn():
    fn.counter=vars(fn).setdefault('counter',-1)
    fn.counter+=1
    print (fn.counter)

В Python функции являются объектами, и мы можем просто добавить или исправить патч переменные-члены к ним через специальный атрибут __dict__. Встроенныйvars() возвращает специальный атрибут __dict__.

РЕДАКТИРОВАТЬ: Обратите внимание, в отличие от альтернативного try:except AttributeErrorответа, при таком подходе переменная всегда будет готова для логики кода после инициализации. Я думаю, что try:except AttributeErrorальтернатива следующему будет менее СУХОЙ и / или иметь неловкий поток:

def Fibonacci(n):
   if n<2: return n
   Fibonacci.memo=vars(Fibonacci).setdefault('memo',{}) # use static variable to hold a results cache
   return Fibonacci.memo.setdefault(n,Fibonacci(n-1)+Fibonacci(n-2)) # lookup result in cache, if not available then calculate and store it

РЕДАКТИРОВАТЬ 2: Я рекомендую вышеупомянутый подход только тогда, когда функция будет вызываться из нескольких мест. Если вместо этого функция вызывается только в одном месте, лучше использовать nonlocal:

def TheOnlyPlaceStaticFunctionIsCalled():
    memo={}
    def Fibonacci(n):
       nonlocal memo  # required in Python3. Python2 can see memo
       if n<2: return n
       return memo.setdefault(n,Fibonacci(n-1)+Fibonacci(n-2))
    ...
    print (Fibonacci(200))
    ...
Риаз Ризви
источник
2
единственная проблема с этим заключается в том, что он на самом деле совсем не аккуратный, и всякий раз, когда вы хотите использовать этот шаблон, вы должны вырезать и вставлять код ... отсюда мое использование декоратора
Claudiu
2
вероятно, следует использовать что-то вродеtry: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0
endolith
2
Пожалуйста, используйте X not in Yвместо not X in Y(или советуйте использовать, если вы просто использовали его для более похожего сравнения между этим и hasattr)
Ник Т
как насчет этого: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne
это не идеально, потому что предложение if добавляет ненужное вложение, в этой ситуации я предпочитаю setdefault
Riaz
27

Python не имеет статических переменных, но вы можете подделать его, определив вызываемый объект класса, а затем используя его как функцию. Также посмотрите этот ответ .

class Foo(object):
  # Class variable, shared by all instances of this class
  counter = 0

  def __call__(self):
    Foo.counter += 1
    print Foo.counter

# Create an object instance of class "Foo," called "foo"
foo = Foo()

# Make calls to the "__call__" method, via the object's name itself
foo() #prints 1
foo() #prints 2
foo() #prints 3

Обратите внимание, что __call__делает экземпляр класса (объекта) вызываемым по его собственному имени. Вот почему вызов foo()выше вызывает метод класса __call__. Из документации :

Экземпляры произвольных классов могут быть вызваны путем определения __call__()метода в их классе.

Daniels
источник
15
Функции уже являются объектами, так что это просто добавляет ненужный слой.
DasIch
Посмотрите этот ТАК ответ для длинного мнения, что это на самом деле хорошая идея. stackoverflow.com/questions/460586 . Я согласен, что создание любого такого класса синглтона, возможно, такого как stackoverflow.com/questions/6760685 , также было бы хорошей идеей. Я не знаю, что @ S.Lott подразумевает под "... переместить счетчик в определение класса ...", потому что, похоже, он уже находится в позиции переменной класса для меня.
Reb.Cabin
1
Основываясь на моих исследованиях, эта методика класса представляется наиболее "питоническим" из подходов, представленных на этой странице, и использует наименьшее количество хитрости. Поэтому я планирую принять его как замену для C-static-подобных переменных в функциях, как нового разработчика Python.
Габриэль Стейплс
1
Что произойдет, если я захочу foo1 = Foo () и foo2 = Foo ()?
Марк Лоуренс
@MarkLawrence Тогда у вас есть два разных экземпляра вызываемого класса, каждый со своим счетчиком. Что именно следует ожидать, если вы не используете экземпляр, fooкоторый предоставляется как синглтон.
Аарон Макмиллин
14

Используйте функцию генератора для генерации итератора.

def foo_gen():
    n = 0
    while True:
        n+=1
        yield n

Тогда используйте это как

foo = foo_gen().next
for i in range(0,10):
    print foo()

Если вы хотите верхний предел:

def foo_gen(limit=100000):
    n = 0
    while n < limit:
       n+=1
       yield n

Если итератор завершается (как в примере выше), вы также можете зацикливаться на нем напрямую, например

for i in foo_gen(20):
    print i

Конечно, в этих простых случаях лучше использовать xrange :)

Вот документация о доходности .

gnud
источник
11

Другие решения прикрепляют к функции атрибут counter, обычно с запутанной логикой для обработки инициализации. Это не подходит для нового кода.

В Python 3 правильным способом является использование nonlocalинструкции:

counter = 0
def foo():
    nonlocal counter
    counter += 1
    print(f'counter is {counter}')

См. PEP 3104 для уточнения nonlocalзаявления.

Если счетчик предназначен для частного модуля, он должен быть назван _counterвместо.

cbarrick
источник
Даже до Python 3 вы всегда могли сделать это с помощью global counterоператора вместо nonlocal counter( nonlocalпросто позволяет писать в закрытое состояние во вложенной функции). Причина, по которой люди присоединяют атрибут к функции, заключается в том, чтобы не загрязнять глобальное пространство имен для состояния, характерного для этой функции, поэтому вам не нужно совершать даже более хакерские действия, когда двум функциям требуются независимые counters. Это решение не масштабируется; Атрибуты на функцию делают. Ответ КДБ - как nonlocalпомочь, но это добавляет сложности.
ShadowRanger
Э-э, я думаю, что сложность фабричной функции или декоратора излишня, если вы не делаете это много, и в этом случае дизайн уже немного вонючий. Для одноразового использования просто добавьте нелокальный счетчик и покончите с этим. Я добавил немного в ответ о соглашениях об именах. Кроме того , по этой причине я рекомендую nonlocalболее globalточно , как вы отмечаете - он работает в строго больше обстоятельств.
cbarrick
8

Использование атрибута функции в качестве статической переменной имеет некоторые потенциальные недостатки:

  • Каждый раз, когда вы хотите получить доступ к переменной, вы должны выписать полное имя функции.
  • Внешний код может легко обращаться к переменной и связываться со значением.

Идиоматический питон для второй проблемы, вероятно, будет именовать переменную с начальным подчеркиванием, чтобы сигнализировать, что к ней нельзя обращаться, сохраняя ее доступной после факта.

Альтернативой может быть шаблон, использующий лексические замыкания, которые поддерживаются nonlocalключевым словом в python 3.

def make_counter():
    i = 0
    def counter():
        nonlocal i
        i = i + 1
        return i
    return counter
counter = make_counter()

К сожалению, я не знаю способа инкапсулировать это решение в декоратор.

KDB
источник
7
def staticvariables(**variables):
    def decorate(function):
        for variable in variables:
            setattr(function, variable, variables[variable])
        return function
    return decorate

@staticvariables(counter=0, bar=1)
def foo():
    print(foo.counter)
    print(foo.bar)

Подобно приведенному выше коду Винсента, он будет использоваться в качестве декоратора функции, и к статическим переменным нужно обращаться с именем функции в качестве префикса. Преимущество этого кода (хотя по общему признанию любой может быть достаточно умен, чтобы понять это) состоит в том, что вы можете иметь несколько статических переменных и инициализировать их более обычным способом.

Джордж Борка-Тасчук
источник
7

Чуть более читабельным, но более многословным (Zen of Python: явный лучше, чем неявный):

>>> def func(_static={'counter': 0}):
...     _static['counter'] += 1
...     print _static['counter']
...
>>> func()
1
>>> func()
2
>>>

Смотрите здесь для объяснения того, как это работает.

warvariuc
источник
Можете ли вы объяснить, почему этот код работает? Второй foo()должен повторно инициализировать словарь в значение, указанное в определении функции (то есть с ключом счетчика, имеющим значение 0). Почему это не так?
Раффаем
3
@raffamaiden: аргументы по умолчанию оцениваются только один раз, когда функция определена, а не каждый раз, когда функция вызывается.
Даниэль К.
6
_counter = 0
def foo ():
   глобальный _counter
   _counter + = 1
   выведите 'counter is', _counter

Python обычно использует подчеркивания для обозначения приватных переменных. Единственная причина в C объявить статическую переменную внутри функции - это скрыть ее за пределами функции, что на самом деле не является идиоматическим Python.

Дейв
источник
4

Попробовав несколько подходов, я в итоге использую улучшенную версию ответа @ warvariuc:

import types

def func(_static=types.SimpleNamespace(counter=0)):
    _static.counter += 1
    print(_static.counter)
VPfB
источник
3

Идиоматический способ заключается в использовании класса , который может иметь атрибуты. Если вам нужно, чтобы экземпляры не были разделены, используйте синглтон.

Есть несколько способов, которыми вы могли бы подделать или изменить «статические» переменные в Python (один из них не упоминался до сих пор - иметь изменяемый аргумент по умолчанию), но это не Python, идиоматический способ сделать это. Просто используйте класс.

Или, возможно, генератор, если ваш шаблон использования подходит.

Тедди
источник
Для автономных рекурсивных функций defaultаргумент является наиболее элегантным.
спасательный круг
3

В ответ на этот вопрос я могу представить другую альтернативу, которая может быть более приятной для использования и будет выглядеть одинаково как для методов, так и для функций:

@static_var2('seed',0)
def funccounter(statics, add=1):
    statics.seed += add
    return statics.seed

print funccounter()       #1
print funccounter(add=2)  #3
print funccounter()       #4

class ACircle(object):
    @static_var2('seed',0)
    def counter(statics, self, add=1):
        statics.seed += add
        return statics.seed

c = ACircle()
print c.counter()      #1
print c.counter(add=2) #3
print c.counter()      #4
d = ACircle()
print d.counter()      #5
print d.counter(add=2) #7
print d.counter()      #8    

Если вам нравится использование, вот реализация:

class StaticMan(object):
    def __init__(self):
        self.__dict__['_d'] = {}

    def __getattr__(self, name):
        return self.__dict__['_d'][name]
    def __getitem__(self, name):
        return self.__dict__['_d'][name]
    def __setattr__(self, name, val):
        self.__dict__['_d'][name] = val
    def __setitem__(self, name, val):
        self.__dict__['_d'][name] = val

def static_var2(name, val):
    def decorator(original):
        if not hasattr(original, ':staticman'):    
            def wrapped(*args, **kwargs):
                return original(getattr(wrapped, ':staticman'), *args, **kwargs)
            setattr(wrapped, ':staticman', StaticMan())
            f = wrapped
        else:
            f = original #already wrapped

        getattr(f, ':staticman')[name] = val
        return f
    return decorator
Клаудиу
источник
3

Другой (не рекомендуется!) Поворот вызываемого объекта, например https://stackoverflow.com/a/279598/916373 , если вы не возражаете против использования подписи в стиле фанк-вызова, это сделать

class foo(object):
    counter = 0;
    @staticmethod
    def __call__():
        foo.counter += 1
        print "counter is %i" % foo.counter

>>> foo()()
counter is 1
>>> foo()()
counter is 2
потерял
источник
3

Вместо создания функции, имеющей статическую локальную переменную, вы всегда можете создать так называемый «объект функции» и присвоить ей стандартную (нестатическую) переменную-член.

Поскольку вы привели пример, написанный на C ++, я сначала объясню, что такое «функциональный объект» в C ++. «Функциональный объект» - это просто любой класс с перегруженным operator(). Экземпляры класса будут вести себя как функции. Например, вы можете написать, int x = square(5);даже если squareэто объект (с перегруженным operator()), а не технически не «функция». Вы можете дать объекту-функции любую из функций, которые вы могли бы дать объекту класса.

# C++ function object
class Foo_class {
    private:
        int counter;     
    public:
        Foo_class() {
             counter = 0;
        }
        void operator() () {  
            counter++;
            printf("counter is %d\n", counter);
        }     
   };
   Foo_class foo;

В Python мы также можем перегрузить, operator()за исключением того, что метод вместо этого называется __call__:

Вот определение класса:

class Foo_class:
    def __init__(self): # __init__ is similair to a C++ class constructor
        self.counter = 0
        # self.counter is like a static member
        # variable of a function named "foo"
    def __call__(self): # overload operator()
        self.counter += 1
        print("counter is %d" % self.counter);
foo = Foo_class() # call the constructor

Вот пример используемого класса:

from foo import foo

for i in range(0, 5):
    foo() # function call

Вывод на консоль выводится:

counter is 1
counter is 2
counter is 3
counter is 4
counter is 5

Если вы хотите, чтобы ваша функция принимала входные аргументы, вы также можете добавить их в __call__:

# FILE: foo.py - - - - - - - - - - - - - - - - - - - - - - - - -

class Foo_class:
    def __init__(self):
        self.counter = 0
    def __call__(self, x, y, z): # overload operator()
        self.counter += 1
        print("counter is %d" % self.counter);
        print("x, y, z, are %d, %d, %d" % (x, y, z));
foo = Foo_class() # call the constructor

# FILE: main.py - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

from foo import foo

for i in range(0, 5):
    foo(7, 8, 9) # function call

# Console Output - - - - - - - - - - - - - - - - - - - - - - - - - - 

counter is 1
x, y, z, are 7, 8, 9
counter is 2
x, y, z, are 7, 8, 9
counter is 3
x, y, z, are 7, 8, 9
counter is 4
x, y, z, are 7, 8, 9
counter is 5
x, y, z, are 7, 8, 9
IdleCustard
источник
3

Soulution n + = 1

def foo():
  foo.__dict__.setdefault('count', 0)
  foo.count += 1
  return foo.count
FECA
источник
3

Глобальная декларация обеспечивает эту функциональность. В приведенном ниже примере (Python 3.5 или выше, чтобы использовать «f»), переменная counter определяется вне функции. Определение его как глобального в функции означает, что «глобальная» версия вне функции должна быть доступна для функции. Таким образом, каждый раз, когда функция запускается, она изменяет значение вне функции, сохраняя его вне функции.

counter = 0

def foo():
    global counter
    counter += 1
    print("counter is {}".format(counter))

foo() #output: "counter is 1"
foo() #output: "counter is 2"
foo() #output: "counter is 3"
Ричард Меррен
источник
Это работает так же, если используется правильно. Отличие от c-кода состоит в том, что в примере c в OP переменная-счетчик может быть затронута только функцией. Глобальная переменная в Python может использоваться или изменяться в любом месте скрипта
MortenSickel
2

Статическая переменная внутри метода Python

class Count:
    def foo(self):
        try: 
            self.foo.__func__.counter += 1
        except AttributeError: 
            self.foo.__func__.counter = 1

        print self.foo.__func__.counter

m = Count()
m.foo()       # 1
m.foo()       # 2
m.foo()       # 3
wannik
источник
1

Я лично предпочитаю следующее декораторам. Каждому свое.

def staticize(name, factory):
    """Makes a pseudo-static variable in calling function.

    If name `name` exists in calling function, return it. 
    Otherwise, saves return value of `factory()` in 
    name `name` of calling function and return it.

    :param name: name to use to store static object 
    in calling function
    :type name: String
    :param factory: used to initialize name `name` 
    in calling function
    :type factory: function
    :rtype: `type(factory())`

    >>> def steveholt(z):
    ...     a = staticize('a', list)
    ...     a.append(z)
    >>> steveholt.a
    Traceback (most recent call last):
    ...
    AttributeError: 'function' object has no attribute 'a'
    >>> steveholt(1)
    >>> steveholt.a
    [1]
    >>> steveholt('a')
    >>> steveholt.a
    [1, 'a']
    >>> steveholt.a = []
    >>> steveholt.a
    []
    >>> steveholt('zzz')
    >>> steveholt.a
    ['zzz']

    """
    from inspect import stack
    # get scope enclosing calling function
    calling_fn_scope = stack()[2][0]
    # get calling function
    calling_fn_name = stack()[1][3]
    calling_fn = calling_fn_scope.f_locals[calling_fn_name]
    if not hasattr(calling_fn, name):
        setattr(calling_fn, name, factory())
    return getattr(calling_fn, name)
spenthil
источник
3
Пожалуйста, не обижайтесь, но это решение немного напоминает мне «стиль большой компании» :-) willa.me/2013/11/the-six-most-common-species-of-code.html
JJC
Да, использование непереносимого (манипулирование стеком в целом - это деталь реализации CPython, а не то, на что вы можете положиться в PyPy, Jython, IronPython, что угодно), хрупкое манипулирование стеком с полдюжиной вызовов функций при каждом использовании это намного лучше, чем простой декоратор ... </ s>
ShadowRanger
1

Этот ответ основан на ответе @claudiu.

Я обнаружил, что мой код становится менее понятным, когда мне всегда приходилось добавлять имя функции всякий раз, когда я собираюсь получить доступ к статической переменной.

А именно, в моем коде функции я бы предпочел написать:

print(statics.foo)

вместо

print(my_function_name.foo)

Итак, мое решение состоит в том, чтобы:

  1. добавить statics атрибут в функцию
  2. в области действия функции добавьте локальную переменную staticsв качестве псевдонимаmy_function.statics
from bunch import *

def static_vars(**kwargs):
    def decorate(func):
        statics = Bunch(**kwargs)
        setattr(func, "statics", statics)
        return func
    return decorate

@static_vars(name = "Martin")
def my_function():
    statics = my_function.statics
    print("Hello, {0}".format(statics.name))

замечание

Мой метод использует класс с именем Bunch, который является словарем, который поддерживает доступ стиля атрибута, как JavaScript (см. Оригинальную статью об этом, около 2000 г.)

Может быть установлен через pip install bunch

Это также может быть написано от руки так:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self
Паскаль Т.
источник
Примечание: types.SimpleNamespace(доступно начиная с версии 3.3) поддерживает это поведение «из коробки» (и реализовано в C на CPython, поэтому оно работает настолько быстро, насколько это возможно).
ShadowRanger
0

Опираясь на ответ Даниила (дополнения):

class Foo(object): 
    counter = 0  

def __call__(self, inc_value=0):
    Foo.counter += inc_value
    return Foo.counter

foo = Foo()

def use_foo(x,y):
    if(x==5):
        foo(2)
    elif(y==7):
        foo(3)
    if(foo() == 10):
        print("yello")


use_foo(5,1)
use_foo(5,1)
use_foo(1,7)
use_foo(1,7)
use_foo(1,1)

Причина, по которой я хотел добавить эту часть, состоит в том, что статические переменные используются не только для приращения на какое-то значение, но и для проверки того, равно ли статическое значение var некоторому значению, как пример из реальной жизни.

Статическая переменная все еще защищена и используется только в рамках функции use_foo ()

В этом примере вызов функции foo () точно такой же (по отношению к соответствующему эквиваленту c ++):

stat_c +=9; // in c++
foo(9)  #python equiv

if(stat_c==10){ //do something}  // c++

if(foo() == 10):      # python equiv
  #add code here      # python equiv       

Output :
yello
yello

если класс Foo определен ограниченно как класс синглтона, это было бы идеально. Это сделало бы это более питоническим.

йаш
источник
-1

Конечно, это старый вопрос, но я думаю, что мог бы предоставить некоторые обновления.

Кажется, что аргумент производительности устарел. Похоже, один и тот же набор тестов дает схожие результаты для siInt_try и isInt_re2. Конечно, результаты могут отличаться, но это один сеанс на моем компьютере с python 3.4.4 на ядре 4.3.01 с Xeon W3550. Я запускал его несколько раз, и результаты кажутся похожими. Я переместил глобальное регулярное выражение в статическую функцию, но разница в производительности незначительна.

isInt_try: 0.3690
isInt_str: 0.3981
isInt_re: 0.5870
isInt_re2: 0.3632

С учетом проблемы с производительностью кажется, что try / catch будет генерировать код, наиболее подходящий для будущего и заглавных букв, поэтому, возможно, просто оберните его в функцию

Кеджи Ли
источник
1
Что ты вообще здесь сравниваешь? Это похоже на комментарий к другим ответам, но не ясно, какие именно, и не отвечает на сам вопрос.
ShadowRanger