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

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

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

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

Боюсь, есть эквивалент НЕТ. Даже если вы воспользуетесь декоратором с атрибутами функции, вы сможете получить доступ к внешней переменной, что, к сожалению, немного опровергает суть. Более того, вам придется жестко закодировать имя функции в функции, что очень раздражает. Я бы предложил вместо этого использовать глобальные переменные класса или модуля с обычным префиксом _.

lpapp 03.09.2014 14:07

Для программистов, не владеющих C, статическая переменная [stackoverflow.com/questions/5033627/… внутри функции видна только внутри области действия этой функции, но ее время жизни - это весь срок службы программы, и она инициализируется только один раз). По сути, постоянный счетчик или переменная хранения, которая существует между вызовами функций.

smci 01.06.2018 11:28

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

smci 01.06.2018 11:37
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
683
3
366 752
26
Перейти к ответу Данный вопрос помечен как решенный

Ответы 26

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

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

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

Ответ принят как подходящий

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

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..

(Credit: @ony)

Не совсем статично (в смысле C++), если вы меняете счетчик, влияет ли это на все другие экземпляры foo ()?

Martin Beckett 11.11.2008 02:47

есть только один экземпляр foo - это одна функция. все вызовы обращаются к одной и той же переменной.

Claudiu 11.11.2008 02:49

Извините за то, что раскопал это, но я бы предпочел поставить if "counter" not in foo.__dict__: foo.counter = 0 в качестве первых строк foo(). Это поможет избежать кода вне функции. Не уверен, что это было возможно еще в 2008 году. P.S. Нашел этот ответ при поиске возможности создавать статические переменные функции, поэтому этот поток все еще «жив» :)

binaryLV 09.08.2012 10:30

@binaryLV: Я бы предпочел это первому подходу. Проблема с первым подходом заключается в том, что не сразу очевидно, что foo и foo.counter = тесно связаны. однако я в конечном итоге предпочитаю подход декоратора, поскольку декоратор не будет вызываться, и семантически более очевидно, что он делает (@static_var("counter", 0) проще и имеет больший смысл для моих глаз, чем if "counter" not in foo.__dict__: foo.counter = 0, тем более что в последнем у вас есть использовать имя функции (дважды), которое может измениться).

Claudiu 01.03.2013 23:31

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

Claudiu 03.09.2014 18:25
def foo():if not hasattr(foo,"counter"): foo.counter=0foo.counter += 1
Erik Aronesty 03.01.2017 20:41

следует также сказать: не делай этого

whenitrains 10.01.2020 05:29

ИМО, это на самом деле не ответ на вопрос, а скорее быстрое решение (и очень уродливое при этом). Принятый ответ должен был быть таким: «Вы не можете, потому что в Python просто нет статических переменных»!

Vinzent 28.05.2020 16:24

А если потом переименовать функцию? например new_name = foo; del foo. Это не сработает, не так ли? Есть ли лучший подход? Мне жаль, что я спрашиваю об этом только в образовательных целях, и у меня нет какой-то конкретной цели.

Hzzkygcs 04.01.2021 20:58

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

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

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

Я пробовал это, но по какой-то причине параметр функции инициализировался значением 140, а не 0. Почему это должно быть?

andrewdotnich 11.11.2008 02:58

@andrewdotnich: Я не уверен, зачем он это делал, я пробовал его на 2.5, 2.6 и 3.0rc1, и в каждом случае он работал правильно. знак равно

Jeremy 11.11.2008 03:06

@bouvard: Да, я стараюсь избегать его использования в целом, но для быстрых скриптов, качество кода которых меня не особо беспокоит, это может быть удобно.

Jeremy 11.11.2008 03:07

@ Джереми: изменит ли что-нибудь тот факт, что функция является методом экземпляра?

andrewdotnich 11.11.2008 03:09

@ JeremyBanksʬʬʬ: Разве это не более эффективно, чем foo.counter += 1? Я где-то читал, что для производительности следует избегать точек. Может он хоть чуть-чуть эффективнее foo.counter? Имея в виду производительность, вы бы выбрали foo.counter или def foo(counter=[0])?

Tadeck 02.02.2012 10:13

@Tadeck Я подозреваю, что точечный поиск будет немного быстрее (потому что он должен быть эквивалентом множественного точечного поиска под капотом), но разница должна быть незначительной почти во всех случаях. (Думаю, это означает, что я выбрал бы foo.counter, потому что это лучший стиль и, возможно, лучшая производительность.)

Jeremy 03.02.2012 03:56

@bouvard Для рекурсивных функций, которым нужна статическая переменная, это единственная, которая действительно хорошо читается.

lifebalance 05.06.2017 19:57

Я пробовал несколько подходов, и я хочу, чтобы этот был принят как питонический. С некоторыми содержательными именами, такими как def foo(arg1, arg2, _localstorage=DataClass(counter=0)), я нахожу его хорошо читаемым. Еще один хороший момент - легкое переименование функций.

VPfB 30.01.2018 15:40

