аватар question@mail.ru · 01.01.1970 03:00

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

У меня есть функция, которой необходимо сохранять значение от вызова к вызову, при этом это значение используется только в этой функции. Как правильно использовать глобальные переменные в Python? Я пробовал написать нечто подобное:

someGlobalVar = 0def incrimentGlobalVar()    someGlobalVar = someGlobalVar + 1

Но данный код не работает. Может есть другие способы решения данной задачи?

аватар answer@mail.ru · 01.01.1970 03:00

Есть несколько способов реализовать подобное поведение.

Глобальные переменные

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

def func1():    print(x)x = 10func1()   # 10

но простое использование на запись не разрешается:

def func2():    y = 20    # создаёт локальную переменную, а не изменяет глобальнуюy = 10func2()print(y)      # 10

Более того, при попытке обратиться к переменной сначала на чтение, потом на запись, мы получим ошибку:

def func3():    print(z)    z = 20z = 10func3()   # UnboundLocalError: local variable 'z' referenced before assignment

Это происходит из-за того, что использование присваивания переменной z обозначает её, как локальную (как в случае 2). Попытка вывести значение локальной переменной, у которой ещё не задано значение, как раз и порождает возникновение этой ошибки.

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


Для того, чтобы этот пример работал, необходимо предварительно пометить переменную, как global:

def func4():    global w    print(w)    w = 20w = 10func4()    # 10print(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()                # 0print(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 += 1func6 = FuncCreator(0)print(func6.state)  # 0func6()             # 0print(func6.state)  # 1

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


Использование изменяемого объекта, как значение по умолчанию для параметра

Четвёртый способ заключается в том, чтобы создать функцию, у которой будет необязательный параметр, использующий изменяемое значение в качестве состояния:

def func7(state=[]):    if not state:        state.append(0)    print(state[0])    state[0] += 1func7()  # 0func7()  # 1

В качестве объекта состояния можно использовать любой изменяемый объект. Это использует то, что все значения по умолчанию присваиваются один раз.


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

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

Для Python 3 код может выглядеть, например, так:

from functools import wrapsdef call_count(func):    count = 0    @wraps(func)    def wrapper(*args, **kwargs):        nonlocal count        count += 1        func(*args, **kwargs)        print(count)    retu wrapper

В Python 2 нет nonlocal, но можно использовать изменяемые переменные:

from functools import wrapsdef call_count(func):    count = [0]    @wraps(func)    def wrapper(*args, **kwargs):        count[0] += 1        func(*args, **kwargs)        print(count[0])    retu wrapper

Использоваться это будет следующим образом:

@call_countdef f():    passf()  # 1f()  # 2

При желании вы можете скомбинировать этот способ с каким-нибудь из описанных ранее.


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

Последние

Похожие