Що таке еквівалент Python статичних змінних всередині функції?


630

Який ідіоматичний еквівалент Python цього коду C / C ++?

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

конкретно, як реалізувати статичний член на рівні функції, на відміну від рівня класу? І чи поміщення функції в клас щось змінює?


22
Існує NO не еквівалентні Я боюся. Навіть якщо ви зламаєте декоратор з атрибутами функції, ви зможете отримати доступ до змінної назовні, яка на жаль перемагає крапку. Більше того, вам доведеться жорстко кодувати ім'я функції у функції, що дуже дратує. Я б запропонував використовувати глобальні змінні класу або модуля замість звичайного _префікса.
lpapp

8
Для програмістів, що не належать до C, [ stackoverflow.com/questions/5033627/… статична змінна всередині функції видно лише в межах цієї функції, але її термін служби - це весь термін служби програми, і він ініціалізується лише один раз). В основному, стійкий лічильник чи змінна пам'ять, що живе між викликами функцій.
smci

2
@lpapp: там є вид, це член класу . Ви правильні, що ми не можемо перешкодити іншому коду його переглядати чи змінювати.
smci

Відповіді:


681

Трохи обернено, але це має працювати:

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

Якщо ви хочете код ініціалізації лічильника вгорі, а не внизу, ви можете створити декоратор:

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

Потім використовуйте такий код:

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

На foo.жаль, вам все-таки потрібно буде використовувати префікс, на жаль.

(Кредит: @ony )


23
є лише один екземпляр foo - ця одна функція. всі виклики мають доступ до однієї змінної.
Клавдіу

121
Вибачте, що це викопав, але я вважаю за краще поставити if "counter" not in foo.__dict__: foo.counter = 0як перші рядки foo(). Це допоможе уникнути коду поза функцією. Не впевнений, чи це було можливо ще в 2008 році. PS знайшов цю відповідь під час пошуку можливості створення статичних змінних функцій, тому ця тема все ще "жива" :)
binaryLV

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

6
@lpapp: Це залежить від точки статичних змінних. Я завжди думав, що це буде однакове значення для декількох функціональних викликів, яке це задовольняє. Я ніколи не вважав, що мова йде про змінне приховування, яке не відповідає, як ви сказали.
Клавдіу

3
def foo(): if not hasattr(foo,"counter"): foo.counter=0 foo.counter += 1
Ерік Аронесті

221

Ви можете додати атрибути до функції та використовувати її як статичну змінну.

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

# attribute must be initialized
myfunc.counter = 0

Крім того, якщо ви не хочете встановлювати змінну за межами функції, ви можете використовувати, hasattr()щоб уникнути AttributeErrorвиключення:

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

У будь-якому випадку статичні змінні є досить рідкісними, і вам слід знайти краще місце для цієї змінної, швидше за все, у класі.


6
Чому б не спробувати замість оператора if?
ravwojdyla

12
try: myfunc.counter += 1; except AttributeError: myfunc.counter = 1слід робити те ж саме, використовуючи винятки.
sleblanc

Винятки повинні використовуватися для виняткових ситуацій, тобто тих, які програміст очікує, що не відбудуться, наприклад, вхідний файл, який він успішно відкрив, раптом не доступний. Це очікувана ситуація, якщо твердження має більше сенсу.
Hack Saw

11
@Hack_Saw: Ну, це піфонічне (краще просити пробачення, ніж дозволу). Це насправді рекомендується в методах оптимізації Python, оскільки це економить вартість if (хоча я не рекомендую передчасну оптимізацію). Ваше правило щодо виняткових випадків: 1. Невдача є в цьому сенсі винятковою справою. Це трапляється лише один раз. 2. Я думаю, що це правило стосується використання (тобто підвищення) винятків. Це є винятком для того, що ви очікуєте працювати, але маєте резервний план, що є звичайною справою для більшості мов.
leewz

@leewangzhong: Чи блокування блоку, який не збільшує виняток, tryдодає будь-які витрати? Просто цікаво.
trss

201

Можна також врахувати:

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

Обґрунтування:

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

11
Я давно не займаюся Python, але це задовольняє одне із неявних принципів мови: якщо це не (досить) просто, ви робите це неправильно .
ZX9

Не працює відразу з методами класу, "self.foo.counter = 1" знову піднімає AttributeError.
villasv

16
Це правильне рішення, і це має бути прийнятою відповіддю, оскільки код ініціалізації буде запускатися, коли функція викликається, а не тоді, коли виконується модуль або коли імпортується щось із нього, що є випадком, якщо ви використовуєте підхід декоратора від наразі прийнята відповідь. Див . Виконання функції декоратора Python . Якщо у вас є величезний модуль бібліотеки, запускається кожен декоратор, включаючи функції, які ви не імпортуєте.
Нілс Ліндеманн

