2010-07-31 172 views
9

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

我用以下资源:
- 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... 

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

+1

为什么在类中的方法?这只会导致事情中断,而且它只会像定义类中的常规函数​​一样工作。除非您使用3.x,否则它可能会正常工作。 – 2010-07-31 16:46:24

+0

装饰器是类上的方法,因为我还没有想过如何将装饰器编码为类1 /接受参数和可以访问当前类本身的方法。这是你的意思吗?主要是自学成才,我无法完全理解布鲁斯艾克尔的指导,装饰和继承。 – jbmusso 2010-07-31 16:57:57

+0

你可以在类的外面复制粘贴该功能,它可以正常工作。这足以回答你的问题吗? – 2010-07-31 17:18:09

回答

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'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 

随意评论或提出的增强。

+0

你可以使用'wrapped_f(request_handler,* args,** kwargs)'作为函数签名。也没有必要把装饰类放到你的RequestHandler类中。我会把它放在模块级别上。 – nils 2010-07-31 18:44:03

+0

感谢您的评论!我想你只是让我明白继承的工作方式不同(而且好得多)。我会相应地更新我的代码。 – jbmusso 2010-07-31 18:53:42