Предположим, нам нужен декоратор, который мы будем использовать только внутри одного определенного класса. Что-то вроде этого:

Где определить этот декоратор?

Функция внутри класса

Немедленное побуждение состоит в том, чтобы поместить atomic_rating_change в сам класс:

Это действительно сработает, но в этом подходе есть серьезный недостаток: atomic_rating_change становится методом экземпляра классаUser. В этом нет никакого смысла. Более того, он даже не работает как метод: если вы его вызовете, параметр decorated будет использоваться как self.

Обратите внимание, что в теле класса atomic_rating_change по-прежнему является простой функцией. Он становится методом только после фактического создания класса, и в это время декоратор уже успешно применен.

Вот небольшой пример, который может помочь вам понять эту концепцию:

Вы действительно можете видеть, что func - это функция внутри тела класса, но это метод вне класса. Более того, значения A.func и A().func тоже различаются благодаря магии дескрипторов Python (если вы не знакомы с дескрипторами в Python, вам следует проверить ссылку). Однако в Python 3 A.func - это просто простая функция.

Что мы можем сделать, чтобы это работало правильно? Вот несколько идей.

Сделайте это статичным (не делайте этого)

Самое простое, что вы делаете каждый раз, когда хотите сохранить в классе простую функцию, - это просто сделать ее статическим методом.

Вы можете подумать, что это полностью решает все проблемы, но, к сожалению, это не так. staticmethod - это еще один дескриптор, а дескрипторы ведут себя по-разному внутри и вне класса (да, опять же).

Итак, как только вы примените этот декоратор к какому-либо методу, вы получите TypeError: 'staticmethod' object is not callable, потому что вы видите необработанный дескриптор внутри класса.

Объявить снаружи

Самое простое решение, о котором вы, вероятно, уже думаете, - объявить функцию вне класса.

Единственным недостатком является то, что функция больше не привязана к классу.

Объявить в другом классе

Чтобы исправить это, мы можем объявить вспомогательный класс, содержащий все декораторы для User:

UserDecorators также не привязан к исходному классу (сохраните имя), но, поскольку это больше не функция, мы можем поместить его в User!

Объявить во внутреннем классе

Вы также можете сделать Decorators закрытым (переименовав его в _Decorators), если вы не хотите, чтобы другие классы использовали ваш декоратор.

Это довольно надежное решение, которое рекомендуется, но есть один привлекательный метод, о котором я знаю.

Сделайте класс, а не метод

Благодаря волшебному методу Python __call__ вы можете объявить любой декоратор как класс, а не как функцию. Напомним, что наша первоначальная проблема заключается в том, что функции ведут себя по-разному внутри и вне тела класса, но это вообще не относится к классам внутри тела класса (посмотрите на Decorators в предыдущих примерах: работает нормально). Так что преобразование декоратора из первого примера в класс будет работать отлично.

Интересный факт: оба рабочих решения включают изменение внутренней функции на внутренний класс (из-за специфического поведения атрибутов функции, да).

Обратите внимание, что описанная проблема не носит концептуального характера, это просто способ бороться с тем фактом, что функция не может быть простым атрибутом класса в Python.