Почему вы говорите, что не должны так поступать? Для меня это выглядит вполне разумно!

Konstantin 29.07.2018 11:01

@VPfB: для общего хранилища вы можете использовать types.SimpleNamespace, сделав его def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)): без необходимости определять специальный класс.

ShadowRanger 28.12.2019 05:25

Моя переменная уже изменяема (набор)! Так что это как раз то решение, которое мне нужно. Как-то забыл про этот приятный вариант

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

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

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

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

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

Почему бы не попробовать вместо оператора if?

rav 01.12.2013 19:01
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1 должен делать то же самое, используя вместо этого исключения.
sleblanc 10.12.2013 08:31

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

Hack Saw 15.01.2014 11:51

@Hack_Saw: Ну, это Pythonic (лучше попросить прощения, чем разрешения). На самом деле это рекомендуется в методах оптимизации Python, поскольку это позволяет сэкономить на if (хотя я не рекомендую преждевременную оптимизацию). Ваше правило об исключительных случаях: 1. Неудача здесь в некотором смысле исключительный случай. Это случается только один раз. 2. Я думаю, что это правило касается использования (то есть повышения) исключений. Это перехватывает исключение для того, что вы ожидаете работать, но у вас есть план резервного копирования, что является обычным явлением для большинства языков.

leewz 18.01.2014 03:42

@leewangzhong: Добавляет ли вложение блока, не вызывающего исключения, в try какие-либо затраты? Просто любопытно.

trss 24.07.2014 00:04

trss: в очень глупом цикле counter ++ он стоит 5%. Но в любом случае цикл выполняется в 2 раза быстрее, если счетчик является глобальной переменной, а не атрибутом. Если это важно, такая часть принадлежит скомпилированной библиотеке, а не чистому питону.

vincent 24.07.2014 00:22

@vincent: то, что вы разместили в своем комментарии, является правильным, но не с точки зрения декораторов, а с объяснения статической переменной. Пример, который вы показали, будет неправильным с точки зрения декораторов, потому что внутренняя (вложенная) функция никогда не вызывается до конца кода, где ссылка на функцию делает явный вызов внутренней функции, переданной из декоратора. Если внутренняя функция использует hasattr (func, "counter"), декоратор выйдет из строя, потому что внутренняя функция никогда не будет достигнута, а myfunc.counter никогда не будет инициализирован нулевым значением, вызывая AttributeError.

skyrocker 07.02.2017 07:43

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__. Из документации:

Instances of arbitrary classes can be made callable by defining a __call__() method in their class.

Функции уже являются объектами, так что это просто добавляет ненужный слой.

DasIch 30.01.2011 17:48

См. Этот SO-ответ, чтобы понять, что это действительно хорошая идея. stackoverflow.com/questions/460586. Я согласен с тем, что сделать любой такой класс одноэлементным, например, stackoverflow.com/questions/6760685, тоже было бы неплохо. Я не знаю, что имеет в виду @ S.Lott, говоря «... переместить счетчик в определение класса ...», потому что мне кажется, что он уже находится в позиции переменной класса.

Reb.Cabin 20.10.2015 20:05

Основываясь на моем исследовании, этот метод классов кажется наиболее "питоническим" из подходов, представленных на этой странице, и использует наименьшее количество уловок. Поэтому я планирую использовать его как замену C-static -подобным переменным в функциях, как новый разработчик Python.

Gabriel Staples 15.08.2016 01:55

Что произойдет, если я захочу foo1 = Foo () и foo2 = Foo ()?

Mark Lawrence 18.02.2018 03:22

@MarkLawrence Затем у вас есть два разных экземпляра вызываемого класса, каждый со своим собственным счетчиком. Чего и следует ожидать, если вы не используете экземпляр foo, который предоставляется как синглтон.

Aaron McMillin 27.11.2018 18:11

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

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

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

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

lifebalance 05.06.2017 20:00

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

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)

Пожалуйста, не обижайтесь, но это решение немного напоминает мне "стиль большой компании" :-) willa.me/2013/11/the-six-most-common-species-of-code.html

JJC 19.11.2013 19:50

Да, использование непереносимого (манипулирование стеком в целом - это деталь реализации CPython, а не то, на что вы можете положиться в PyPy, Jython, IronPython и т. д.), Хрупкое манипулирование стеком, с полдюжиной вызовов функций при каждом использовании путь лучше простого декоратора ... </s>

ShadowRanger 28.12.2019 06:05

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

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

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

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

Claudiu 30.01.2013 04:07

наверное стоит использовать что-то вроде try: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0

endolith 06.12.2013 06:26

Пожалуйста, используйте X not in Y, а не not X in Y (или посоветуйте использовать, если вы просто использовали его, для более похожего сравнения между этим и hasattr)

Nick T 30.09.2014 23:09

как насчет этого: def fn(): if not hasattr(fn, 'c'): fn.c = 0fn.c += 1 return fn.c

MANU 18.12.2017 08:11

