Python装饰器和类继承

时间:2010-07-31 16:36:24

标签: python web-applications decorator subclassing

我正在尝试使用装饰器来管理用户可能访问或不访问Web应用程序(在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...

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

2 个答案:

答案 0 :(得分: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 :(得分:1)

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

它涉及将装饰器实现为Parent类中的类:

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

随意发表评论或提出改进建议。