Возможно ли создать переменную в функции-обертке, а использовать ее в декорируемой функции?

Задача из попытки совместить 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 шт):

Автор решения: insolor

Перенесу из комментариев.

Теперь у меня поменялся вопрос. Почему это работает?? Почему 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()
→ Ссылка