【问题标题】:Python decorators and class inheritancePython 装饰器和类继承
【发布时间】:2011-03-23 16:17:28
【问题描述】:

我正在尝试使用装饰器来管理用户可能会或可能不会访问网络应用程序(在 Google App Engine 上运行)中的资源的方式。请注意,我不允许用户使用他们的 Google 帐户登录,因此无法在 app.yaml 中设置对特定路由的特定访问权限。

我使用了以下资源:
- Bruce Eckel's guide to decorators
- SO : get-class-in-python-decorator2
- SO : python-decorators-and-inheritance
- SO : get-class-in-python-decorator

但是我还是有点迷茫……

这是我的代码!在以下示例中,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...

我想我刚刚达到了我对装饰器和类继承的知识的极限:)。有什么想法吗?

【问题讨论】:

  • 为什么是类上的方法?这只会导致事情崩溃,它只会像定义它的类中的常规函数​​一样工作。除非你使用的是 3.x,在这种情况下它可能会正常工作。
  • 装饰器是类上的一个方法,因为我还没有想出如何将装饰器编码为一个类 1/ 接受参数和 2/ 可以访问当前类的方法本身。这是你的意思吗?由于主要是自学成才,我无法完全理解 Bruce Eckell 的指南、装饰器和继承。
  • 你可以在课堂外复制粘贴函数,它会正常工作。这足以回答您的问题吗?
  • 将 requiredLevel 装饰器从 FoobarController 移动到 RequestHandler 并用@staticmethod 装饰它似乎是根据stackoverflow.com/questions/3001138/… 的解决方案,但是在我的情况下它并不能解决问题。很可能是因为我的装饰器接受参数?
  • 不,我的意思是把它从课堂上完全删除。使其成为常规功能。

标签: python web-applications decorator subclassing


【解决方案1】:

您的原始代码经过两个小调整,应该也可以工作。对于这样一个简单的装饰器来说,基于类的方法似乎相当重:

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

【讨论】:

    【解决方案2】:

    在挖掘 StackOverflow 并仔细阅读 Bruce Eckel's guide to decorators 之后,我想我找到了一个可能的解决方案。

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

    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 类中。我会把它放在模块级别。
    • 感谢您的评论!我认为您只是让我了解了继承的不同工作方式(并且更好)。我会相应地更新我的代码。
    猜你喜欢
    • 2011-03-01
    • 2014-03-07
    • 2019-12-07
    • 2023-04-09
    • 2014-02-02
    • 2022-01-18
    • 2018-01-16
    • 2018-07-10
    • 2012-11-14
    相关资源
    最近更新 更多