装饰器定义中未引用的局部变量

Local variable unreferenced in decorator definition

我的代码表现得很奇怪,我不明白为什么。

代码如下:

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")

我在 name

上得到了一个 UnboundLocalError
 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 文件中。

答案在您没有发布的部分。您的真实代码实际上看起来像

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 中的名称会使名称 "name" 成为 dec 的本地名称,因此不会在封闭范围中查找它。既然你测试它 ("reference") before 你分配给它,你会得到非常明显的(不,开玩笑)“局部变量 'name' 在赋值之前引用" 错误。

此处的解决方案是在 dec 函数的顶部将 name 声明为 "nonlocal",因此 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,您将不得不诉诸 hack 来模拟此行为:

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

这里的 hack bing 你 mutate name 而不是重新绑定它,所以 Python 不会将它标记为本地变量。

作为旁注,我不建议尝试将此 @route(url) 习语(Flask 任何人?)移植到 Django。首先,因为将视图定义与 url 映射分开是一个深思熟虑的设计决策,它允许将第三方应用程序 urls 重新映射到我们想要的任何东西,而无需 hack 或 forks 等,也因为大多数 Django开发人员希望 urls 在 urls.py 模块中明确定义,并且会因为你不遵守约定而讨厌你。现在您当然可以随心所欲地编写您的项目,但是遵循约定会让每个人都更容易。我的2美分...