3
Простіший підхід: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne

5
@MANU Використовувати hasattr()для цього не простіше, а й менш ефективно.
moooeeeep

48

Інші відповіді продемонстрували, як ви повинні це зробити. Ось такий спосіб, як ви не повинні:

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

Значення за замовчуванням ініціалізуються лише тоді, коли функція вперше оцінюється, а не кожного разу, коли вона виконується, тому ви можете використовувати список або будь-який інший об'єкт, що змінюється, для зберігання статичних значень.


Я спробував це, але чомусь параметр функції ініціалізував себе до 140, а не до 0. Чому це було б?
andrewdotnich

1
@bouvard Для рекурсивних функцій, які потребують статичної змінної, це єдина, яка справді добре читає.
життєвий баланс

1
Я спробував кілька підходів і хочу, щоб цей став прийнятим як пітонічний. З деякими значущими назвами, як def foo(arg1, arg2, _localstorage=DataClass(counter=0))я вважаю, це добре читається. Ще одним хорошим моментом є легке перейменування функції.
VPfB

2
Чому ти кажеш, що ти не повинен робити це так? Мені здається абсолютно розумним!
Костянтин

1
@VPfB: Для загального зберігання ви можете використовувати його types.SimpleNamespace, не створюючи def foo(arg1, arg2, _staticstorage=types.SimpleNamespace(counter=0)):спеціального класу.
ShadowRanger

43

Багато людей вже запропонували тестувати "hasattr", але є простіша відповідь:

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

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


2
зверніть увагу на третю частину getattr, коли ви кладете туди func, наприклад: def func (): def foo (): повертайте 1112 func.counter = getattr (func, 'counter', foo ()) + 1 при дзвінку фунц, фу буде завжди називатися!
Codefor

1
Просто дзвінок до getattr кожного разу, коли функціонує виклик. Це добре, якщо продуктивність не є проблемою, якщо вона намагається /, окрім, виграє руки.
Марк Лоуренс

2
@MarkLawrence: Насправді, принаймні, на моїй установці Windows x64 3.8.0, різниця в продуктивності між цією відповіддю та рівнозначним підходом try/ рівнодействомexcept є досить безглуздою. Простий ipython %%timeitмікро-показник дав вартість try/ exceptв 255 нс за дзвінок проти 263 нс для базового getattrрішення. Так, try/ exceptшвидше, але це не зовсім "вигравши руки вниз"; це крихітна мікрооптимізація. Пишіть будь-який код, який вам здається чіткішим, не турбуйтеся про подібні тривіальні відмінності.
ShadowRanger

@ShadowRanger спасибі за тестування цього. Я цікавився заявою MarkLawrence протягом 2 років, і я дуже радий, що ви зробили дослідження. Я безумовно згоден з вашим заключним реченням - "пишіть все, що код здається зрозумілішим" - саме тому я написав цю відповідь.
Джонатан

28

Ось повністю капсульована версія, яка не вимагає зовнішнього виклику ініціалізації:

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альтернатива наступному буде меншою сухим та / або мати незграбний потік:

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

2
Єдина проблема з цим полягає в тому, що це насправді зовсім не охайно, і щоразу, коли ви хочете скористатися цією схемою, вам доведеться вирізати і вставити код ... звідси моє використання декоратора
Клавдіу

2
напевно, слід використовувати щось на кшталтtry: mystaticfun.counter+=10 except AttributeError: mystaticfun.counter=0
endolith

2
Будь ласка, використовуйте, X not in Yа не not X in Y(або радить використовувати, якщо ви просто використовували його для більш подібного вигляду порівняння між цим і hasattr)
Nick T

як щодо цього: def fn(): if not hasattr(fn, 'c'): fn.c = 0 fn.c += 1 return fn.c
TheCuriousOne

це не ідеально, тому що якщо додаток додає зайве вкладення, то в цій ситуації я віддаю перевагу setdefault
Riaz

27

У Python немає статичних змінних, але ви можете їх підробити, визначивши об'єкт класу, що викликається, а потім використовувати його як функцію. Дивіться також цю відповідь .

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

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

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

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

