【问题标题】:Python decorator internally calls wrong functionPython 装饰器内部调用了错误的函数
【发布时间】:2013-09-16 16:08:27
【问题描述】:

我们已经实现了一个扭曲的 web api。

为了处理身份验证,我们使用了一个装饰器,我们用它来包装一些路由。

@requires_auth(roles=[Roles.Admin])
def get_secret_stuff(request):
    return 42

requires_auth 包装器实现如下。

def requires_auth(roles):
    def wrap(f):
        def wrapped_f(request, *args, **kwargs):
            # If the user is authenticated then...
            return f(request, *args, **kwargs)
        return wrapped_f
    return wrap

问题是如果这个装饰器有多个路由,那么调用 它们中的任何一个都会导致调用要装饰的最新路由。

这显然不是我想要的,与我对装饰器应该如何工作的理解背道而驰。 我在代码中添加了一些打印语句来尝试弄清楚:

def requires_auth(roles):
    def wrap(f):
        print(f) # This shows that the decorator is being called correctly once per each
                 # route that is decorated
        def wrapped_f(request, *args, **kwargs):
            # If the user is authenticated then...
            return f(request, *args, **kwargs)
        return wrapped_f
    return wrap

如果它很重要,我将使用 twisted 的 inlineCallbacks 来处理其中一些路由,以及使用 twisted web 的 @app.route(url, methods) 装饰器来处理所有这些路由。

感谢您的阅读:)

编辑: 我删除了构造函数的默认参数,因为有人告诉我这是一个坏主意:)

编辑:这是一个说明问题的最小示例:

from klein import Klein
import json
app = Klein()

def requires_auth(roles):
    def wrap(f):
        print('inside the first clojure with f=%s' % str(f))
        def wrapped_f(request, *args, **kwargs):
            print('inside the second closure with f=%s' % str(f))
            return f(request, *args, **kwargs)
        return wrapped_f
    return wrap

@app.route('/thing_a')
@requires_auth(roles=['user'])
def get_a(request):
    return json.dumps({'thing A': 'hello'})

@app.route('/thing_b')
@requires_auth(roles=['admin'])
def get_b(request):
    return json.dumps({'thing B': 'goodbye'})

app.run('0.0.0.0', 8080)

去路由'/thing_a'会产生来自route_b的json

【问题讨论】:

  • 不应该wrap返回wrapped_f吗?
  • 是的,对不起,我错误地漏掉了,更新了。
  • 你有@requires_authdef require_auth...注意s
  • 再一次,这是一个错字:p
  • 能否在wrapped_f的开头和结尾打印f以确保还是一样?

标签: python twisted decorator


【解决方案1】:

试试这个:

from functools import wraps

def require_auth(roles=(Roles.USER,), *args, **kwargs):

    def call(f, *args, **kwargs):
        return f(*args, **kwargs)

    def deco(f):
        @wraps(f)
        def wrapped_f(request, *a, **kw):
            # do your authentication here
            return call(f, request, *a, **kw)

        return wrapped_f

    return deco

【讨论】:

  • 是@wraps 修复了它。即使没有电话,这仍然有效:)
【解决方案2】:

避免使用可变参数(例如列表)作为任何函数或方法的默认参数。 More on why this is a bad idea.

我无法确认,但很有可能这是导致您的问题的原因。

编辑:如果我不清楚,我指的是

def requires_auth(roles=[Roles.USER]):

默认参数是可变的(一个列表)。

【讨论】:

  • 感谢您的回答,但是在删除默认参数后,装饰器似乎仍然调用了错误的函数:(
  • 默认可变参数如果你不实际改变它们就可以了。虽然def require_auth(roles=(Roles.USER,)):在这里会更好
【解决方案3】:

您需要以正确的顺序应用您的装饰器。这可能会起作用:

@route(...)
@requires_auth(roles=[Roles.Admin])
def get_secret_stuff(request):
    return 42

这可能不会:

@requires_auth(roles=[Roles.Admin])
@route(...)
def get_secret_stuff(request):
    return 42

因为这些分别意味着

get_secret_stuff包装在一个授权器中,并将结果用作路由

使用get_secret_stuff 作为路由,并将结果包装在授权方中。授权者永远不会进入路由。

【讨论】:

  • 谢谢,但我尝试只定义了两条路由并排列了每个合理的包装器顺序,但它仍然显示相同的问题
  • @avoid3d:您需要在您的问题中向我们展示更多代码 - 您将其简化为的情况没有任何问题。
  • 好的,谢谢,我应该重新编辑问题还是创建一个新问题。我打算做一个独立的例子
猜你喜欢
  • 2017-08-01
  • 2018-01-28
  • 2020-12-29
  • 2017-06-06
  • 2018-05-08
  • 1970-01-01
  • 2017-12-11
  • 1970-01-01
  • 2018-10-03
相关资源
最近更新 更多