10

在我的 Flask-App 中,我定义了一个这样的视图函数:

@app.route("/some/restricted/stuff")
@login_required
def main():
    return render_template("overview.html",
                       stuff = getstuff() )

装饰器被定义为:

def login_required(something):
    @wraps(something)
    def wrap(*args, **kwargs):
        if "some_admin_name" in session:
            return something(*args, **kwargs)
        else:
            flash("\"You shall not pass!\" - Gandalf")
            return redirect(url_for("login"))
    return wrap

我基本上只是复制粘贴,因为我发现了一些使用此代码的来源,但没有解释。

很容易理解该代码的作用:它允许我使用一个装饰器,该装饰器在 app.route() 之后和 main() 之前为每个请求调用,允许我执行检查活动登录等操作。

所以,作为一个 Flask/Python 新手,我只想知道这到底是如何工作的,尤其是:
- “某事”的论点是什么?是这个要求吗?!
- args 和 kwargs(关键字参数?)是什么?
- 为什么我必须在方法内包装一个方法才能将其用作装饰器?
- 这只能与烧瓶一起使用吗?还有其他类似的情况可以派上用场吗?

4

3 回答 3

30

欢迎来到 Python!这是很多很棒的问题。让我们一次拿一个。此外,只是一个公平的警告点。这个话题让你头晕目眩,然后才全部合二为一。

作为参考,这是您的示例装饰器和被装饰的函数:

# Decorator Function
def login_required(something):
    @wraps(something)
    def wrap(*args, **kwargs):
        if "some_admin_name" in session:
            return something(*args, **kwargs)
        else:
            flash("\"You shall not pass!\" - Gandalf")
            return redirect(url_for("login"))
    return wrap

# Function Being Decorated
@app.route("/some/restricted/stuff")
@login_required
def main():
    return render_template("overview.html",
                       stuff = getstuff() )

“某事”的论据是什么?是这个要求吗?!

要回答这个问题,我们首先要回答什么是装饰器。根据您正在装饰的对象类型,答案可能会有所不同。在这种情况下,当您正在装饰一个函数时,您可以将装饰器视为一种方法/函数,它允许程序员修改另一个函数的行为。

有了这个,我们可以回答你的问题。“某物”是您要装饰的功能/方法。 是的,它是一个将另一个函数作为参数的函数。

让我们更改装饰器函数的语言以使其更清晰:

def login_required(function_to_wrap):
    @wraps(function_to_wrap)
    def wrap(*args, **kwargs):
        if "some_admin_name" in session:
            return function_to_wrap(*args, **kwargs)
        else:
            flash("\"You shall not pass!\" - Gandalf")
            return redirect(url_for("login"))
    return wrap

args 和 kwargs 是什么?

简短的回答是,这是 Python 允许参数程序员编写采用可变数量的关键字和非关键字参数的函数/方法的方式。

通常,在编写函数时,会显式指定参数。例如:

def add_these_numbers(number_1, number_2):
    return number_1 + number_2

然而,这并不是唯一的做事方式。你也可以使用 *args 或 **kargs 来完成同样的事情:

def add_these_numbers(*args):
    return args[0] + args[1]

def add_these_numbers_too(**kwargs):
    return kwargs['first_number'] + kwargs['second_number']

因为它与您的问题有关,*args/**kwargs所以通常在装饰器中使用,因为装饰器通常应用于各种方法,这些方法将采用各种参数。

Usingargs/**kwargs允许您的装饰器方法通过装饰器函数传递该方法最初需要的方法。 如果这让你头晕目眩,请告诉我,我会尽力澄清。

让我们更改 main() 以便更清楚:

# Function Being Decorated
@app.route("/some/restricted/stuff")
@login_required
def main(html_template):
    return render_template(html_template, stuff = getstuff())

为什么我必须在方法内包装一个方法才能将其用作装饰器?

在我看来,这是理解装饰器最棘手的部分。 关键是要理解,装饰器的核心是接管原始函数的名称。

理解这一点的最简单方法是应用装饰器而不使用方便的 @ 语法。以下是等价的:

@login_required
def main():
    ....

main = login_required(main)

抓住你的马,这就是令人敬畏的地方! 这两个代码片段告诉 Python 的是,“'main' 这个词应该不再指代 main() 函数,而是当它作为参数传递给原始 main() 函数时的结果 login_required() 函数。

哇!?

是的。对 main() 的调用现在指的是对 login_required(main()) 的调用结果。这也是 login_required 返回嵌套函数的原因。新的 main() 必须仍然是一个函数,就像旧的一样。

不同的是,现在新的 main 函数实际上是 wrap() 的一个实例,由传递给 login_required() 的参数自定义。

所以...实际上 main() 现在等效于以下内容:

def main(*args, **kwargs):
    if "some_admin_name" in session:
        return predecorator_main_function(*args, **kwargs)
    else:
        flash("\"You shall not pass!\" - Gandalf")
        return redirect(url_for("login"))

这仅适用于 Flask 吗?还有其他类似的情况可以派上用场吗?

当然不!装饰器是 Python 内置的众多超棒功能之一。当您不想创建其他函数以避免代码重复时,装饰器在您想要对现有函数/方法进行修改(我认为相对较小)的任何情况下都很有用

于 2013-11-05T21:39:21.767 回答
1

装饰器简介:您可以在此处阅读。

args 和 kwargs(关键字参数?)是什么?

args当你不确定有多少参数可以传递给你的函数时。

random(*args)
random('d', 'c', 'b')
random('a', 'e', 'f','z')

kwargs: 命名参数。基本上如上所述,就像你在传递一本字典。

random(**kwargs)
random(a="a", b='test', c='ab')

为什么我必须在方法内包装一个方法才能将其用作装饰器?

检查链接装饰器链接。

这只适用于烧瓶吗?还有其他类似的情况可以派上用场吗?

不,装饰器通常在 Python 中可用,它不仅限于烧瓶。您可以将它用于日志记录、同步和许多其他用途。在 Django 中,您可以使用装饰器或 login_required 等来允许发布或获取请求...

于 2013-11-05T20:18:48.713 回答
0

这:

@deco
def foo():
    return 100

等于:

def temp_foo():
    return 100
foo = deco(temp_foo)

for 的定义login_required采用旧的mainas something,围绕它包装一个新函数,然后返回那个。

于 2013-11-05T20:10:09.477 回答