Каков эквивалент статических переменных внутри функции в Python?
Вопрос: Какой идиоматический аналог этого кода на C/C++ на Python?
void foo()
{
static int counter = 0;
counter++;
printf("counter is %d\n", counter);
}
Конкретно, как реализовать статический член на уровне функции, а не на уровне класса? И изменяет ли размещение функции в классе что-то?
5 ответ(ов)
Вы можете добавлять атрибуты к функции и использовать их как статическую переменную. Например:
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
Тем не менее, статические переменные довольно редки, и вам стоит рассмотреть возможность переноса этой переменной в более подходящее место, скорее всего, внутрь класса.
Многие уже предлагали использовать hasattr
, но есть более простой способ:
def func():
func.counter = getattr(func, 'counter', 0) + 1
В этом решении нет необходимости использовать try/except
или проверять hasattr
— достаточно воспользоваться getattr
с значением по умолчанию.
Другие ответы уже продемонстрировали, как правильно это делать. Вот способ, которым делать этого не стоит:
>>> def foo(counter=[0]):
... counter[0] += 1
... print("Counter is %i." % counter[0]);
...
>>> foo()
Counter is 1.
>>> foo()
Counter is 2.
>>>
Стоит обратить внимание, что значения по умолчанию инициализируются только при первом выполнении функции, а не каждый раз при её вызове. Поэтому использование списка или любого другого изменяемого объекта для хранения статических значений может привести к неожиданному поведению.
Использование атрибута функции в качестве статической переменной имеет несколько потенциальных недостатков:
- Каждый раз, когда вы хотите получить доступ к переменной, вам нужно писать полное имя функции.
- Внешний код может легко получить доступ к переменной и изменить её значение.
В идиоматичном 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
Но я не знаю ни одного встроенного типа, который четко передает это намерение.
Ваш код определяет переменную _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
Такой подход лучше соблюдает принципы инкапсуляции и позволяет избежать глобальных состояний.
Как клонировать список, чтобы он не изменялся неожиданно после присваивания?
Преобразование списка словарей в DataFrame pandas
Как отсортировать список/кортеж списков/кортежей по элементу на заданном индексе
Как отменить последнюю миграцию?
Как явно освободить память в Python?