Python

Известно, что Python очень гибкий язык, который может использоваться в функциональном, процедурном и объектно-ориентированном программировании. Честно говоря, я пишу на нем классы только при необходимости, просто потому что не хочу переоценивать проблемы. Например, не имеет большого смысла использовать ООП, когда Python применяется для выполнения специального анализа данных.

Однако все изменилось, когда я узнал про библиотеку “Attrs”. Она упрощает программирование на Python в объектно-ориентированном режиме еще сильнее (думаю, что ООП в Python и так очень лаконичен и легок). В этой статье я расскажу о том, как эта библиотека может облегчить ООП в стиле Python.attrs
Release 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.

pip install attrs

Теперь напишем код класса на 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__.

Давайте также попробуем вывод и тестирование на равенство.

Очевидно, что у нас есть значимый вывод объекта, а также мы можем проверить равенство между объектами.

Атрибуты без инициализации

Фото NordWood Themes на Unsplash

Теперь у вас может возникнуть вопрос. Что делать, если существует такой атрибут, который мы не хотим инициализировать во время создания экземпляра, но, вероятно, присвоим ему значение позже? Конечно, если вы все еще используете 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 изначально был пустым, но позже мы можем добавить к нему значения.

Проверка атрибутов

Фото Markus Winkler на Unsplash

Теперь мы хотим добавить проверку атрибутов, чтобы убедиться, что переданные значения действительны. Это также очень легко реализовать с помощью 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 будут автоматически унаследованы непосредственно без каких-либо подробных определений.

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

Сериализация в словарь

Фото libellule789 на Pixabay

Библиотека attrs также помогает нам легко сериализовать экземпляры в словари, которые затем можно использовать в JSON и делать с ними все, что вы захотите.

attr.asdict(student)

Читайте также:


Перевод статьи Christopher Tao: Probably the Best Practice of Object-Oriented Python — Attr