Локальная переменная, на которую нет ссылки в определении декоратора - PullRequest
0 голосов
/ 02 октября 2019

Мой код ведет себя странно, и я не могу понять, почему.

Вот код:

from django.urls import path


app_name = 'portal'
urlpatterns = []

def route(url, name=""):

    def dec(f):
        f_name = name or f.__name__
        urlpatterns.append(
            path(url, f, name=f_name)
        )   
        return f
    return dec 

from . import views                  
# 2) The decorator call in the other file
from . import urls

@urls.route("/my_function")
def my_function():
    print("Hello world")

Я получаю UnboundLocalError на name

 File "urls.py", line 10, in dec
    if name == "":
UnboundLocalError: local variable 'name' referenced before assignment

name должен быть установлен на "" по умолчанию, я не понимаю, в чем проблема. Странная вещь, если я запускаю тот же код и меняю декоратор на это:

urlpatterns = []

def route(url, name=""):

    def dec(f):
        if name == "":
            print("I work!")
        urlpatterns.append(
            path(url, f, name=name)
        )
        return f
    return dec

Он работает отлично и выдает:

I work ! 

, в то время как проблема должна была исходить изстрока if name == ""

PS: я программирую на django, эта строка находится в файле urls.py.

1 Ответ

1 голос
/ 02 октября 2019

Ответ находится в той части, которую вы не опубликовали. Ваш реальный код на самом деле выглядит примерно так:

def route(url, name=""):    
    def dec(f):
        if name == "":
            # here's the real issue
            name = "something_" + f.__name__

        urlpatterns.append(
            path(url, f, name=name)
        )
        return f
    return dec

. Присвоение name делает его локальной переменной - Python не имеет объявления переменной, поэтому именно место, где связано имя, определяет его область действия. В вашем случае присвоение имени в dec делает имя «имя» локальным для dec, поэтому оно НЕ ищется в прилагаемых областях. И так как вы проверяете его («ссылка») до , который вы присваиваете ему, вы получаете очень очевидную (нет, просто шучу) «локальную переменную« имя », на которую ссылаются перед присвоением».

Решение здесь состоит в том, чтобы объявить name как "нелокальную" в верхней части вашей dec функции, чтобы Python знал, что ее нужно искать во вложенной области видимости (или, точнее, в ячейках замыкания, которые захватываютdec окружение функции):

def route(url, name=""):    
    def dec(f):
        nonlocal name

        if name == "":
            # here's the real issue
            name = "something_" + f.__name__

        urlpatterns.append(
            path(url, f, name=name)
        )
        return f
    return dec

Обратите внимание, что это работает только для Python3 - если вы используете Python2, вам придется прибегнуть к хаку, чтобы эмулировать это поведение:

def route(url, name=""):    
    # Py2 hack: wrap the "nonlocal" variable in
    # a mutable container

    name = [name]

    def dec(f):

        if name[0] == "":
            name[0] = "something_" + f.__name__

        urlpatterns.append(
            path(url, f, name=name[0])
        )
        return f
    return dec

Взлом здесь, что вы мутируете name вместо повторного связывания, поэтому Python не помечает его как локальную переменную.

Как примечание, я бы не сталне рекомендую пытаться портировать эту @route(url) идиому (Flask кто-нибудь?) на Django. Во-первых, потому что отделение определений представлений от отображения URL является преднамеренным дизайнерским решением, которое позволяет переназначить URL-адреса приложений третьей части на все, что мы хотим, без хаков, вилок и т. Д., А также потому, что большинство разработчиков Django ожидают, что URL-адреса будут точно определены вurls.py модуль и будет ненавидеть вас за несоблюдение конвенции. Теперь вы, конечно, можете писать свой проект так, как вам хочется, но соблюдение соглашений облегчает задачу для всех. Мои 2 цента ...

...