Python Forum
A look at @dataclass
Thread Rating:
  • 1 Vote(s) - 3 Average
  • 1
  • 2
  • 3
  • 4
  • 5
A look at @dataclass
#1
This will be a look/tutorial at the biggest news with Python 3.7 which was @dataclass.
Can just write a class and test it out.
from dataclasses import dataclass, field
from decimal import Decimal

@dataclass()
class Vehicle:
    vehicle_type: str
    name : str
    price: Decimal
So this @dataclass generate .__init__(), .__repr__(), and .__eq__() for you.
An other thing to look at is that type hint(class annotations) is mandatory when defining the fields in your data.

These are hints and will not affect when say use a wrong type at run time.
To actually catch type errors, type checkers like Mypy can be run on your source code.
Use class:
# Instantiate class
>>> vehicle_1 = Vehicle('Car', 'Bmw', Decimal('200_000.55'))
>>> vehicle_2 = Vehicle('Bus', 'Volvo', Decimal('1000_000.99'))

# Call instance objects
>>> vehicle_1.name
'Bmw'
>>> vehicle_1.price
Decimal('200000.55')
>>> vehicle_2.vehicle_type
'Bus'

>>> # __repr__ in action
>>> vehicle_1
Vehicle(vehicle_type='Car', name='Bmw', price=Decimal('200000.55'))
>>> print(vehicle_2)
Vehicle(vehicle_type='Bus', name='Volvo', price=Decimal('1000000.99'))

>>> # __eq__ in action
>>> vehicle_1 == Vehicle('Car', 'Bmw', Decimal('200_000.55'))
True
>>> vehicle_1 == Vehicle('Bus', 'Volvo', Decimal('1000_000.99'))
False

# Some use of class with f-string
>>> print(f'The bus cost {vehicle_2.price - vehicle_1.price}$ more than the car')
The bus cost 800000.44$ more than the car

>>> # They are type hint,nothing happen if assassin a int to vehicle_type: str
>>> vehicle_1 = Vehicle('Car', 'Bmw', 200_000.55)
>>> vehicle_1.name = 9999999
>>> vehicle_1.name
9999999
So what is going on in background,here my take on it writing the generated code for this @dataclass.
I used Raymond Hettinger Pycon 2018 Talk about @dataclasse as help to write this.
Generated code:
Here we see the missing method like eg __init__.

Adding to decorator and field
from dataclasses import dataclass, field
from decimal import Decimal

@dataclass(order=True, frozen=True)
class Vehicle:
    kind: str = field(compare=False)
    name : str = field(compare=False)
    price: Decimal
Just doing this will generate about 40 more lines of code.
Quote:order: If true (the default is False), __lt__(), __le__(), __gt__(), and __ge__() methods will be generated.

frozen: If true (the default is False), assigning to fields will generate an exception.
This emulates read-only frozen instances. If __setattr__() or __delattr__() is defined in the class,
then TypeError is raised. See the discussion below.

Now is @dataclass orderable(not bye default) immutability and hashability is added.
Also tell that str shall not be in comparison when eg using sorted().
So now it will only sort bye price of vehicle.
Use class:
# Instantiate class
>>> vehicle_1 = Vehicle('Car', 'Bmw', 200_000.55)
>>> vehicle_2 = Vehicle('Bus', 'Volvo', 1000_000.99)
>>> vehicle_3 = Vehicle('Cycle', 'Colnago', 10000)

# Sort bye price
>>> sorted((vehicle_1, vehicle_2, vehicle_3))
[Vehicle(kind='Cycle', name='Colnago', price=10000),
 Vehicle(kind='Car', name='Bmw', price=200000.55),
 Vehicle(kind='Bus', name='Volvo', price=1000000.99)]

# High to low
>>> sorted((vehicle_1, vehicle_2, vehicle_3), reverse=True)
[Vehicle(kind='Bus', name='Volvo', price=1000000.99),
 Vehicle(kind='Car', name='Bmw', price=200000.55),
 Vehicle(kind='Cycle', name='Colnago', price=10000)]

