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

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

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

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

Ответ 1

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

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., к сожалению.


EDIT (спасибо 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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

Ответ 6

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 name itself
foo() #prints 1
foo() #prints 2
foo() #prints 3

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

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

Ответ 7

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

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

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

EDIT: обратите внимание, в отличие от альтернативной try:except AttributeError ответа try:except AttributeError, при таком подходе переменная всегда будет готова к логике кода после инициализации. Я думаю, что try:except AttributeError альтернативы try:except AttributeError следующая ниже будет меньше DRY и/или иметь неудобный поток:

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

EDIT2: я рекомендую только этот подход, когда функция будет вызвана из нескольких мест. Если вместо этого функция вызывается только в одном месте, лучше использовать 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))
    ...

Ответ 8

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

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

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

Ответ 9

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

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

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

См. PEP 3104 для спецификации nonlocal оператора.

Ответ 10

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)

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

Ответ 11

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

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

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

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

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

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

Ответ 12

_counter = 0
def foo():
   global _counter
   _counter += 1
   print 'counter is', _counter

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

Ответ 13

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

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

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

Ответ 14

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

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

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

Ответ 15

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

@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

Ответ 16

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

Поскольку вы представили пример написанного С++, я сначала объясню, что такое "объект функции" на С++. "Функциональный объект" - это просто любой класс с перегруженным 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

Ответ 17

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

import types

def func(_static=types.SimpleNamespace(counter=0)):
    _static.counter += 1
    print(_static.counter)

Ответ 18

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

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)

Ответ 19

Статическая переменная внутри метода 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

Ответ 20

Еще один (не рекомендуется!) поворот на вызываемом объекте, таком как fooobar.com/questions/17178/..., если вы не возражаете использовать фанк-подпись, было бы сделать

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

Ответ 21

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

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

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

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

Ответ 22

Этот ответ показывает, что setdefault действительно не удовлетворяет вопросу OP о том, как создавать статические локальные переменные.

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

Он работает до тех пор, пока fn. имеет префикс для каждого имени переменной. Если вы удалите их так:

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

ошибок нет, но счетчик всегда равен 0, и это говорит мне, что vars (fn) НЕ обращается к локальным переменным, а скорее к глобальному, возможно, к декоратору или атрибуту.

Если бы это сработало, это было бы моим предпочтительным решением. Однако, так как это не так, я склонен идти с полностью инкапсулированным определением класса для создания таких статических vars.

ИМХО, что является самым простым. Конечно, это зависит от того, насколько вы более знакомы с функциональными стилями кодирования OOP и OOP.

Ответ 23

Soulution n + = 1

def foo():
  foo.__dict__.setdefault('count', 0)
  foo.count += 1
  return foo.count

Ответ 24

Глобальная декларация обеспечивает эту функциональность. В приведенном ниже примере (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"

Ответ 25

Этот ответ основывается на ответе @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, который является словарем, поддерживающим доступ к атрибуту, a la JavaScript (см. Оригинальную статью об этом около 2000)

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

Он также может быть написан вручную:

class Bunch(dict):
    def __init__(self, **kw):
        dict.__init__(self,kw)
        self.__dict__ = self

Ответ 26

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

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 определяется ограничительно как одноэлементный класс, это было бы идеальным. Это сделало бы его более питоническим.

Ответ 27

Тем не менее, это тихий старый пост, так как у меня другая идиоматическая цель, я поставил следующее:

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

Как я люблю красиво писать и быть старым программистом в стиле C. Я попытался определить макросоподобное письмо:

def  Foo () :
    StaticVar( Foo, ‘Var, CalculateStatic())
    StaticVar( Foo, ‘Step, CalculateStep())
    Foo.Var += Foo.Step
    print(‘Value of Var : ‘, Foo.Var)

Затем я написал "StaticVar так:

def StaticVar(Cls, Var, StaticVal) :
    if not hasattr(Cls, Var) :
        setattr(Cls, Var, StaticVal)

Еще приятнее в Python:

def StaticVars(Cls, **Vars) :
    for Var, StaticVal in Vars.items() :
        if not hasattr(Cls, Var) :
            setattr(Cls, Var, StaticVal)

def  Foo () :
    StaticVars( Foo, Var = CalculateStatic(),Step= CalculateStep()))
    Foo.Var += Foo. Step
    print(‘Value of Var : ‘, Foo.Var)

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

def CalculateStatic() :
    print("Costly Initialization")
    return 0

Для достижения моей цели я бы лучше написал:

def  Foo () :
    if not hasattr(Foo, ‘Var) :
        setattr ( Foo, ‘Var, CalculateStatic())
        setattr ( Foo, ‘Step, CalculateStep())

    Foo.Var += Foo. Step
    print(‘Value of Var : ‘, Foo.Var)

Если бы у Python была "Marcro preprocessing", было бы лучше...