Известно, что Python очень гибкий язык, который может использоваться в функциональном, процедурном и объектно-ориентированном программировании. Честно говоря, я пишу на нем классы только при необходимости, просто потому что не хочу переоценивать проблемы. Например, не имеет большого смысла использовать ООП, когда Python применяется для выполнения специального анализа данных.
Однако все изменилось, когда я узнал про библиотеку “Attrs”. Она упрощает программирование на Python в объектно-ориентированном режиме еще сильнее (думаю, что ООП в Python и так очень лаконичен и легок). В этой статье я расскажу о том, как эта библиотека может облегчить ООП в стиле Python.attrsRelease v19.3.0 (). is the Python package that will bring back the joy of writing classes by relieving you from the…www.attrs.org
Как и большинство библиотек Python, мы можем просто установить attrs
с помощью pip
.
Теперь напишем код класса на Python без каких-либо библиотек.
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person('Chris', 32)
p2 = Person('Chris', 32)
Обратите внимание, что я также создал два экземпляра с точно такими же значениями атрибутов.
При выводе экземпляра мы можем получить только имя класса. Кроме того, два экземпляра с одинаковыми значениями их атрибутов не будут считаться равными, потому что они не указывают на один и тот же адрес памяти.
Что же attrs
может сделать для нас? Давайте выполним то же самое здесь.
from attr import attrs, attrib
@attrs
class Person(object):
name = attrib()
age = attrib()
p1 = Person('Chris', 32)
p2 = Person('Chris', 32)
Предполагаю, что нужно дать простое объяснение. Во-первых, @attrs
— это аннотация, которая сообщает, что этот класс будет объявлен с помощью библиотеки attrs
. Затем каждый атрибут нужно определить как attrib()
, потому что нет необходимости использовать метод __init__()
. После этого мы можем просто создать экземпляр класса точно так же, как если бы существовал метод __init__
.
Давайте также попробуем вывод и тестирование на равенство.
Очевидно, что у нас есть значимый вывод объекта, а также мы можем проверить равенство между объектами.
Теперь у вас может возникнуть вопрос. Что делать, если существует такой атрибут, который мы не хотим инициализировать во время создания экземпляра, но, вероятно, присвоим ему значение позже? Конечно, если вы все еще используете attrib()
, как указано ниже, он не будет работать.
С помощью attrs
можно достичь этого, передав аргумент init
, используя False
.
@attrs
class Person(object):
name = attrib()
age = attrib()
skills = attrib(init=False)
p1 = Person('Chris', 32)
Как было показано выше, изначально нам не нужно присваивать значение skills
, но можем сделать это позже.
Вы также можете спросить, как насчет того, чтобы дать атрибутам значения по умолчанию? Да, с attrs
сделать это легко.
@attrs
class Person(object):
name = attrib(default='Chris')
age = attrib(default=32)
p1 = Person()
p2 = Person('Chris', 32)
Как показано выше, мы даем обоим атрибутам значения по умолчанию. Теперь экземпляр без каких-либо переданных аргументов точно такой же, как и тот, который явно инициализируется аргументами.
Что делать, если мы хотим установить атрибут с пустой коллекцией в качестве значения по умолчанию? Обычно мы не хотим передавать []
в качестве аргумента, это одна из известных ловушек Python, которая может вызвать много неожиданных проблем. Не волнуйтесь, attrs
предоставляет нам “фабричный метод”.
@attrs
class Person(object):
name = attrib(default='Chris')
age = attrib(default=32)
skills = attrib(factory=list)
Как было показано, атрибут skills
изначально был пустым, но позже мы можем добавить к нему значения.
Теперь мы хотим добавить проверку атрибутов, чтобы убедиться, что переданные значения действительны. Это также очень легко реализовать с помощью attrs
.
@attrs
class Student(object):
name = attrib()
age = attrib()
student_id = attrib()
@student_id.validator
def check_student_id(self, attribute, value):
if len(str(value)) != 6:
raise ValueError(f'student_id must be 6 characters! got {len(str(value))}')
В приведенном выше примере мы объявили класс “Student” с атрибутом “student_id”. Предположим, что нужно проверить длину студенческого билета, которая должна быть равна 6 символам.
Обратите внимание, что для этого нам нужно определить функцию. Она должна быть аннотирована как @<attribute_name>.validator
. В нашем случае — @student_id.validator
. Затем мы можем вызвать исключение в этой функции проверки, как показано выше.
Проведем простой тест.
Первый экземпляр s1
не имеет никаких проблем, потому что его student_id
имеет длину 6, но второй экземпляр s2
не пройдет, потому что его длина равна 5, и исключение с заранее определенным сообщением об ошибке отображается правильно.
В простом Python мы должны использовать функцию super()
в функции __init__()
для реализации наследования. Это будет очень сложно, если нужно осуществить мульти-наследование. attrs
делает этот процесс чрезвычайно легким.
@attrs
class Person(object):
name = attrib()
age = attrib()
def get_name(self):
return self.name
@attrs
class User(object):
user_id = attrib()
def get_user_id(self):
return self.user_id
@attrs
class Student(Person, User):
student_id = attrib()
def get_student_id(self):
return self.student_id
student = Student(name='Chris', age=32, user_id='ctao', student_id=123456)
В приведенном выше примере Student
наследует как от Person
, так и от User
. В классе Student
нам нужно только определить его конкретный атрибут student_id
, а другие атрибуты как от Person
, так и от User
будут автоматически унаследованы непосредственно без каких-либо подробных определений.
Как было показано выше, функции также наследуются без проблем.
Библиотека attrs
также помогает нам легко сериализовать экземпляры в словари, которые затем можно использовать в JSON и делать с ними все, что вы захотите.
Перевод статьи Christopher Tao: Probably the Best Practice of Object-Oriented Python — Attr
Комментарии