Существует ли пифонический способ пропустить декорирование по методу подкласса?

У меня есть класс, который украшает некоторые методы, используя декоратор из другой библиотеки. В частности, подклассы класса, поддерживающие фляжку, украшают http-методы с помощью httpauth.HTTPBasicAuth().login_required() и делают некоторые разумные значения по умолчанию для службы модели.

В большинстве подклассов я хочу, чтобы декоратор применялся; поэтому я скорее удалю его, чем добавлю его в подклассы.

Моя мысль состоит в том, чтобы иметь частный метод, который выполняет операции и публичный метод, который оформлен. Эффектов от украшения можно избежать, переопределив открытый метод, чтобы вызвать частный, а не украсить это переопределение. Подчеркнутый пример ниже.

Мне любопытно узнать, есть ли лучший способ сделать это. Есть ли ярлык для "отмены декораторов" в python, который дает этот эффект?

Или вы можете рекомендовать лучший подход?

Некоторые другие вопросы имеют подходящие ответы для этого, например. Есть ли способ получить функцию, которую украсил декоратор?. Но мой вопрос заключается в более широком дизайне - меня интересует любой питонический способ запускать операции в декорированных методах без эффекта украшения. Например. мой пример - один такой способ, но могут быть и другие.

def auth_required(fn):
    def new_fn(*args, **kwargs):
        print('Auth required for this resource...')
        fn(*args, **kwargs)
    return new_fn

class Resource:
    name = None

    @auth_required
    def get(self):
        self._get()

    def _get(self):
        print('Getting %s' %self.name)

class Eggs(Resource):
    name = 'Eggs'

class Spam(Resource):
    name = 'Spam'

    def get(self):
        self._get()
        # super(Spam, self)._get()

eggs = Eggs()
spam = Spam()

eggs.get()
# Auth required for this resource...
# Getting Eggs

spam.get()
# Getting Spam

Ответы

Ответ 1

Flask-HTTPAuth использует functools.wraps в декораторе login_required:

def login_required(self, f):
    @wraps(f)
    def decorated(*args, **kwargs):
        ...

Из Python 3.2, так как это вызывает update_wrapper, вы можете получить доступ к исходной функции через __wrapped__:

Чтобы разрешить доступ к исходной функции для интроспекции и других (например, в обход декоратора кеширования, такого как lru_cache()), эта функция автоматически добавляет атрибут __wrapped__ к который ссылается на обернутую функцию.

Если вы пишете свои собственные декораторы, как и в вашем примере, вы также можете использовать @wraps, чтобы получить ту же функциональность (а также сохранить докстоки и т.д.).

См. также Есть ли способ получить функцию, которую украсил декоратор?

Ответ 2

Другим распространенным вариантом является то, что украшенная функция сохранит копию исходной функции, к которой можно получить доступ:

def auth_required(fn):
    def new_fn(*args, **kwargs):
        print('Auth required for this resource...')
        fn(*args, **kwargs)
    new_fn.original_fn = fn
    return new_fn

Теперь, для любой украшенной функции вы можете получить доступ к ее атрибуту original_fn, чтобы получить дескриптор оригинальной, не украшенной функции.

В этом случае вы можете определить некоторый тип диспетчера, который либо выполняет обычные вызовы функций (когда вы довольны поведением декоратора), либо делает вызовы на thing.original_fn, когда вы предпочитаете избегать поведения декоратора.

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

Ответ 3

Мне любопытно узнать, есть ли лучший способ сделать это. Есть ли ярлык для "отмены декораторов" в python, который дает этот эффект?

Используйте библиотеку undecorated. Он копает все декораторы и возвращает только оригинальную функцию. Документы должны быть понятными, в основном вы просто вызываете: undecorated(your_decorated_function)