8

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

1

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

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

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

5 ответ(ов)

2

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

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

# атрибут должен быть инициализирован
myfunc.counter = 0

В качестве альтернативы, если вы не хотите настраивать переменную вне функции, вы можете использовать hasattr(), чтобы избежать исключения AttributeError:

def myfunc():
    if not hasattr(myfunc, "counter"):
        myfunc.counter = 0  # атрибут еще не существует, инициализируем его
    myfunc.counter += 1

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

0

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

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

В этом решении нет необходимости использовать try/except или проверять hasattr — достаточно воспользоваться getattr с значением по умолчанию.

0

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

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

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

0

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

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

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

Использование замыканий

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

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

counter = make_counter()

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

Использование параметра внутреннего состояния

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

def counter(*, _i=[0]):
    _i[0] += 1
    return _i[0]

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

Чаще всего будет более чисто использовать тип контейнера вместо списка, например:

def counter(*, _i=Mutable(0)):
    _i.value += 1
    return _i.value

Но я не знаю ни одного встроенного типа, который четко передает это намерение.

0

Ваш код определяет переменную _counter, которую нельзя считать полностью "приватной" в Python, так как Python не имеет настоящего уровня доступа, как языки программирования C или Java. Однако согласно соглашениям о кодировании, переменные с одним подчеркиванием в начале имени обычно считаются "защищенными" и не должны использоваться за пределами модуля.

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

Пример переосмысленного кода с использованием класса будет выглядеть следующим образом:

class Counter:
    def __init__(self):
        self._counter = 0
    
    def increment(self):
        self._counter += 1
        print('counter is', self._counter)

# Использование
counter_instance = Counter()
counter_instance.increment()  # counter is 1
counter_instance.increment()  # counter is 2

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь