У меня есть функция, которой необходимо сохранять значение от вызова к вызову, при этом это значение используется только в этой функции. Как правильно использовать глобальные переменные в Python? Я пробовал написать нечто подобное:
someGlobalVar = 0
def incrimentGlobalVar()
someGlobalVar = someGlobalVar + 1
Но данный код не работает. Может есть другие способы решения данной задачи?
Ответ
Есть несколько способов реализовать подобное поведение.
Глобальные переменные
Первое, что может прийти в голову, это использовать глобальные переменные.
Важно учитывать то, что глобальные переменные доступы на чтение
def func1():
print(x)
x = 10
func1() # 10
но простое использование на запись не разрешается:
def func2():
y = 20 # создаёт локальную переменную, а не изменяет глобальную
y = 10
func2()
print(y) # 10
Более того, при попытке обратиться к переменной сначала на чтение, потом на запись, мы получим ошибку:
def func3():
print(z)
z = 20
z = 10
func3() # UnboundLocalError: local variable 'z' referenced before assignment
Это происходит из-за того, что использование присваивания переменной z обозначает её, как локальную (как в случае 2). Попытка вывести значение локальной переменной, у которой ещё не задано значение, как раз и порождает возникновение этой ошибки.
Аналогичный пример как раз приведён в вашем вопросе. Там тоже переменная someGlobalVar определяется, как локальная, потому что выполняется присваивание. Так как в этом присваивании используется сначала чтение значения ещё не инициализированной переменной someGlobalVar, мы получаем ту же ошибку.
Для того, чтобы этот пример работал, необходимо предварительно пометить переменную, как global
def func4():
global w
print(w)
w = 20
w = 10
func4() # 10
print(w) # 20
Аналогично будет работать и в вашем случае.
Использование поля функции
Второй способ, который может прийти в голову, это использование объекта функции для хранения состояния функции.
def func5():
if not hasattr(func5, '_state'): # инициализация значения
func5._state = 0
print(func5._state)
func5._state = func5._state + 1
# до первого вызова функции значение не установлено
# print(func5._state) # AttributeError: 'function' object has no attribute '_state'
func5() # 0
print(func5._state) # 1
В этом способе удобно то, что значение ассоциировано с самой функцией.
Стоит быть осторожным, давая имена подобным полям функции, так как в Python 2 у функции есть стандартные поля с названиями, не начинающимися с двух подчёркиваний, например, func_closure, func_code и т.п. Все они начинаются с func_, поэтому главное не использовать этот префикс и не начинать название поля с __, в остальных случаях шанс коллизии имён практически равен нулю.
Использование класса с поведением функции
Третий способ заключается в создании класса с поведением функции. Это наиболее удобный и безопасный, по моему мнению, способ реализации подобного поведения. Просто создайте класс и перегрузите его метод __call__
class FuncCreator:
def __init__(self, start_state):
self.state = start_state
def __call__(self):
print(self.state)
self.state += 1
func6 = FuncCreator(0)
print(func6.state) # 0
func6() # 0
print(func6.state) # 1
Это увеличивает объём кода, но добавляет удобств от использования функциональности класса.
Использование изменяемого объекта, как значение по умолчанию для параметра
Четвёртый способ заключается в том, чтобы создать функцию, у которой будет необязательный параметр, использующий изменяемое значение в качестве состояния:
def func7(state=[]):
if not state:
state.append(0)
print(state[0])
state[0] += 1
func7() # 0
func7() # 1
В качестве объекта состояния можно использовать любой изменяемый объект. Это использует то, что все значения по умолчанию присваиваются один раз.
Использование декоратора, выполняющего необходимые вычисления
Если вернуться к исходному примеру, то для подсчёта числа вызовов функции будет также может быть удобно использовать декораторы. Это позволит в том числе и переиспользовать код.
Для Python 3 код может выглядеть, например, так:
from functools import wraps
def call_count(func):
count = 0
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal count
count += 1
func(*args, **kwargs)
print(count)
return wrapper
В Python 2 нет nonlocal, но можно использовать изменяемые переменные:
from functools import wraps
def call_count(func):
count = [0]
@wraps(func)
def wrapper(*args, **kwargs):
count[0] += 1
func(*args, **kwargs)
print(count[0])
return wrapper
Использоваться это будет следующим образом:
@call_count
def f():
pass
f() # 1
f() # 2
При желании вы можете скомбинировать этот способ с каким-нибудь из описанных ранее.
Из всех выше упомянутых способов я бы рекомендовал использовать классы (так как функция с изменяющимся состоянием уже больше похожа на класс, чем на функцию) или поле функции, в случае необходимости быстрого добавления функциональности в код.
Комментариев нет:
Отправить комментарий