What are dataclasses and when to use them?

Asked

Viewed 347 times

6

In version 3.7* calls were added dataclasses which were designed from the PEP 557 and consists of using a decorator, dataclass, in "normal classes".

  1. What are these dataclasses?
  2. What are the benefits and harms of using them?
  3. What are the main differences between a dataclass and a normal class, without the decorator?
  4. What justifies its use?

(*) Can be installed in version 3.6 from PIP, in its current version 0.6, being implemented and maintained by the author of PEP himself: https://pypi.org/project/dataclasses.

1 answer

4


An example of the use of dataclass that will be referenced throughout the explanation:

from dataclasses import dataclass

@dataclass
class Item:
    nome: str
    preco_unitario: float
    quantidade: int = 0

    def custo_total(self) -> float:
        return self.preco_unitario * self.quantidade

item = Item('machado', 10.49, 12)
print(repr(item))
# Saida: Item(nome="machado", preco_unitario=10.49, quantidade=12)
print(item.custo_total())
# Saida: 125.88
  1. Dataclasses can be viewed as an extension of the standard python classes. The goal is to reduce the repetition and cost of codes that are common in the community.

    The idea is that many programs use classes for simple data storage, and always have to implement repetitive code "Boilerplate" to work.

  2. Benefits and harm

    Consider the example above. Putting the @dataclass before the class the following automatic operations were made:

    • Creation of a __init__ which receives the parameters and places them in attributes in self.
    • Creation of a __repr__ much more useful for the class, which returns the class name and attributes instead of the memory address
    • Creation of comparison functions __eq__ and __ne__ comparing attributes by equality in order, allowing comparison of class instances with operators = and !=;
    • Optionally create the functions __lt__, __le__, __gt__ and __ge__, comparing attributes in order, allowing the class to be used with comparison operators > >= <= < and with ordination functions .sort() and sorted()
    • Optionally "freezes" class, that is, does not allow changing fields after initialization, to simulate an immutable object.

    The benefits then are obvious because you won’t need to write all this code. It will already be ready and tested. The harm is in case you use dataclass when you do not need all these facilities. Then they will be created, but will only be spending resources because they will not be used for anything.

  3. None. Classes are equal, with the only difference that methods are added to you automatically by the decorator, while you would have to define them manually (or not) in the normal class.

  4. Just look at the class above, defined by dataclass:

    def __init__(self, nome: str, preco_unitario: float, 
            quantidade: int = 0) -> None:
        self.nome = nome
        self.preco_unitario = preco_unitario
        self.quantidade = quantidade
    
    def __repr__(self):
        return f'Item(nome={self.nome!r}, preco_unitario={self.preco_unitario!r}, quantidade={self.quantidade!r})'
    
    def __eq__(self, outra):
        if outra.__class__ is self.__class__:
            return (self.nome, self.preco_unitario, self.quantidade) == (outra.nome, outra.preco_unitario, outra.quantidade)
        return NotImplemented
    
    def __ne__(self, outra):
        if outra.__class__ is self.__class__:
            return (self.nome, self.preco_unitario, self.quantidade) != (outra.nome, outra.preco_unitario, outra.quantidade)
        return NotImplemented
    
    def __lt__(self, outra):
        if outra.__class__ is self.__class__:
            return (self.nome, self.preco_unitario, self.quantidade) < (outra.nome, outra.preco_unitario, outra.quantidade)
        return NotImplemented
    
    def __le__(self, outra):
        if outra.__class__ is self.__class__:
            return (self.nome, self.preco_unitario, self.quantidade) <= (outra.nome, outra.preco_unitario, outra.quantidade)
        return NotImplemented
    
    def __gt__(self, outra):
        if outra.__class__ is self.__class__:
            return (self.nome, self.preco_unitario, self.quantidade) > (outra.nome, outra.preco_unitario, outra.quantidade)
        return NotImplemented
    
    def __ge__(self, outra):
        if outra.__class__ is self.__class__:
            return (self.nome, self.preco_unitario, self.quantidade) >= (outra.nome, outra.preco_unitario, outra.quantidade)
        return NotImplemented
    

    As you can see, it’s a lot of code saved by this decorator, so it’s totally justified to use it if you’re going to define these methods anyway. The less code, the less work and the less chance of errors.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.