это не идеально, потому что предложение if добавляет ненужную вложенность, в этой ситуации я предпочитаю setdefault

Riaz Rizvi 18.12.2017 21:36

@RiazRizvi: Джонатан ответ исправляет эту проблему; getattr - это универсальная версия того, что вы делаете с vars + setdefault, и более прямой / понятный способ сделать это (поскольку вы пытаетесь получить атрибут, используйте getattr [ibute]).

ShadowRanger 28.12.2019 05:52

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

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

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

  • много питонического («просить прощения, а не разрешения»)
  • использовать исключение (генерируется только один раз) вместо ветки if (подумайте об исключении StopIteration)

Я не так давно занимаюсь Python, но это удовлетворяет одному из неявных элементов языка: если это не (довольно) просто, вы делаете это неправильно.

ZX9 21.09.2016 20:19

Не сразу работает с методами класса, "self.foo.counter = 1" снова вызывает AttributeError.

villasv 15.10.2016 00:10

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

Nils Lindemann 08.05.2017 12:48

Более простой подход: def fn(): if not hasattr(fn, 'c'): fn.c = 0fn.c += 1 return fn.c

MANU 18.12.2017 08:12

@MANU Использование hasattr() для этого не проще, но и менее эффективно.

moooeeeep 08.02.2019 14:39

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

@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

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

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

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

обратите внимание на третий параметр getattr, когда вы помещаете туда функцию, например: def func (): def foo (): return 1112 func.counter = getattr (func, 'counter', foo ()) + 1 при вызове func, всегда будет вызываться foo!

Codefor 19.11.2015 13:36

Просто вызов getattr каждый раз, когда вызывается эта функция. Это нормально, если производительность не является проблемой, если попытка / кроме того, что победит.

Mark Lawrence 18.02.2018 03:20

@MarkLawrence: На самом деле, по крайней мере, при моей установке Windows x64 3.8.0 разница в производительности между этим ответом и эквивалентный подход раввойдила на основе try / except довольно бессмысленна. Простой микробенч ipython%%timeit показал, что стоимость try / except составляет 255 нс на вызов, по сравнению с 263 нс для решения на основе getattr. Да, try / except быстрее, но это не совсем «выигрышные руки»; это крошечная микрооптимизация. Пишите любой код, который кажется более понятным, не беспокойтесь о таких тривиальных различиях в производительности.

ShadowRanger 28.12.2019 05:47

@ShadowRanger спасибо за тестирование. Я размышлял о заявлении Марка Лоуренса в течение 2 лет, и я очень рад, что вы провели исследование. Я определенно согласен с вашим последним предложением - «пишите код, который кажется более ясным» - именно поэтому я написал этот ответ.

Jonathan 06.02.2020 06:05

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

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

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

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

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

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

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

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

Другой (не рекомендуется!) Поворот вызываемого объекта, такого как 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

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

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

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

ShadowRanger 28.12.2019 06:08

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

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

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

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

raffaem 06.07.2017 12:49

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

Daniel K. 20.06.2019 14:08

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

Поскольку вы привели пример, написанный на 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

Этот ответ основан на ответе @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 28.12.2019 06:12

Soulution n + = 1

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

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

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

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

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

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

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

ShadowRanger 28.12.2019 05:57

Эх, я думаю, что сложность фабричной функции или декоратора чрезмерна, если вы не делаете этого много, и в этом случае дизайн уже немного воняет. Для одноразового использования просто добавьте нелокальный счетчик и покончите с этим. Я добавил немного к ответу о соглашениях об именах. Кроме того, причина, по которой я рекомендую nonlocal вместо global, именно такая, как вы указываете - он работает строго в большем количестве обстоятельств.

cbarrick 03.01.2020 07:28

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

import types

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

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

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)

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

Статическая переменная по-прежнему защищена и используется только в рамках функции 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 определен строго как одноэлементный класс, это было бы идеально. Это сделало бы его более питоническим.

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

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 11.05.2019 13:53

Я пишу простую функцию для использования статических переменных:

def Static():
    ### get the func object by which Static() is called.
    from inspect import currentframe, getframeinfo
    caller = currentframe().f_back
    func_name = getframeinfo(caller)[2]
    # print(func_name)
    caller = caller.f_back
    func = caller.f_locals.get(
        func_name, caller.f_globals.get(
            func_name
        )
    )
    
    class StaticVars:
        def has(self, varName):
            return hasattr(self, varName)
        def declare(self, varName, value):
            if not self.has(varName):
                setattr(self, varName, value)

    if hasattr(func, "staticVars"):
        return func.staticVars
    else:
        # add an attribute to func
        func.staticVars = StaticVars()
        return func.staticVars

Как использовать:

def myfunc(arg):
    if Static().has('test1'):
        Static().test += 1
    else:
        Static().test = 1
    print(Static().test)

    # declare() only takes effect in the first time for each static variable.
    Static().declare('test2', 1)
    print(Static().test2)
    Static().test2 += 1

Другие вопросы по теме