Зауважте, що __call__примірник класу (об'єкта) може називатися власним іменем. Ось чому виклик foo()вище називає __call__метод класу ' . З документації :

Екземпляри довільних класів можна викликати, визначивши __call__()метод у своєму класі.


15
Функції - це вже об'єкти, тому це просто додає зайвий шар.
DasIch

Дивіться цю відповідь ТА для тривалої думки, що це насправді хороша ідея. stackoverflow.com/questions/460586 . Я погоджуюся, що зробити будь-який подібний клас одинарним, можливо, як цей stackoverflow.com/questions/6760685 , також було б хорошою ідеєю. Я не знаю, що @ S.Lott означає "... перенести лічильник на визначення класу ...", тому що схоже, що він для мене вже знаходиться в перемінному класі.
Reb.Cabin

1
На основі мого дослідження ця методика класу виявляється найбільш «пітонічною» з підходів, представлених на цій сторінці, і використовує найменшу хитрість. Тому я планую прийняти його як мій перехід на C-статичні змінні у функціях, як новий розробник Python.
Габріель Степлес

1
Що станеться, якщо я хочу foo1 = Foo () і foo2 = Foo ()?
Марк Лоуренс

@MarkLawrence Тоді у вас є два різних екземпляри класу, за яким можна телефонувати, у кожного зі своїм лічильником. Що саме ви повинні очікувати, якщо ви не використовуєте екземпляр, fooякий надається як синглтон.
Аарон Макміллін

14

Використовуйте функцію генератора для генерації ітератора.

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

Потім використовуйте його як

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

Якщо ви хочете верхню межу:

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

Якщо ітератор завершується (як, наприклад, приклад вище), ви також можете передати його циклічно безпосередньо, як-от

for i in foo_gen(20):
    print i

Звичайно, у цих простих випадках краще використовувати xrange :)

Ось документація на дохідність .


11

Інші рішення приєднують атрибут лічильника до функції, як правило, зі складеною логікою для обробки ініціалізації. Це не підходить для нового коду.

У 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просто дозволяє записувати в стан закриття вкладеної функції). Причина, чому люди прив'язують атрибут до функції, полягає у тому, щоб уникнути забруднення глобального простору імен для стану, специфічного для функції, тому вам не потрібно робити навіть більш хакірні речі, коли дві функції потрібні незалежні counters. Це рішення не масштабується; атрибути функції do. Відповідь kdb полягає в тому, як nonlocalможе допомогти, але це додає складності.
ShadowRanger

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

8

Використання атрибута функції як статичної змінної має деякі потенційні недоліки:

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

Ідіоматичний пітон для другого випуску, ймовірно, буде називати змінну з провідним підкресленням, щоб сигналізувати про те, що вона не призначена для доступу, зберігаючи її доступною після факту.

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

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

На жаль, я не знаю способу інкапсуляції цього розчину в декоратор.


7
def staticvariables(**variables):
    def decorate(function):
        for variable in variables:
            setattr(function, variable, variables[variable])
        return function
    return decorate

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

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


7

Трохи більш читабельний, але більш багатослівний (Zen of Python: явний краще, ніж неявний):

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

Дивіться тут для пояснення, як це працює.


Ви можете детальніше пояснити, чому цей код працює? Другий foo()повинен повторно ініціалізувати словник до значення, визначеного у визначенні функції (так, з клавішею лічильника, що має значення 0). Чому цього немає?
raffaem

3
@raffamaiden: Аргументи за замовчуванням оцінюються лише один раз, коли функція визначена, а не кожен раз, коли функція викликається.
Даніель К.

6
_counter = 0
def foo ():
   глобальний _counter
   _counter + = 1
   print 'лічильник', _counter

Python зазвичай використовує підкреслення для позначення приватних змінних. Єдина причина в C оголосити статичну змінну всередині функції - це приховати її поза функцією, що насправді не є ідіоматичним Python.


4

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

import types

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

3

Ідіоматичний спосіб полягає у використанні класу , який може мати атрибути. Якщо вам потрібні екземпляри, щоб вони не були окремими, використовуйте синглтон.

Існує кілька способів підробити або об'єднати "статичні" змінні в Python (один з яких до цього часу не згадується - мати змінні аргументи за замовчуванням), але це не пітонічний, ідіоматичний спосіб зробити це. Просто використовуйте клас.

Або, можливо, генератор, якщо ваша схема використання відповідає.


Для автономних рекурсивних функцій defaultаргумент є найелегантнішим.
життєвий баланс

3

Запропонований цим запитанням , чи можу я представити іншу альтернативу, яку можна трохи приємніше використовувати і виглядати однаково як для методів, так і для функцій:

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

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

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

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

Якщо вам подобається використання, ось реалізація:

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

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

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

        getattr(f, ':staticman')[name] = val
        return f
    return decorator

3

Ще один (не рекомендується!) Поворот на об'єкт , що дзвонить, як-от https://stackoverflow.com/a/279598/916373 , якщо ви не заперечуєте за допомогою підпису прикольного дзвінка

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

>>> foo()()
counter is 1
>>> foo()()
counter is 2

3

Замість створення функції, що має статичну локальну змінну, ви завжди можете створити те, що називається "об'єктом функції", і надати йому стандартну (нестатичну) змінну члена.

