Декораторы 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

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