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

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

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

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

Ответы [ 26 ]

6 голосов
/ 03 августа 2015

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

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

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

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

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

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

6 голосов
/ 08 апреля 2018

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

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

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

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

4 голосов
/ 02 августа 2016

Немного более читабельно, но более многословно:

>>> def func(_static={'counter': 0}):
...     _static['counter'] += 1
...     print _static['counter']
...
>>> func()
1
>>> func()
2
>>>
2 голосов
/ 20 июля 2018

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

import types

def func(_static=types.SimpleNamespace(counter=0)):
    _static.counter += 1
    print(_static.counter)
2 голосов
/ 26 октября 2017

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

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

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

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

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

2 голосов
/ 02 октября 2013

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

@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
1 голос
/ 20 сентября 2018

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

counter = 0

def foo():
    global counter
    counter += 1
    print("counter is {}".format(counter))

foo() #output: "counter is 1"
foo() #output: "counter is 2"
foo() #output: "counter is 3"
1 голос
/ 27 марта 2018

Soulution n + = 1

def foo():
  foo.__dict__.setdefault('count', 0)
  foo.count += 1
  return foo.count
1 голос
/ 22 марта 2018

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

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

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

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

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

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

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

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