Python 装饰器与类继承

9 投票
2 回答
6089 浏览
提问于 2025-04-16 02:07

我正在尝试使用装饰器来管理用户在一个网络应用程序中访问资源的方式(这个应用程序运行在Google App Engine上)。请注意,我并没有让用户使用他们的Google账户登录,所以在app.yaml中为特定路径设置特定的访问权限并不是一个选项。

我参考了以下资源:
- Bruce Eckel的装饰器指南
- SO : 在Python装饰器中获取类
- 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...

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

2 个回答

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

欢迎随时评论或提出改进建议。

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

撰写回答