Python 装饰器 - func_wrapper() 不带参数(给定 1 个)

Posted

技术标签:

【中文标题】Python 装饰器 - func_wrapper() 不带参数(给定 1 个)【英文标题】:Python decorator - func_wrapper() takes no arguments (1 given) 【发布时间】:2015-09-18 02:16:36 【问题描述】:

我正在尝试写我的第一个decorator,但有点迷茫。我希望 decorator 在执行主函数之前检查 request 是否来自特定地址。

目前我有:

def check_referrer(url):
    def func_wrapper():
        if request.referrer == url:
            return render_template('index.html', error=None)
        else:
            return render_template('login.html', error="some_error")
    return func_wrapper

@app.route('/index', methods = ['GET'])
@check_referrer("/venue/login")
def index():
    return

@app.route /venue/login(此代码已简化)

@app.route('/venue/login', methods=['GET', 'POST'])
def login():

    error = None

    if login_valid():
        return redirect(url_for('index'))                                                                                   
    else:
        error = 'Invalid Credentials. Please try again.'

    return render_template('login.html', error=error)

1) 我确信我正在做的事情存在一些问题,但我首先需要了解我收到错误的原因:

TypeError: func_wrapper() takes no arguments (1 given)

我以为我只是将argument 传递给check_referrer

2) 我的return 语句正确吗?

任何帮助将不胜感激!

【问题讨论】:

func_wrapper 必须接受被包装的函数作为参数。 def func_wrapper(f): 也许this 会有所帮助。 【参考方案1】:

考虑使用Flask-Login 来处理身份验证并在用户未登录时重定向。(或仔细检查它的工作原理。)它比您编写的函数更强大地处理这一点。


你写的函数不是装饰器,还有很多其他问题。首先,它需要适当的结构。它需要 3 层:

    第 1 层函数采用 url 参数并生成装饰器。 第 2 层函数装饰一个函数并返回一个包装函数。 第 3 层是装饰视图。

request.referrer 包含整个 url,而不仅仅是匹配路由的路径。使用urlparse 获取路径。无法保证客户端的浏览器会发送引荐来源网址或正确的引荐来源网址,因此您不应依赖此值。

装饰函数需要接受可以传递给视图的任意参数。装饰函数应使用wraps 正确包装原始函数。

只从同一个视图渲染不同的模板并不是一个好主意。而不是呈现index.htmllogin.html,您应该重定向到相关视图。如果您需要将消息与重定向一起传递,请将它们放在 session 中。

from functools import wraps
from urllib.parse import urlparse
# or from urlparse import urlparse for py2
from flask import request, session, redirect

def check_referrer(path):
    def decorator(f):
        @wraps(f)
        def decorated(*args, **kwargs):
            if not request.referrer:
                session['error'] = 'no referrer'
                return redirect('login')

            referrer_path = urlparse(request.referrer).path

            if referrer_path != path:
                session['error'] = 'expected referrer !r'.format(path)
                return redirect('login')

             return f(*args, **kwargs)

        return decorated

    return decorator

【讨论】:

【参考方案2】:

理解python装饰器的关键是这样的:

@decorator
def func():
    pass

等价于:

func = decorator(func)

现在您可以理解为什么您的代码不起作用了:@check_referrer("/venue/login") 返回一个不带参数的函数func_wrapper,因此func_wrapper 不能是装饰器。

您可以使用 2 级内部函数定义不带参数的装饰器。要制作一个接受参数的装饰器,您需要另一个层次的内部函数,如 davidism 的代码所示。

会发生什么:

decorator = check_referrer(url) 
decorated = decorator(index) 
index = decorated

【讨论】:

以上是关于Python 装饰器 - func_wrapper() 不带参数(给定 1 个)的主要内容,如果未能解决你的问题,请参考以下文章

Python 装饰器和装饰器模式有啥区别?

python 装饰器:装饰器实例内置装饰器

python 装饰器:装饰器实例内置装饰器

python 装饰器:装饰器实例类装饰器(装饰函数)

python 装饰器:装饰器实例类装饰器(装饰函数)

理解Python装饰器