Страницы

Поиск по вопросам

понедельник, 29 октября 2018 г.

Глобальные переменные в Python: сохранить локальную переменную от вызова к вызову функции

У меня есть функция, которой необходимо сохранять значение от вызова к вызову, при этом это значение используется только в этой функции. Как правильно использовать глобальные переменные в 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
При желании вы можете скомбинировать этот способ с каким-нибудь из описанных ранее.

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

Комментариев нет:

Отправить комментарий