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

Язык Python предоставляет несколько встроенных декораторов функций для выполнения таких действий, как создание статических методов, но программисты также имеют возможность создавать свои собственные декораторы. Несмотря на то что они строго не привязаны к классам, тем не менее пользовательские декораторы функций часто оформляются как классы, в которых сохраняется оригинальная функция наряду с другими данными, такими как информация о состоянии. Кроме того, недавно появилось похожее расширение, доступное в Python 2.6 и 3.0: декораторы классов, непосредственно связанные с моделью классов, и метаклассы, играющие похожую роль.

Основы декораторов функций

Синтаксически декоратор функции – это разновидность объявления функции времени выполнения. Декоратор функции записывается в строке, непосредственно перед строкой с инструкцией def, которая определяет функцию или метод, и состоит из символа @, за которым следует то, что называется мета функцией, – функция (или другой вызываемый объект), которая управляет другой функцией. В настоящее время статические методы, к примеру, могут быть оформлены в виде декораторов, как показано ниже:

class C:
    @staticmethod # Синтаксис декорирования
    def meth():
       ...

Результат, возвращаемый функцией-декоратором, повторно присваивается
имени метода. В результате вызов метода по имени функции фактически будет приводить к вызову результата, полученному от декоратора staticmethod.

Пример:

class Spam:
    numInstances = 0
    def __init__(self):
        Spam.numInstances = Spam.numInstances + 1
    @staticmethod
    def printNumInstances():
      print(“Number of instances created: “, Spam.numInstances)
#Вызов
a = Spam()
b = Spam()
c = Spam()
Spam.printNumInstances() # Теперь вызовы могут производиться как через класс,
# так и через экземпляр!
a.printNumInstances() # В обоих случаях будет выведено
# “Number of instances created: 3”

Staticmethod – это все та же встроенная функция. Она может
использоваться как декоратор просто потому, что принимает другую функцию
в виде аргумента и возвращает вызываемый объект. Фактически любая такая
функция может использоваться в качестве декоратора, даже пользовательские
функции, которые мы пишем сами.

Пример декоратора функции

 

class tracer:
    def __init__(self, func): #Обязательно должна передаваться функция, т.к. декоратор обертывает функцию
       self.calls = 0
       self.func = func
    def __call__(self, *args):
      self.calls += 1
      print(‘call %s to %s’ % (self.calls, self.func.__name__))
      self.func(*args)

@tracer # То же, что и spam = tracer(spam)
def spam(a, b, c): # Обертывает spam в объект-декоратор
    print a, b, c

spam(1, 2, 3) # В действительности вызывается объект-обертка
spam(‘a’, ‘b’, ‘c’) # То есть вызывается метод __call__ в классе
spam(4, 5, 6) # Метод __call__ выполняет дополнительные действия
# и вызывает оригинальную функцию

Декораторы классов и метаклассы

В двух словах, декораторы классов
похожи на декораторы функций, но они запускаются после инструкции class, чтобы повторно присвоить имя класса вызываемому объекту. Кроме того, они могут использоваться для изменения классов сразу после их создания или добавлять дополнительный слой логики уже после создания экземпляров. При применении декоратора к классу программный код вида:

def decorator(aClass): ...
@decorator
class C: ...

отображается в следующий эквивалент:

def decorator(aClass): ...
class C: ...
C = decorator(C)

Декоратор класса может расширить функциональность самого класса или вернуть объект, который будет перехватывать последующие попытки конструирования экземпляров.

def count(aClass):
    aClass.numInstances = 0
    return aClass # Возвращает сам класс, а не обертку
@count
class Spam: ... # То же, что и Spam = count(Spam)
@count
class Sub(Spam): ... # Инструкция numInstances = 0 не нужна здесь
@count
class Other(Spam): ...

Метаклассы

Метаклассы представляют собой похожий инструмент на основе классов, область применения которого отчасти перекрывает область применения декораторов классов. Они предоставляют альтернативную модель управления созданием объектов классов за счет создания подклассов класса type и включения их
в инструкцию class:

class Meta(type):
    def __new__(meta, classname, supers, classdict): ...

class C(metaclass=Meta): ...

Обычно метакласс переопределяет метод __new__ или __init__ класса type, с целью взять на себя управление созданием или инициализацией нового объекта класса. Как и при использовании декораторов классов, суть состоит в том, чтобы определить программный код, который будет вызываться автоматически на этапе создания класса. Оба способа позволяют расширять классы или возвращать произвольные объекты для его замены – протокол с практически неограниченными возможностями.