Краткое руководство по декораторам и метаклассам
Недавно я столкнулся с очень интересной концепцией - метапрограммированием на Python. Я хотел бы поделиться своими выводами по этой теме в этой статье. Я надеюсь, что это поможет вам осознать это, потому что они говорят, что это крепкий орешек.
Что такое метапрограммирование?
Итак, одной строкой: «Мета-программирование - это акт написания кода, который манипулирует кодом».
Чего ждать? Да, вы правильно прочитали. Код, управляющий кодом. Разве это не звучит захватывающе и мощно? Что ж, на самом деле это так.
В контексте Python метапрограммирование можно сформулировать так: «Мета-программирование - это акт построения функций и классов, которые могут управлять кодом, изменяя, упаковывая существующий код или генерируя код».
Мета-программирование на Python может быть достигнуто с помощью:
- Декораторы
- Мета-классы
Давайте познакомимся с ними по очереди.
Декораторы
Декоратор - это способ добавления новых функций к существующей функции без изменения ее исходной структуры.
Например, у нас есть три функции:
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, поэтому я не смог охватить все в этом посте.
Я надеюсь, что эта статья помогла вам познакомиться с концепцией метапрограммирования. Критика всегда приветствуется!