Что такое Python-эквивалент статических переменных внутри функции? - PullRequest
539 голосов
/ 11 ноября 2008

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

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

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

Ответы [ 26 ]

602 голосов
/ 11 ноября 2008

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

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

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

def static_var(varname, value):
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate

Затем используйте код, подобный следующему:

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

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


РЕДАКТИРОВАТЬ (благодаря ony ): это выглядит еще лучше:

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
198 голосов
/ 11 ноября 2008

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

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

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

169 голосов
/ 25 апреля 2013

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

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

Рассуждение:

  • много питонов (ask for forgiveness not permission)
  • использовать исключение (выбрасывается только один раз) вместо if ответвления (думаю StopIteration исключение)
37 голосов
/ 11 ноября 2008

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

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

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

29 голосов
/ 05 января 2015

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

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

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

23 голосов
/ 11 ноября 2008

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__() в их классе.

22 голосов
/ 04 сентября 2012

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

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))
    ...
11 голосов
/ 11 ноября 2008

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

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:)

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

6 голосов
/ 11 ноября 2008
_counter = 0
def foo():
   global _counter
   _counter += 1
   print 'counter is', _counter

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

6 голосов
/ 09 февраля 2015
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)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...