Оскільки ви наводили приклад, написаний на C ++, я спочатку поясню, що таке "об’єкт функції" в C ++. "Об'єктом функції" є просто будь-який клас із перевантаженням operator(). Екземпляри класу будуть вести себе як функції. Наприклад, ви можете писати, int x = square(5);навіть якщо squareце об'єкт (із перевантаженням operator()), а технічно не є "функцією". Ви можете надати об’єкту функції будь-яку з функцій, яку ви могли б надати об’єкту класу.

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

У Python ми також можемо перевантажуватись, operator()за винятком того, що метод не називається __call__:

Ось визначення класу:

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

Ось приклад класу, який використовується:

from foo import foo

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

Вихід, надрукований на консолі:

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

Якщо ви хочете, щоб ваша функція приймала вхідні аргументи, ви можете також додати їх __call__:

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

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

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

from foo import foo

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

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

counter is 1
x, y, z, are 7, 8, 9
counter is 2
x, y, z, are 7, 8, 9
counter is 3
x, y, z, are 7, 8, 9
counter is 4
x, y, z, are 7, 8, 9
counter is 5
x, y, z, are 7, 8, 9

3

Сульфування n + = 1

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

3

Глобальна декларація забезпечує цю функціональність. У наведеному нижче прикладі (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 ОП, до змінної лічильника може торкатися лише функція. Глобальна змінна в python може використовуватися або змінюватися в будь-якому місці сценарію
MortenSickel

2

Статична змінна всередині методу Python

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

        print self.foo.__func__.counter

m = Count()
m.foo()       # 1
m.foo()       # 2
m.foo()       # 3

1

Я особисто віддаю перевагу наступним декораторам. Кожному своє.

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

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

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

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

    """
    from inspect import stack
    # get scope enclosing calling function
    calling_fn_scope = stack()[2][0]
    # get calling function
    calling_fn_name = stack()[1][3]
    calling_fn = calling_fn_scope.f_locals[calling_fn_name]
    if not hasattr(calling_fn, name):
        setattr(calling_fn, name, factory())
    return getattr(calling_fn, name)

3
Будь ласка, не ображайтесь, але це рішення трохи нагадує мені "великий стиль компанії" :-) willa.me/2013/11/the-six-most-common-species-of-code.html
JJC

Так, використання не портативних (маніпуляція стеком взагалі є детальною інформацією про реалізацію CPython, а не те, на що можна покластися в PyPy, Jython, IronPython, what-have-you), неміцна маніпуляція стеком, що містить півдюжини функції, яка вимагає кожного використання це спосіб краще , ніж простий декоратор ... </ s>
ShadowRanger

1

Ця відповідь ґрунтується на відповіді @claudiu.

Я виявив, що мій код стає менш зрозумілим, коли мені завжди доводилося додавати ім'я функції, коли я маю намір отримати доступ до статичної змінної.

А саме, у своєму коді функції я вважаю за краще написати:

print(statics.foo)

замість

print(my_function_name.foo)

Отже, моє рішення полягає в тому, щоб:

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

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

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

Зауваження

У моєму методі використовується клас з іменем Bunch, який є словником, який підтримує доступ до атрибутивного стилю, a la JavaScript (див. Оригінальну статтю про нього, близько 2000 р.)

Його можна встановити через pip install bunch

Це також може бути написано від руки так:

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

Примітка: types.SimpleNamespace(доступно з 3.3) підтримує таку поведінку поза коробкою (і реалізується в C на CPython, тому вона проходить так само швидко, як це може бути).
ShadowRanger

0

Спираючись на відповідь Даніеля (доповнення):

class Foo(object): 
    counter = 0  

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

foo = Foo()

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


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

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

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

У цьому прикладі виклик до foo () функціонує точно так само (стосовно відповідного еквівалента c ++):

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

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

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

Output :
yello
yello

якщо клас Foo визначається обмежено як однотонний клас, це було б ідеально. Це зробило б її більш пітонічною.


-1

Звичайно, це старе питання, але я думаю, я можу надати деяке оновлення.

Здається, аргумент ефективності застарілий. Схоже, те саме набір тестів дає схожі результати для siInt_try та isInt_re2. Звичайно, результати відрізняються, але це один сеанс на моєму комп'ютері з python 3.4.4 на ядрі 4.3.01 з Xeon W3550. Я запускав її кілька разів, і результати, здається, схожі. Я перемістив глобальний регулярний вираз у функцію статичної, але різниця в продуктивності незначна.

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

Якщо проблема з продуктивністю не виходить, схоже, що "try / catch" створить найбільш захищений від майбутніх кодів код і, можливо, просто перетворить його на функцію


1
Що ти тут навіть порівнюєш? Це здається коментарем до інших відповідей, але незрозуміло, які з них, і це не відповідає на саме запитання.
ShadowRanger
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.