# Instantiate class Duplicate   
>>> vehicle_1 = Vehicle('Car', 'Bmw', 200_000.55)
>>> vehicle_2 = Vehicle('Bus', 'Volvo', 1000_000.99)
>>> vehicle_2 = Vehicle('Bus', 'Volvo', 1000_000.99)
>>> vehicle_3 = Vehicle('Cycle', 'Colnago', 10000)

# Using set() to remove
>>> set((vehicle_1, vehicle_2, vehicle_3))
{Vehicle(kind='Cycle', name='Colnago', price=10000),
 Vehicle(kind='Car', name='Bmw', price=200000.55),
 Vehicle(kind='Bus', name='Volvo', price=1000000.99)}

# Is immutable 
>>> vehicle_1.name = 'Taxi'
Traceback (most recent call last):
  File "<string>", line 428, in runcode
  File "<interactive input>", line 1, in <module>
  File "<string>", line 3, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'name'

Adding methods
This works as normal method in a class,here can use Type hint or not.
from dataclasses import dataclass, field
from decimal import Decimal

@dataclass(order=True)
class Vehicle:
    vehicle_type: str = field(compare=False)
    name : str = field(compare=False)
    price: Decimal

    def __str__(self):
        return f'{self.vehicle_type} {self.name} at cost of {self.price}$'

    @property
    def color_choice(self):
        if self.name == 'Bmw':
            print(f'Color choice is Black | Blue | Red for {self.name} {self.vehicle_type}')
        if self.name == 'Volvo':
            print(f'Color choice is Orange | Yellow | Green for {self.name} {self.vehicle_type}')
        if self.name == 'Colnago':
            print(f'Color choice is Dark Blue | Red/yellows(Mixed in Green)',
                           f'| Stealth for {self.name} {self.vehicle_type}')
Use class:
# Instantiate class
>>> vehicle_1 = Vehicle('Car', 'Bmw', Decimal('200_000.55'))
>>> vehicle_2 = Vehicle('Bus', 'Volvo', Decimal('1000_000.99'))
>>> vehicle_3 = Vehicle('Cycle', 'Colnago', Decimal('10_000'))
>>> 
>>> # New added __str__
>>> print(vehicle_1)
Car Bmw at cost of 200000.55$
>>> # Generated __repr___
>>> vehicle_1
Vehicle(vehicle_type='Car', name='Bmw', price=Decimal('200000.55'))
>>> 
>>> # The new method
>>> vehicle_2.color_choice
Color choice is Orange | Yellow | Green for Volvo Bus
>>> vehicle_3.color_choice
Color choice is Dark Blue | Red/yellows(Mixed in Green) | Stealth for Colnago Cycle

Mixed Stuff
__post_init__ can be used to add stuff.
It run after the auto-generated __init__.
from dataclasses import dataclass, field

@dataclass
class MakeUpper:
    field_a: str
    field_b: str = field(default='')

    def __post_init__(self):
        self.orginal =  self.field_b
        self.field_b = self.field_b.upper()
Use class:
>>> result = MakeUpper(field_a='a', field_b='all lowercase given')
>>> result.field_a
'a'
>>> result.orginal
'all lowercase given'
>>> result.field_b
'ALL LOWERCASE GIVEN'
>>> result.__annotations__
{'field_a': <class 'str'>, 'field_b': <class 'str'>}
Inheritance
@dataclass support inheritance like normal python classes.
from dataclasses import dataclass, field

@dataclass
class Plantes:
    name: str
    size: int = 0

    def __str__(self):
        return f'{self.name} size is {self.size}-km and temprature is {self.celsius}°C'

@dataclass
class Temprature(Plantes):
    celsius: int = 0
Use class:
>>> obj = Temprature('Venus', 6052, 465)
>>>
>>> # __repr__
>>> obj
Temprature(name='Venus', size=6052, celsius=465)
>>> 
>>> # __str__
>>> print(obj)
Venus size is 6052-km and temprature is 465°C
>>>
>>> obj.celsius
465
__post_init__ is can be called in inheritance using super() as a normal __init__
def __post_init__(self):
    super().__post_init__() #Call post init of Parent class
    print("Something")

Thoughts?
I can like the look and use of it,as get a lot of stuff generated for free Cool
This is a open tutorials,so if someone has tested or have a opinions about @dataclass feel free to posts about it here.
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020