Python装饰器和类继承

9
我试图使用装饰器来管理用户在网络应用程序(运行在Google App Engine上)中访问资源的方式。请注意,我不允许用户使用他们的Google帐户登录,因此在app.yaml中设置特定路由的特定访问权限不是一种选择。
我使用了以下资源:
- Bruce Eckel的装饰器指南
- SO:获取Python装饰器中的类2
- SO:Python装饰器和继承
- SO:获取Python装饰器中的类 然而,我仍然有点困惑...
这是我的代码!在下面的示例中,current_user是属于RequestHandler类的@property方法。它返回存储在数据存储中的User(db.model)对象,并具有一个级别IntProperty()。
class FoobarController(RequestHandler):

    # Access decorator
    def requiredLevel(required_level):
        def wrap(func):
            def f(self, *args):
                if self.current_user.level >= required_level:
                    func(self, *args)
                else:
                    raise Exception('Insufficient level to access this resource') 
            return f
        return wrap

    @requiredLevel(100)
    def get(self, someparameters):
        #do stuff here...

    @requiredLevel(200)
    def post(self):
        #do something else here...

然而,我的应用程序针对不同类型的资源使用不同的控制器。为了在所有子类中使用@requiredLevel修饰符,我需要将其移到父类(RequestHandler)中:
class RequestHandler(webapp.RequestHandler):

    #Access decorator
    def requiredLevel(required_level):
        #See code above

我的想法是使用以下代码访问所有控制器子类中的装饰器:

class FoobarController(RequestHandler):

    @RequestHandler.requiredLevel(100)
    def get(self):
        #do stuff here...

我觉得我已经达到了装饰器和类继承方面知识的极限 :). 你有什么想法吗?


1
为什么这是一个类中的方法?这只会导致出现错误,并且它只能像普通函数一样在定义它的类内起作用。除非你在3.x上,否则它可能无法正常工作。 - Devin Jeanpierre
装饰器是类上的一个方法,因为我还没有想出如何编写一个可以接受参数并且可以访问当前类方法的装饰器类。这是你所指的吗?作为一个大多数自学的人,我很难完全理解Bruce Eckell的指南,关于装饰器和继承。 - jbmusso
你可以将函数复制粘贴到类外部,它就能正常工作。这样回答你的问题足够了吗? - Devin Jeanpierre
将 requiredLevel 装饰器从 FoobarController 移动到 RequestHandler 并使用 @staticmethod 进行装饰似乎是根据 https://dev59.com/5XA75IYBdhLWcg3w-OVA 的解决方案,但在我的情况下它并没有起作用。最有可能是因为我的装饰器接受参数。 - jbmusso
不,我的意思是完全从类中删除它。 把它变成一个常规函数。 - Devin Jeanpierre
显示剩余2条评论
2个回答

4

您原来的代码只需要进行两个小改动,就可以工作。基于类的方法似乎对于这样一个简单的装饰器来说有些过重:

class RequestHandler(webapp.RequestHandler):

    # The decorator is now a class method.
    @classmethod     # Note the 'klass' argument, similar to 'self' on an instance method
    def requiredLevel(klass, required_level):
        def wrap(func):
            def f(self, *args):
                if self.current_user.level >= required_level:
                    func(self, *args)
                else:
                    raise Exception('Insufficient level to access this resource') 
            return f
        return wrap


class FoobarController(RequestHandler):
    @RequestHandler.requiredLevel(100)
    def get(self, someparameters):
        #do stuff here...

    @RequestHandler.requiredLevel(200)
    def post(self):
        #do something else here...

或者,您可以使用@staticmethod

class RequestHandler(webapp.RequestHandler):

    # The decorator is now a static method.
    @staticmethod     # No default argument required...
    def requiredLevel(required_level):

原始代码无法正常工作的原因是假设requiredLevel是一个实例方法,这在类声明时(当您装饰其他方法时)不可用,也不会从类对象中提供(在您的RequestHandler基类上放置装饰器是一个很好的主意,并且生成的装饰器调用非常易于自我记录)。
您可能会对阅读@classmethod@staticmethod的文档感兴趣。
此外,我喜欢在我的装饰器中加入一些样板文件:
    @staticmethod
    def requiredLevel(required_level):
        def wrap(func):
            def f(self, *args):
                if self.current_user.level >= required_level:
                    func(self, *args)
                else:
                    raise Exception('Insufficient level to access this resource') 
            # This will maintain the function name and documentation of the wrapped function.
            # Very helpful when debugging or checking the docs from the python shell:
            wrap.__doc__ = f.__doc__
            wrap.__name__ = f.__name__
            return f
        return wrap

1

在浏览 StackOverflow 并仔细阅读 Bruce Eckel 的装饰器指南 后,我认为我找到了一个可能的解决方案。

它涉及将装饰器实现为父类中的一个类:

class RequestHandler(webapp.RequestHandler):

    # Decorator class :
    class requiredLevel(object):
        def __init__(self, required_level):
            self.required_level = required_level

        def __call__(self, f):
            def wrapped_f(*f_args):
                if f_args[0].current_user.level >= self.required_level:
                    return f(*f_args)
                else:
                    raise Exception('User has insufficient level to access this resource') 
            return wrapped_f

这样做就可以了!对我来说,使用 f_args[0] 有点不太卫生,如果我发现更好的方法,我会编辑这个答案。

然后,您可以按以下方式装饰子类中的方法:

FooController(RequestHandler):
    @RequestHandler.requiredLevel(100)
    def get(self, id):
        # Do something here

    @RequestHandler.requiredLevel(250)
    def post(self)
        # Do some stuff here

BarController(RequestHandler):
    @RequestHandler.requiredLevel(500)
    def get(self, id):
        # Do something here

欢迎随意评论或提出改进意见。


你可以使用wrapped_f(request_handler, *args, **kwargs)作为函数签名。而且,没有必要把装饰器类放进你的RequestHandler类中,我会把它放在模块级别上。 - nils
谢谢您的评论!我认为您让我对继承的工作方式有了不同(而且更好)的理解。我会相应地更新我的代码。 - jbmusso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接