Декораторы Python и наследование классов

Я пытаюсь использовать декораторы, чтобы управлять тем, как пользователи могут или не могут получить доступ к ресурсам в веб-приложении (работает в Google App Engine). Обратите внимание, что я не разрешаю пользователям входить в систему со своими учетными записями Google, поэтому настройка определенных прав доступа к определенным маршрутам в app.yaml не является вариантом.

Я использовал следующие ресурсы:
– Руководство Брюса Эккеля к декораторам
– SO: get-class-in-python-decorator2
– SO: python-decorators-and-inheritance
– SO: get-class-in-python-decorator

Однако я все еще немного смущен …

Вот мой код! В следующем примере current_user – это метод @property, который принадлежит классу RequestHandler. Он возвращает объект 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... 

Я думаю, что я просто достиг предела моих знаний об декораторах и наследовании классов :). Есть предположения ?

Ваш исходный код с двумя небольшими настройками также должен работать. Классный подход кажется довольно тяжелым для такого простого декоратора:

 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 

После копания в StackOverflow и, внимательно прочитав руководство Bruce Eckel к декораторам , я думаю, что нашел возможное решение.

Это включает в себя реализацию декоратора как класса класса 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 

Не стесняйтесь комментировать или предлагать улучшения.