Что такое идиоматический Python-эквивалент этого кода C / C++?
void foo()
{
static int counter = 0;
counter++;
printf("counter is %d\n", counter);
}
в частности, как реализовать статический член на уровне функции, а не на уровне класса? И что-то меняет размещение функции в классе?
Для программистов, не владеющих C, статическая переменная [stackoverflow.com/questions/5033627/… внутри функции видна только внутри области действия этой функции, но ее время жизни - это весь срок службы программы, и она инициализируется только один раз). По сути, постоянный счетчик или переменная хранения, которая существует между вызовами функций.
@lpapp: вроде как, это член класса. Вы правы, что мы не можем предотвратить его просмотр или изменение другим кодом.






Используйте функцию генератора для создания итератора.
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 ()?
есть только один экземпляр foo - это одна функция. все вызовы обращаются к одной и той же переменной.
Извините за то, что раскопал это, но я бы предпочел поставить if "counter" not in foo.__dict__: foo.counter = 0 в качестве первых строк foo(). Это поможет избежать кода вне функции. Не уверен, что это было возможно еще в 2008 году. P.S. Нашел этот ответ при поиске возможности создавать статические переменные функции, поэтому этот поток все еще «жив» :)
@binaryLV: Я бы предпочел это первому подходу. Проблема с первым подходом заключается в том, что не сразу очевидно, что foo и foo.counter = тесно связаны. однако я в конечном итоге предпочитаю подход декоратора, поскольку декоратор не будет вызываться, и семантически более очевидно, что он делает (@static_var("counter", 0) проще и имеет больший смысл для моих глаз, чем if "counter" not in foo.__dict__: foo.counter = 0, тем более что в последнем у вас есть использовать имя функции (дважды), которое может измениться).
@lpapp: это зависит от того, в чем заключается смысл статических переменных. Я всегда думал, что это будет одно и то же значение для нескольких вызовов функций, и это меня устраивает. Я никогда не думал, что речь идет о сокрытии переменных, а это не так, как вы сказали.
def foo():if not hasattr(foo,"counter"): foo.counter=0foo.counter += 1следует также сказать: не делай этого
ИМО, это на самом деле не ответ на вопрос, а скорее быстрое решение (и очень уродливое при этом). Принятый ответ должен был быть таким: «Вы не можете, потому что в Python просто нет статических переменных»!
А если потом переименовать функцию? например new_name = foo; del foo. Это не сработает, не так ли? Есть ли лучший подход? Мне жаль, что я спрашиваю об этом только в образовательных целях, и у меня нет какой-то конкретной цели.
Другие ответы продемонстрировали, как вы должны это делать. Вот как не следует:
>>> def foo(counter=[0]):
... counter[0] += 1
... print("Counter is %i." % counter[0]);
...
>>> foo()
Counter is 1.
>>> foo()
Counter is 2.
>>>
Значения по умолчанию инициализируются только при первом вычислении функции, а не при каждом ее выполнении, поэтому вы можете использовать список или любой другой изменяемый объект для хранения статических значений.
Я пробовал это, но по какой-то причине параметр функции инициализировался значением 140, а не 0. Почему это должно быть?
@andrewdotnich: Я не уверен, зачем он это делал, я пробовал его на 2.5, 2.6 и 3.0rc1, и в каждом случае он работал правильно. знак равно
@bouvard: Да, я стараюсь избегать его использования в целом, но для быстрых скриптов, качество кода которых меня не особо беспокоит, это может быть удобно.
@ Джереми: изменит ли что-нибудь тот факт, что функция является методом экземпляра?
@ JeremyBanksʬʬʬ: Разве это не более эффективно, чем foo.counter += 1? Я где-то читал, что для производительности следует избегать точек. Может он хоть чуть-чуть эффективнее foo.counter? Имея в виду производительность, вы бы выбрали foo.counter или def foo(counter=[0])?
@Tadeck Я подозреваю, что точечный поиск будет немного быстрее (потому что он должен быть эквивалентом множественного точечного поиска под капотом), но разница должна быть незначительной почти во всех случаях. (Думаю, это означает, что я выбрал бы foo.counter, потому что это лучший стиль и, возможно, лучшая производительность.)
@bouvard Для рекурсивных функций, которым нужна статическая переменная, это единственная, которая действительно хорошо читается.
Я пробовал несколько подходов, и я хочу, чтобы этот был принят как питонический. С некоторыми содержательными именами, такими как def foo(arg1, arg2, _localstorage=DataClass(counter=0)), я нахожу его хорошо читаемым. Еще один хороший момент - легкое переименование функций.
Почему вы говорите, что не должны так поступать? Для меня это выглядит вполне разумно!
@VPfB: для общего хранилища вы можете использовать types.SimpleNamespace, сделав его def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)): без необходимости определять специальный класс.
Моя переменная уже изменяема (набор)! Так что это как раз то решение, которое мне нужно. Как-то забыл про этот приятный вариант
_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?
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1 должен делать то же самое, используя вместо этого исключения.
Исключения следует использовать в исключительных ситуациях, то есть тех, которые, как ожидает программист, не произойдут, например, когда входной файл, который он успешно открыл, внезапно становится недоступным. Это ожидаемая ситуация, оператор if имеет больше смысла.
@Hack_Saw: Ну, это Pythonic (лучше попросить прощения, чем разрешения). На самом деле это рекомендуется в методах оптимизации Python, поскольку это позволяет сэкономить на if (хотя я не рекомендую преждевременную оптимизацию). Ваше правило об исключительных случаях: 1. Неудача здесь в некотором смысле исключительный случай. Это случается только один раз. 2. Я думаю, что это правило касается использования (то есть повышения) исключений. Это перехватывает исключение для того, что вы ожидаете работать, но у вас есть план резервного копирования, что является обычным явлением для большинства языков.
@leewangzhong: Добавляет ли вложение блока, не вызывающего исключения, в try какие-либо затраты? Просто любопытно.
trss: в очень глупом цикле counter ++ он стоит 5%. Но в любом случае цикл выполняется в 2 раза быстрее, если счетчик является глобальной переменной, а не атрибутом. Если это важно, такая часть принадлежит скомпилированной библиотеке, а не чистому питону.
@vincent: то, что вы разместили в своем комментарии, является правильным, но не с точки зрения декораторов, а с объяснения статической переменной. Пример, который вы показали, будет неправильным с точки зрения декораторов, потому что внутренняя (вложенная) функция никогда не вызывается до конца кода, где ссылка на функцию делает явный вызов внутренней функции, переданной из декоратора. Если внутренняя функция использует hasattr (func, "counter"), декоратор выйдет из строя, потому что внутренняя функция никогда не будет достигнута, а myfunc.counter никогда не будет инициализирован нулевым значением, вызывая AttributeError.
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.
Функции уже являются объектами, так что это просто добавляет ненужный слой.
См. Этот SO-ответ, чтобы понять, что это действительно хорошая идея. stackoverflow.com/questions/460586. Я согласен с тем, что сделать любой такой класс одноэлементным, например, stackoverflow.com/questions/6760685, тоже было бы неплохо. Я не знаю, что имеет в виду @ S.Lott, говоря «... переместить счетчик в определение класса ...», потому что мне кажется, что он уже находится в позиции переменной класса.
Основываясь на моем исследовании, этот метод классов кажется наиболее "питоническим" из подходов, представленных на этой странице, и использует наименьшее количество уловок. Поэтому я планирую использовать его как замену C-static -подобным переменным в функциях, как новый разработчик Python.
Что произойдет, если я захочу foo1 = Foo () и foo2 = Foo ()?
@MarkLawrence Затем у вас есть два разных экземпляра вызываемого класса, каждый со своим собственным счетчиком. Чего и следует ожидать, если вы не используете экземпляр foo, который предоставляется как синглтон.
Способ идиоматический заключается в использовании класс, который может иметь атрибуты. Если вам нужно, чтобы экземпляры не были отдельными, используйте синглтон.
Существует несколько способов подделки или замены «статических» переменных в Python (один из них, о котором пока не упоминалось, - иметь изменяемый аргумент по умолчанию), но это не способ сделать это с помощью Пифонический, идиоматический. Просто используйте класс.
Или, возможно, генератор, если вам подходит ваш шаблон использования.
Для автономных рекурсивных функций аргумент default является наиболее элегантным.
Лично я предпочитаю декораторам следующее. Каждому свое.
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
Да, использование непереносимого (манипулирование стеком в целом - это деталь реализации CPython, а не то, на что вы можете положиться в PyPy, Jython, IronPython и т. д.), Хрупкое манипулирование стеком, с полдюжиной вызовов функций при каждом использовании путь лучше простого декоратора ... </s>
Вот полностью инкапсулированная версия, которая не требует внешнего вызова инициализации:
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))
...
единственная проблема с этим заключается в том, что это действительно совсем не аккуратно, и всякий раз, когда вы хотите использовать этот шаблон, вам нужно вырезать и вставить код ... поэтому я использую декоратор
наверное стоит использовать что-то вроде try: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0
Пожалуйста, используйте X not in Y, а не not X in Y (или посоветуйте использовать, если вы просто использовали его, для более похожего сравнения между этим и hasattr)
как насчет этого: def fn(): if not hasattr(fn, 'c'): fn.c = 0fn.c += 1 return fn.c
это не идеально, потому что предложение if добавляет ненужную вложенность, в этой ситуации я предпочитаю setdefault
@RiazRizvi: Джонатан ответ исправляет эту проблему; getattr - это универсальная версия того, что вы делаете с vars + setdefault, и более прямой / понятный способ сделать это (поскольку вы пытаетесь получить атрибут, используйте getattr [ibute]).
Можно также рассмотреть:
def foo():
try:
foo.counter += 1
except AttributeError:
foo.counter = 1
Рассуждение:
if (подумайте об исключении StopIteration)Я не так давно занимаюсь Python, но это удовлетворяет одному из неявных элементов языка: если это не (довольно) просто, вы делаете это неправильно.
Не сразу работает с методами класса, "self.foo.counter = 1" снова вызывает AttributeError.
Это правильное решение, и это должен быть принятый ответ, потому что код инициализации будет запускаться при вызове функции, а не при выполнении модуля или при импорте чего-либо из него, что имеет место, если вы используете подход декоратора из принятый в настоящее время ответ. См. Выполнение функции декоратора Python. Если у вас есть огромный библиотечный модуль, то будут запущены все декораторы, включая декораторы функций, которые вы не импортируете.
Более простой подход: def fn(): if not hasattr(fn, 'c'): fn.c = 0fn.c += 1 return fn.c
@MANU Использование hasattr() для этого не проще, но и менее эффективно.
По подсказке этот вопрос, могу ли я представить другую альтернативу, которая может быть немного приятнее в использовании и будет выглядеть одинаково как для методов, так и для функций:
@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!
Просто вызов getattr каждый раз, когда вызывается эта функция. Это нормально, если производительность не является проблемой, если попытка / кроме того, что победит.
@MarkLawrence: На самом деле, по крайней мере, при моей установке Windows x64 3.8.0 разница в производительности между этим ответом и эквивалентный подход раввойдила на основе try / except довольно бессмысленна. Простой микробенч ipython%%timeit показал, что стоимость try / except составляет 255 нс на вызов, по сравнению с 263 нс для решения на основе getattr. Да, try / except быстрее, но это не совсем «выигрышные руки»; это крошечная микрооптимизация. Пишите любой код, который кажется более понятным, не беспокойтесь о таких тривиальных различиях в производительности.
@ShadowRanger спасибо за тестирование. Я размышлял о заявлении Марка Лоуренса в течение 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
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 создаст наиболее перспективный и надежный код, поэтому, возможно, просто оберните его в функцию
Что вы тут вообще сравниваете? Это похоже на комментарий к другим ответам, но неясно, какие из них, и не отвечает на сам вопрос.
Чуть более читабельный, но более подробный (Zen of Python: явный лучше, чем неявный):
>>> def func(_static = {'counter': 0}):
... _static['counter'] += 1
... print _static['counter']
...
>>> func()
1
>>> func()
2
>>>
См. здесь для объяснения того, как это работает.
можете подробнее объяснить, почему этот код работает? Второй foo() должен повторно инициализировать словарь значением, указанным в определении функции (то есть с ключом счетчика, имеющим значение 0). Почему нет?
@raffamaiden: аргументы по умолчанию оцениваются только один раз, когда функция определена, а не каждый раз, когда функция вызывается.
Вместо создания функции, имеющей статическую локальную переменную, вы всегда можете создать так называемый «объект функции» и дать ему стандартную (нестатическую) переменную-член.
Поскольку вы привели пример, написанный на 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)
Итак, мое решение:
staticsstatics в качестве псевдонима к my_function.staticsfrom 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, поэтому он работает настолько быстро, насколько это возможно).
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 может помочь, но это добавляет сложности.
Эх, я думаю, что сложность фабричной функции или декоратора чрезмерна, если вы не делаете этого много, и в этом случае дизайн уже немного воняет. Для одноразового использования просто добавьте нелокальный счетчик и покончите с этим. Я добавил немного к ответу о соглашениях об именах. Кроме того, причина, по которой я рекомендую nonlocal вместо global, именно такая, как вы указываете - он работает строго в большем количестве обстоятельств.
Попробовав несколько подходов, я использую улучшенную версию ответа @ 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 может использоваться или изменяться в любом месте скрипта.
Я пишу простую функцию для использования статических переменных:
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
Боюсь, есть эквивалент НЕТ. Даже если вы воспользуетесь декоратором с атрибутами функции, вы сможете получить доступ к внешней переменной, что, к сожалению, немного опровергает суть. Более того, вам придется жестко закодировать имя функции в функции, что очень раздражает. Я бы предложил вместо этого использовать глобальные переменные класса или модуля с обычным префиксом
_.