Краткое руководство по декораторам и метаклассам

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

Что такое метапрограммирование?

Итак, одной строкой: «Мета-программирование - это акт написания кода, который манипулирует кодом».

Чего ждать? Да, вы правильно прочитали. Код, управляющий кодом. Разве это не звучит захватывающе и мощно? Что ж, на самом деле это так.

В контексте Python метапрограммирование можно сформулировать так: «Мета-программирование - это акт построения функций и классов, которые могут управлять кодом, изменяя, упаковывая существующий код или генерируя код».

Мета-программирование на Python может быть достигнуто с помощью:

  1. Декораторы
  2. Мета-классы

Давайте познакомимся с ними по очереди.

Декораторы

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

Например, у нас есть три функции:

def add(x, y):
    return x + y    

def sub(x, y):
    return x - y
    
def mul(x, y):
    return x * y

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

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

def add(x, y):
    print("add is called with parameter {0},{1}".format(x,y))
    return x + y    

def sub(x, y):
    print("sub is called with parameter {0},{1}".format(x,y))
    return x - y
    
def mul(x, y):
    print("mul is called with parameter {0},{1}".format(x,y))
    return x * y    

print(add(5,3))
print(sub(5,3))
print(mul(5,3))

*********************** output *********************

add is called with parameter 5, 3
8
sub is called with parameter 5, 3
2
mul is called with parameter 5, 3
15

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

def my_decorator(func):
    def wrapper_function(*args):
        print("{0} is called with parameter {1}".format(func.__name__, args))
        return func(*args)
    return wrapper_function

@my_decorator
def add(x, y):
    return x + y
    
@my_decorator
def sub(x, y):
    return x - y

@my_decorator    
def mul(x, y):
    return x * y 

*********************** output *********************

add is called with parameter (5, 3)
8
sub is called with parameter (5, 3)
2
mul is called with parameter (5, 3)
15

Бинго! В приведенном выше фрагменте кода my_decorator - это функция-декоратор. Мы украшаем все три функции символом @my_decorator, и мы не трогали существующее тело функции, чтобы добавить эту функцию печати.

Итак, в основном декораторы - это функции высшего порядка, которые принимают функцию в качестве аргумента и возвращают другую функцию. Здесь my_decorator принимает функцию в качестве аргумента и в результате возвращает wrapper_function, где wrapper_function добавляет нашу функцию печати к func.

Есть еще кое-что о декораторах, но это краткое введение в декораторы в Python.

Мета-классы

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

Мета-классы - это особые типы классов, а не обычные классы в Python. Если обычный класс определяет поведение своего собственного экземпляра, мета-класс определяет поведение обычного класса и его экземпляра.

Мета-класс может добавлять или вычитать метод или поле из обычного класса. В Python есть один специальный класс type, который по умолчанию является мета-классом. Все классы настраиваемого типа должны наследовать от класса type.

Например, если у нас есть класс Calc с тремя методами класса, и мы хотим предоставить функции отладки для всех методов в одном классе, тогда мы можем использовать для этого мета-класс.

class Calc():
    def add(self, x, y):
        return x + y
    
    def sub(self, x, y):
        return x - y
    
    def mul(self, x, y):
        return x * y

Во-первых, нам нужно создать мета-класс MetaClassDebug с функцией отладки и сделать Calc класс унаследованным от MetaClassDebug.

И когда мы вызываем любой метод из класса Calc, он будет вызван с нашим debug_function.

def debug_function(func):

    def wrapper(*args, **kwargs):
        print("{0} is called with parameter {1}".format(func.__qualname__, args[1:]))
        return func(*args, **kwargs)
    
    return wrapper


def debug_all_methods(cls):

    for key, val in vars(cls).items():
        if callable(val):
            setattr(cls, key, debug_function(val))
    return cls


class MetaClassDebug(type):

    def __new__(cls, clsname, bases, clsdict):
        obj = super().__new__(cls, clsname, bases, clsdict)
        obj = debug_all_methods(obj)
        return obj


class Calc(metaclass=MetaClassDebug):
    def add(self, x, y):
        return x + y

    def sub(self, x, y):
        return x - y

    def mul(self, x, y):
        return x * y


calc = Calc()
print(calc.add(2, 3))
print(calc.sub(2, 3))
print(calc.mul(2, 3))


**************** output ****************

Calc.add is called with parameter (2, 3)
5
Calc.sub is called with parameter (2, 3)
-1
Calc.mul is called with parameter (2, 3)
6

Бинго! В приведенном выше фрагменте мы создали мета-класс MetaClassDebug и написали метод new, который отвечает за создание экземпляра класса и применяет нашу функцию-декоратор debug_function к объекту (экземпляру), который будет создан. для каждого класса, наследующего MetaClassDebug.

Calc унаследован от MetaClassDebug, поэтому каждый метод был украшен debug_function из debug_all_methods.

Таким образом, мы можем добавить новое поведение ко всем методам внутри класса, а также управлять созданием экземпляра класса с помощью метакласса. Мы можем многого добиться с помощью метакласса, например, добавить метод или поле к классу, удалить метод или поле из класса и многое другое.

Я хотел, чтобы вы быстро взглянули на метапрограммирование на Python, поэтому я не смог охватить все в этом посте.

Я надеюсь, что эта статья помогла вам познакомиться с концепцией метапрограммирования. Критика всегда приветствуется!