Автор оригинала: Marco Predari.
Python славится своей высокой производительностью, в основном благодаря чистому и лаконичному синтаксису. Тем не менее, некоторые задачи требуют написания шаблонного кода. Например, любой конструктор класса получит необходимые значения для инициализации состояния экземпляра объекта с помощью последовательности self.fieldname
.
Обычно имя формальных параметров конструктора совпадает с полями для инициализации (т. Е. Каждая строка конструктора будет читаться как self.fieldname
).
В этой статье я покажу вам, как избежать написания шаблонного кода для инициализации полей класса с помощью Python метаклассов и декораторов .
Требования
- Версия Python 3.*
дорожная карта
Я создам класс с именем Person с некоторыми атрибутами наиболее распространенным способом. Затем я шаг за шагом представлю код с метаклассами и декораторами с логикой “автоматического инициализации”, также используя магический модуль inspect .
Базовый код
Я создал main.py
и в начале мой код выглядит так:
class Person (): def __init__(self, first_name, last_name, birth_date, sex, address): self.first_name = first_name self.last_name = last_name self.birth_date = birth_date self.sex = sex self.address = address def __repr__(self): return "{} {} {} {} {}"\ .format(self.first_name, self.last_name, self.birth_date, self.sex, self.address) if __name__ == '__main__': john = Person('Jonh', 'Doe', '21/06/1990', 'male', '216 Caledonia Street') print(john)
Модуль автозапуска
Я создам новый файл с именем autoinit.py
, где я буду размещать всю свою логику.
class AutoInit(type): def __new__(meta, classname, supers, classdict): return type.__new__(meta, classname, supers, classdict)
Я создал класс AutoInit , который на данный момент ничего не делает. Это всего лишь простой метакласс. Поэтому я могу сразу же использовать свой метакласс в классе Person , как это:
#main.py from autoinit import AutoInit class Person (metaclass=AutoInit): ... ... ...
Так что это первое изменение в классе person, которое мне пришлось сделать. Мы завершим рефакторинг позже, как только модуль autoinit будет завершен.
Декорируйте метод класса ‘__init__’
Метод __init__
является методом конструктора класса. Чтобы достичь нашей цели, мы должны поместить декоратора вокруг этого метода, чтобы включить нормальное поведение.
#autoinit.py class AutoInit(type): def __new__(meta, classname, supers, classdict): classdict['__init__'] = autoInitDecorator(classdict['__init__']) return type.__new__(meta, classname, supers, classdict) def autoInitDecorator (toDecoreFun): def wrapper(*args): print("Hello from autoinit decorator") toDecoreFun(*args) return wrapper
Модуль “Проверка”
Из модуля inspect
я буду использовать функцию getargspec
. Из документации по Python:
Get the names and default values of a function's arguments. A tuple of four things is returned: (args, varargs, varkw, defaults). 'args' is a list of the argument names (it may contain nested lists). 'varargs' and 'varkw' are the names of the * and ** arguments or None. 'defaults' is an n-tuple of the default values of the last n arguments.
Таким образом, с помощью этой мощной функции мой код будет выглядеть следующим образом:
#autoinit.py class AutoInit(type): def __new__(meta, classname, supers, classdict): classdict['__init__'] = autoInitDecorator(classdict['__init__']) return type.__new__(meta, classname, supers, classdict) def autoInitDecorator (toDecoreFun): def wrapper(*args): # ['self', 'first_name', 'last_name', 'birth_date', 'sex', 'address'] argsnames = getargspec(toDecoreFun)[0] # the values provided when a new instance is created minus the 'self' reference # ['Jonh', 'Doe', '21/06/1990', 'male', '216 Caledonia Street'] argsvalues = [x for x in args[1:]] # 'self' -> the reference to the instance objref = args[0] # setting the attribute with the corrisponding values to the instance # note I am skipping the 'self' reference for x in argsnames[1:]: objref.__setattr__(x,argsvalues.pop(0)) return wrapper
Рефакторинг кода
Так что мой main.py
код будет выглядеть следующим образом:
from autoinit import AutoInit class Person (metaclass=AutoInit): def __init__(self, first_name, last_name, birth_date, sex, address): pass def __repr__(self): return "{} {} {} {} {}"\ .format(self.first_name, self.last_name, self.birth_date, self.sex, self.address) if __name__ == '__main__': john = Person('Jonh', 'Doe', '21/06/1990', 'male', '216 Caledonia Street') print(john)
Вот и все, ребята
Обратите внимание, что этот пример не предназначен для работы также с ключевыми аргументами (возможно, в будущей статье я мог бы расширить это упражнение).
Метаклассы и декораторы-очень мощные инструменты в Python. Я очень быстро делаю удивительные вещи на других языках. Но также легко впасть в кошмары кода, когда исходный код вырастает.