Probably the Best Practice of Object-Oriented Python — Attr | by Christopher Tao | Towards Data Science

It is known that Python is very flexible that can be written in functional, procedural and object-oriented. Honestly, I rarely write classes in Python unless I have to, just because most of the time I don’t want to over-engineered the problems. For example, it probably does not make too much sense to use object-oriented programming when we use Python to perform some ad-hoc data analysis.
However, things got changed when I knew the library called “Attrs”. It makes Python be programmed in object-oriented mode even easier (I think Python’s object-oriented programming is already very concise and easy). In this article, I will introduce this library regarding how it can facilitate Pythonic Object-Oriented Programming.
www.attrs.org
Like most of the Python libraries, we can simply install attrs using pip.
pip install attrs
Now, let’s code a class in Python without any libraries.
class Person(object):    def __init__(self, name, age):        self.name = name        self.age = age        p1 = Person('Chris', 32)p2 = Person('Chris', 32)
Please note that I have also instantiated two instances with the same values the attributes.
When we output the instance, we can only get the name of the class. Also, the two instances with the same values of their attributes will not be considered equal because they are not pointing to the same memory address.
Then, what attrs can do for us? Let’s do the same thing here.
from attr import attrs, attrib@attrsclass Person(object):    name = attrib()    age = attrib()    p1 = Person('Chris', 32)p2 = Person('Chris', 32)
Something needs to have a simple explanation I guess. Firstly, the @attrs is the annotation that tells this class is going to be declared with the attrs library. Then, for each attribute, we only need to define them as attrib() and it is not necessary to have a __init__() method. After that, we can simply instantiate the class just like there was an __init__ method.
Let’s also try output and equal testing.
Obviously, we have a very meaningful output of the object, as well as we can test the equality between objects.
You may have a question now. What if there is such an attribute that we don’t want to initialise it during instantiation but probably assign value to it later? Of course, if you still use attrib() as follows, it will not work.
With attrs, you can achieve this by passing the init argument with False.
@attrsclass Person(object):    name = attrib()    age = attrib()    skills = attrib(init=False)    p1 = Person('Chris', 32)
As shown, we don’t have to give the value of skills initially, but we can assign it later on.
You may also ask how about I want to give default values to the attributes? Yes, with attrs you can do that easily.
@attrsclass Person(object):    name = attrib(default='Chris')    age = attrib(default=32)    p1 = Person()p2 = Person('Chris', 32)
As shown, we give both the attributes default values. Then, the instance without any arguments passed in is exactly the same as the other one that is explicitly initialised with arguments.
What if we want to have an attribute with an empty collection as a default value? Usually, we don’t want to pass an [] in as an argument, this is one of the famous traps of Python that may cause a lot of unexpected problems. Don’t worry, attrs provides a “factory” for us.
@attrsclass Person(object):    name = attrib(default='Chris')    age = attrib(default=32)    skills = attrib(factory=list)
As shown, the attribute skills was empty initially, but later on, we can append values to it.
Now, we want to add some validation to the attributes to make sure that the values we passed in are valid. It is also very easy to implemented using attrs.
@attrsclass 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))}')
In the above example, we have declared a “Student” class with the attribute “student_id”. Suppose that we want to validate the student id that it must be 6 characters long.
Note that we need to define a function to do this. The function needs to be annotated as @<attribute_name>.validator. Therefore, in our case, this should be @student_id.validator. Then, we can raise an exception in this validating function as above shown.
Let’s have a simple test.
The first instance s1 doesn’t have any problem because its student_id has a length of 6, but the second instance s2 won’t pass because the length is 5, and the exception with a pre-defined error message is correctly displayed.
In plain Python, we have to use the super() function in the __init__() function to achieve inheritance. This will be super complex when we want a multi-inheritance. attrs makes it extremely easy.
@attrsclass Person(object):    name = attrib()    age = attrib()        def get_name(self):        return self.name    @attrsclass User(object):    user_id = attrib()        def get_user_id(self):        return self.user_id    @attrsclass Student(Person, User):    student_id = attrib()        def get_student_id(self):        return self.student_idstudent = Student(name='Chris', age=32, user_id='ctao', student_id=123456)
In the above example, Student inherits from both Person and User. In the Student class, we only need to define its specific attribute student_id, and the other attributes from both Person and User will be automatically inherited directly without any verbose definitions.
As shown above, the functions are inherited without problems too.
The attrs library can also help us to easily serialise the instances to dictionaries. After that, you may use that in JSON and do whatever you want to do.
attr.asdict(student)
In this article, I have introduced the library attrs and some of the basic usage. I like this library because it makes Python even simpler and more concise. It is highly recommended to try it out if you haven’t used it before. Hope you will like it, too.
medium.com
If you feel my articles are helpful, please consider joining Medium Membership to support me and thousands of other writers! (Click the link above)