2010-07-31 64 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