Возможно ли создать переменную в функции-обертке, а использовать ее в декорируемой функции?
Задача из попытки совместить tkinter, sqlite и декораторы.
В коде в нескольких местах встречается конструкция вида:
def sql_x():
conn = sqlite('some.db')
c = conn.cursor()
c.execute(command(some_params_from_menu_selected))
conn.commit()
conn.close()
Хочется сделать декоратор типа
def sqler(f):
def wrapper():
conn = sqlite('some.db')
c = conn.cursor()
f() # внутри f() - только команда SQLite была бы ...
conn.commit()
conn.close()
return wrapper
def fin():
# c - определяется в обертке...
# global c - не работает (
# nonlocal c - не работает (
print(c, "SQL -command")
# применение декоратора:
zz = sqler(fin)
zz()
Но ни при указании для с область видимости global или nonlocal - одна и таже проблема - код падает. То с не определено, то замыкания не существует.
Думаю, для этой задачи уже существует решение, но не нашел в сети.
Проблема в том, что передавать параметр нельзя.
Решение нашлось. Но теперь я не понимаю - епе гп мпили деле работает в этом случае global.
Если переставить global не в декорируеиую функцию, а в обертку (wrapper), неожиданно все заработало:
def sqler(fun):
def wrapper():
global c
print(1)
c = dict(k=3)
fun()
print(2)
return wrapper
@sqler
def fin():
# global c
# nonlocal c
print(c)
# ВЫЗЫВАЕМ fin
fin()
И все сработало:
1
{'k': 3}
2
Теперь у меня поменялся вопрос. Почему это работает?? Почему global надо ставить в обертку, а не в декорируемую функцию?? Ведь c в обертке и определяется, для нее он не global...
Подскажите или киньте в меня ссылку)
Ответы (1 шт):
Перенесу из комментариев.
Теперь у меня поменялся вопрос. Почему это работает?? Почему global надо ставить в обертку, а не в декорируемую функцию?? Ведь c в обертке и определяется, для нее он не global...
Потому что обертка меняет глобальную переменную, а декорируемая функция - нет.
global нужно прописывать в функции, которая создает глобальную переменную или изменяет ее значение. Декорируемая функция никак не меняет глобальную переменную, а только обращается к ней, поэтому global в ней не нужен.
Тут особых рассуждений не нужно, все достаточно просто: если функция модифицирует значение глобальной переменной, значит в этой функции нужно для Python указать, что мы изменяем именно глобальную переменную, а не локальную.
В функции, которая не изменяет глобальную переменную, Python и так поймет, что если локальной переменной с таким именем нет, то нужно искать глобальную переменную (или нелокальную переменную в функции на уровень вложенности выше, если несколько вложенных функций), поэтому прописывать global в такой функции не обязательно (но его наличие не будет ошибкой).
Вообще, я бы сделал не через глобальные переменные, а через передачу параметра из обертки в декорируемую функцию.
насколько я понимаю, в tkinter функция, передаваемая как command параметр, сама не должна иметь параметров. Еслия ошибаюсь - был бы рад этому
Не должна, но в данном случае обертка без параметров может заменить функцию с параметрами (обертка ничего не будет принимать, но создавать то же соединение внутри себя и передавать его внутрь декорируемой функции через параметр), и все будет работать (помним, что добавление декоратора sqler к функции fin аналогично записи fin = sqler(fin), sqler возвращает обертку, в итоге исходная функция заменяется на обертку).
Рабочий пример (модифицированный пример из вопроса, но без использования глобальных переменных):
def sqler(fun):
def wrapper():
print(1)
connection = dict(k=3)
fun(connection)
print(2)
return wrapper
@sqler
def fin(c):
print(c)
# ВЫЗЫВАЕМ fin
fin()
Это будет работать и при использовании такой функции, например, в качестве обработчика нажатия на кнопку в tkinter:
tk.Button(text="Press me", command=fin).pack()