Writing it cleanly

INPUT · Slides

Writing a data-only class short

01 / 07

Those lines that only copy

When you met classes in Chapter 5, you wrote this inside __init__. Remember it?

class Member:    def __init__(self, name, age):        self.name = name        self.age = age

Every one of those lines only copies a value it received straight into an attribute. Five items, five lines; ten items, ten. The same thing over and over with only the name changed — and you go on writing it forever.

02 / 07

Add one line of @dataclass

Take dataclass out of the standard library module dataclasses and write @dataclass on the line directly above the class. This @ form is called a decorator.

You no longer write __init__. Instead you list the belongings with type hints on them. The name: str shape from the last lesson goes straight in.

from dataclasses import dataclass@dataclassclass Member:    name: str    age: intmember = Member("Yui", 15)print(member.name)print(member.age)

Result

Yui
15

03 / 07

You can print it as it is

In Chapter 5, printing an instance without writing a __str__ gave you something like this.

<__main__.Member object at 0x7f3a1c0d2e50>

The address on the end changes every time you run it, so there was nothing to lean on. A class with @dataclass on it gets a readable display of the class name and the belongings for nothing.

from dataclasses import dataclass@dataclassclass Member:    name: str    age: intprint(Member("Yui", 15))

Result

Member(name='Yui', age=15)

04 / 07

Comparing contents with ==

The other thing that comes along for nothing is comparison with ==.

With an ordinary class, two separately made real things are not equal even when the contents match. Put @dataclass on it and all the belongings matching counts as equal. Asking "is this the same member?" no longer means comparing the items yourself, one at a time.

from dataclasses import dataclass@dataclassclass Member:    name: str    age: inta = Member("Yui", 15)b = Member("Yui", 15)print(a == b)

Result

True

05 / 07

Defaults go in too

Add = value after the type hint and that is what you get when nothing is passed. Exactly the argument defaults of Chapter 3.

Items with a default have to sit after the items without one. Put them the other way round and a TypeError tells you off.

from dataclasses import dataclass@dataclassclass Member:    name: str    years: int = 1print(Member("Minato"))print(Member("Yui", 3))

Result

Member(name='Minato', years=1)
Member(name='Yui', years=3)

06 / 07

Methods are as they always were

What @dataclass leaves out is only the part that goes without saying. A method using self goes inside the class exactly as before.

So @dataclass does not replace the class. It is a tool that deletes the lines nobody was ever in doubt about.

from dataclasses import dataclass@dataclassclass Member:    name: str    years: int    def yearly_fee(self) -> int:        if self.years >= 3:            return 4000        return 5000member = Member("Yui", 3)print(member.yearly_fee())

Result

4000

07 / 07

When to reach for it

The rule of thumb is easy. If the class is mainly there to hold data, use @dataclass. If the work is the main event, write __init__ as before.

  • @dataclass … __init__, a readable display and == come along with it
  • the belongings are listed with type hints on them
  • methods are written as usual

Fewer lines to write means the work that only this class does stands out. That is the last tool of the chapter. Next lesson we put all four of them together.