Декораторы Python и наследование классов
Я пытаюсь использовать декораторы для управления тем, как пользователи могут или не могут получить доступ к ресурсам в веб-приложении (работает на Google App Engine). Обратите внимание, что я не разрешаю пользователям входить в свои учетные записи Google, поэтому устанавливаю определенные права доступа к определенным маршрутам в приложении.yaml не является вариантом.
я использовал следующие ресурсы :
- руководство Брюса Экеля по декораторам
- так : get-class-in-python-decorator2
- Итак: python-декораторы-и-наследование
- Итак: get-class-in-python-декоратор
однако я все еще немного смущен...
вот мой код ! В следующем примере current_user-это метод @property, который принадлежит классу RequestHandler. Он возвращает пользователя (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 ответов
ваш исходный код, с двумя небольшими настройками, также должен работать. Классовый подход кажется довольно тяжелым для такого простого декоратора:
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, и тщательно читать руководство Брюса Экеля по декораторам, Я думаю, что нашел возможное решение.
Он включает в себя реализацию декоратора как класса в родительском классе:
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
Не стесняйтесь комментировать или предлагать усовершенствования.