An exciting new feature in Python 3.7 is data classes. A data class is usually a class that primarily contains data, although there are virtually no restrictions. It is created using the new @dataclass decorator, as follows.
from dataclasses import dataclass @dataclass class DataClassCard: rank: str suit: str
This code and all the other examples in this tutorial are only for Python 3.7 and later.
Attention:
Of course, you can use this feature in Python 3.6, but you need to install the dataclasses library, which you can easily do with the pip install dataclasses command, available on Github:dataclass (dataclasses have been included as a standard in Python 3.7)
The dataclass class comes with basic functionality already implemented. For example, you can instantiate, print and compare dataclass instances directly.
>>> queen_of_hearts = DataClassCard('Q', 'Hearts') >>> queen_of_hearts.rank 'Q' >>> queen_of_hearts DataClassCard(rank='Q', suit='Hearts') >>> queen_of_hearts == DataClassCard('Q', 'Hearts') True
Compare dataclass with other common classes. The most basic common class looks like this:
class RegularCard: def __init__(self, rank, suit): = rank = suit
There's not much code to write, but you should already see the bad points: both rank and suit are repeated three times in order to initialize an object. Also, if you try to use this common class, you'll notice that the representation of the objects is not very descriptive, and for some reason queen_of_hearts and DataClassCard('Q', 'Hearts') will not be equal, as follows:
>>> queen_of_hearts = RegularCard('Q', 'Hearts') >>> queen_of_hearts.rank 'Q' >>> queen_of_hearts <__main__.RegularCard object at 0x7fb6eee35d30> >>> queen_of_hearts == RegularCard('Q', 'Hearts') False
It seems that the dataclass class is doing something for us behind the scenes. By default, dataclass implements a __repr__() method, which is used to provide a better representation of strings, and also implements the __eq__() method, which enables comparisons between basic objects. If you want the RegularCard class to emulate the dataclass class above, you need to add the following methods:
class RegularCard: def __init__(self, rank, suit): = rank = suit def __repr__(self): return (f'{self.__class__.__name__}' f'(rank={!r}, suit={!r})') def __eq__(self, other): if other.__class__ is not self.__class__: return NotImplemented return (, ) == (, )
In this tutorial you will be able to understand exactly what conveniences the dataclass class offers. In addition to good representations and object comparisons, you will also see:
dataclass
dataclass
dataclass
Next, we'll dive into these features of the dataclass class. Perhaps, you may think you've seen something similar before.
1. First, the dataclass alternative
For simple data structures, you might use a tuple or dict. You can represent a Q of hearts playing card in either of the following ways:
>>> queen_of_hearts_tuple = ('Q', 'Hearts') >>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
There is nothing wrong with writing it this way. However, as a programmer, you also need to be careful:
You need to you memorize the queen of hearts, the king of hearts... Wait, all the variables represent playing cards.
For the version above that uses tuples, you need to remember the order of the elements. For example, if you write ('spade', 'ace'), the order is messed up, but the program may not give you an easy-to-understand error message
If you use the dict approach, you must make sure that the names of the attributes are consistent. For example, if you write {'value': 'A', 'suit': 'Spades'}, again, it won't work as intended.
Also, using these structures is not the best:
>>> queen_of_hearts_tuple[0] # Cannot be accessed by name 'Q' >>> queen_of_hearts_dict['suit'] # In that case it would be better to use `.suit`. 'Hearts'
So here's a better alternative: use namedtuple.
It has long been used to create small readable data structures (to build objects with a few attributes but no methods). We can recreate the dataclass class example above using namedtuple:
from collections import namedtuple NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
This definition of NamedTupleCard will have exactly the same output as our previous DataClassCard example.
>>> queen_of_hearts = NamedTupleCard('Q', 'Hearts') >>> queen_of_hearts.rank 'Q' >>> queen_of_hearts NamedTupleCard(rank='Q', suit='Hearts') >>> queen_of_hearts == NamedTupleCard('Q', 'Hearts') True
So why use the dataclass class at all?
First of all, the dataclass class has a lot more features than we've seen so far. At the same time, namedtuple has other features that are not necessarily needed.
By design, namedtuple is an ordinary tuple. This can be seen in the following code comparison:
>>> queen_of_hearts == ('Q', 'Hearts') True
While this may seem like a good thing, a lack of awareness of its own type can lead to subtle and hard-to-find bugs, especially since it also allows for friendly comparisons between two different namedtuple classes, as follows:
>>> Person = namedtuple('Person', ['first_initial', 'last_name'] >>> ace_of_spades = NamedTupleCard('A', 'Spades') >>> ace_of_spades == Person('A', 'Spades') True
namedtuple also has some limitations. For example, it is difficult to add default values to some of the fields in a namedtuple. A namedtuple is also inherently immutable. That is, the value of a namedtuple never changes. This is a great feature in some applications, but in other settings it would be nice to have more flexibility.
>>> card = NamedTupleCard('7', 'Diamonds') >>> = '9' AttributeError: can't set attribute
dataclass will not replace all uses of namedtuple. For example, if you need your data structures to behave like tuples, namedtuple is a good choice!
An alternative to dataclass (and one of the inspirations for dataclass) is the attrs library. After installing attrs (which you can do with the pip install attrs command), you can write the Card class as follows:
import attr @ class AttrsCard: rank = () suit = ()
It is possible to use exactly the same methods as the previous DataClassCard and NamedTupleCard examples. attrs is great and supports some features that DataClass doesn't, such as converters and validators. In addition, attrs has been around for a while and is supported in Python 2.7 and Python 3.4 and above. However, since attrs is not in the standard library, it does require an external dependency to be added to the project. With dataclass, similar functionality can be used anywhere.
In addition to tuples, dicts, namedtuples, and attrs, there are a number of other similar items, including namedlist, attrdict, plumber, and fields. While dataclass is a good new choice, there are still older versions that are suitable for better use cases. For example, if you need compatibility with a specific API that expects tuples, or if you encounter a need for functionality not supported in dataclass.
2. dataclass basic elements
Let's continue back to the dataclass. For example, we'll create a Position class that will represent a geographic location using a name as well as latitude and longitude.
from dataclasses import dataclass @dataclass class Position: name: str lon: float lat: float
The @dataclass decorator above defines the Position class as a dataclass. Below the Position: line, simply list the required fields of the dataclass class. The :representation for fields uses a new feature in Python 3.6 called variable annotations. We'll discuss more about this representation soon, and why you should specify data types like str and float.
It only takes a few lines of code. The newly created class is ready to use:
>>> pos = Position('Oslo', 10.8, 59.9) >>> print(pos) Position(name='Oslo', lon=10.8, lat=59.9) >>> 59.9 >>> print(f'{} is at {}°N, {}°E') Oslo is at 59.9°N, 10.8°E
You can also create dataclass classes in a similar way to how you create named tuples. The following way is (almost) equivalent to the definition of the position above:
from dataclasses import make_dataclass Position = make_dataclass('Position', ['name', 'lat', 'lon'])
The dataclass class is an ordinary Python class. The only thing that makes it different is that it has some as well as implementations of basic data model methods such as __init__() , __repr__() , and __eq__() .
2.1 Adding default values
Adding default values to fields of the dataclass class is easy:
from dataclasses import dataclass @dataclass class Position: name: str lon: float = 0.0 lat: float = 0.0
This is exactly the same as specifying default values in the definition of the __init__() method of a normal class:
>>> Position('Null Island') Position(name='Null Island', lon=0.0, lat=0.0) >>> Position('Greenwich', lat=51.8) Position(name='Greenwich', lon=0.0, lat=51.8) >>> Position('Vancouver', -123.1, 49.3) Position(name='Vancouver', lon=-123.1, lat=49.3)
Next, learn about thedefault_factory
, which is a way to provide more complex defaults.
2.2 Type Hints
So far, we haven't made a big deal about the fact that the dataclass class supports out-of-the-box usage. You may have noticed that we use type hints to define fields, name: str : indicating that name should be a text string (of type str).
In fact, some type of hint must be added when defining a field in the dataclass class. Without a type hint, the field will not be part of the dataclass class. However, if you do not want to add an explicit type to the dataclass class, you can use :
from dataclasses import dataclass from typing import Any @dataclass class WithoutExplicitTypes: name: Any value: Any = 42
While it is necessary to add type hints in some form when using the dataclass class, these types are not mandatory at runtime. The following code runs without any problems:
>>> Position(3.14, 'pi day', 2018) Position(name=3.14, lon='pi day', lat=2018)
This is how Python for typing usually works: Python is and always will be a dynamically typed language. To actually catch type errors, run a type checker like Mypy in your code.
2.3 Method of addition
As mentioned earlier, the dataclass class is also just a regular class. This means that you are free to add your own methods to the dataclass class. For example, let's calculate the distance along the surface of the Earth between one location and another. One way to do this is to use the hasrsine formula:
You can add the distance_to() method to a data class just as you would with a regular class:
from dataclasses import dataclass from math import asin, cos, radians, sin, sqrt @dataclass class Position: name: str lon: float = 0.0 lat: float = 0.0 def distance_to(self, other): r = 6371 # Earth radius in kilometers lam_1, lam_2 = radians(), radians() phi_1, phi_2 = radians(), radians() h = (sin((phi_2 - phi_1) / 2)**2 + cos(phi_1) * cos(phi_2) * sin((lam_2 - lam_1) / 2)**2) return 2 * r * asin(sqrt(h))
As you would expect:
>>> oslo = Position('Oslo', 10.8, 59.9) >>> vancouver = Position('Vancouver', -123.1, 49.3) >>> oslo.distance_to(vancouver) 7181.7841229421165
3. More flexible dataclasses
So far, you've seen some of the basic features of the dataclass class: it provides some convenience methods, the ability to add default values, and other methods. Now, you'll learn about some more advanced features, such as the @dataclass decorator argument and the field() method. Together, they give you more control when creating dataclass classes.
Let's go back to the playingcard example you saw at the beginning of this tutorial and add a class containing a deck of cards:
from dataclasses import dataclass from typing import List @dataclass class PlayingCard: rank: str suit: str @dataclass class Deck: cards: List[PlayingCard]
It is possible to create a simple deck which contains only two cards, as shown below:
>>> queen_of_hearts = PlayingCard('Q', 'Hearts') >>> ace_of_spades = PlayingCard('A', 'Spades') >>> two_cards = Deck([queen_of_hearts, ace_of_spades]) Deck(cards=[PlayingCard(rank='Q', suit='Hearts'), PlayingCard(rank='A', suit='Spades')])
3.1 Advanced Usage of Default Values
Suppose you want to give default values to the deck of cards. For example, Deck() is handy for creating a normal deck of 52 playing cards. First, specify different numbers (ranks) and suits (suits). Then, add a method, make french deck(), which creates a list of instances of PlayingCard:
RANKS = '2 3 4 5 6 7 8 9 10 J Q K A'.split() SUITS = '♣ ♢ ♡ ♠'.split() def make_french_deck(): return [PlayingCard(r, s) for s in SUITS for r in RANKS]
For visualization purposes, four different colors are specified using Unicode notation.
Note: Above, we used Unicode glyphs like ♠ directly in the source code. We can do this because Python supports writing source code in UTF-8 by default. For information on how to enter these on your system, see: Unicode input . You can also use \N named character escaping (e.g., \N{BLACK SPADE SUIT}) or \u Unicode escaping (e.g., \u2660) to enter Unicode symbols for a fancy color.
In order to simplify card comparisons later, the numbers and suits are also listed in the usual order.
>>> make_french_deck() [PlayingCard(rank='2', suit='♣'), PlayingCard(rank='3', suit='♣'), ... PlayingCard(rank='K', suit='♠'), PlayingCard(rank='A', suit='♠')]
In theory, you can now use this method to specify a default value for the
from dataclasses import dataclass from typing import List @dataclass class Deck: # Will NOT work cards: List[PlayingCard] = make_french_deck()
Don't do this! This introduces one of the most common anti-patterns in Python: the use of variable default arguments .
The problem is that all instances of Deck will use the same list object as the default value for the cards property. This means that if a card is removed from a deck, it will also disappear from all other instances of the card. In fact, the dataclass class will also prevent you from doing this, and the code above will raise a ValueError.
Instead, the dataclass class uses something called default_factory to handle variable default values. To use default_factory (and many other cool features of the dataclass class), you need to use the field() descriptor:
from dataclasses import dataclass, field from typing import List @dataclass class Deck: cards: List[PlayingCard] = field(default_factory=make_french_deck)
The argument to default_factory can be zero for any adjustable parameter. It is now easy to create a full deck of playing cards:
>>> Deck() Deck(cards=[PlayingCard(rank='2', suit='♣'), PlayingCard(rank='3', suit='♣'), ... PlayingCard(rank='K', suit='♠'), PlayingCard(rank='A', suit='♠')])
The field() descriptor is used to customize each field of the dataclass class individually. You will see some other examples later. Here are some of the parameters supported by field() for your reference:
default : the default value of the field
default_factory : This function returns the initial value of a field.
init : Whether to use the field in the __init__() method (defaults to True.)
repr : Whether to use the field in the object's repr (defaults to True.)
compare : Whether to include this field in the comparison (default is True.)
hash : whether the field is included in the calculation of hash() (the default is to use the same value as the comparison)
metadata : a map containing information about the field
In the Position example above, you learned how to add a simple default value by writing lat: float = 0.0. However, if you also want to customize the field, for example by hiding it in repr, you need to use the default parameter: lat: float = field(default = 0.0, repr = False).
You cannot specify both default and default_factory. The parameter metadata is not used by the dataclass class itself, but you (or a third-party package) can attach information to the field. For example, in the Position example, you can specify that latitude and longitude should be expressed in degrees.
from dataclasses import dataclass, field @dataclass class Position: name: str lon: float = field(default=0.0, metadata={'unit': 'degrees'}) lat: float = field(default=0.0, metadata={'unit': 'degrees'})
You can use the fields() function to retrieve metadata (and other information about a field, note that field is plural).
>>> from dataclasses import fields >>> fields(Position) (Field(name='name',type=<class 'str'>,...,metadata={}), Field(name='lon',type=<class 'float'>,...,metadata={'unit': 'degrees'}), Field(name='lat',type=<class 'float'>,...,metadata={'unit': 'degrees'})) >>> lat_unit = fields(Position)[2].metadata['unit'] >>> lat_unit 'degrees'
3.2 Better representation
Recall that we can create a deck of cards using the code below:
>>> Deck() Deck(cards=[PlayingCard(rank='2', suit='♣'), PlayingCard(rank='3', suit='♣'), ... PlayingCard(rank='K', suit='♠'), PlayingCard(rank='A', suit='♠')])
While this representation of Deck is explicit and readable, it is also very verbose. (In the output above, I have removed 48 of the 52 cards. On an 80-column display, printing just the full Deck would take up 22 lines!)
Let's have a more concise representation. Typically, Python objects have two different string representations:
repr(obj) is defined by obj.__repr__() and should return a developer-friendly representation of obj. If possible, this should be code that recreates obj. The dataclass class does this.
str(obj) is defined by obj.__str__() and should return a user-friendly representation of obj. The dataclass class does not implement the __str__() method, so Python will return to the __repr__() method.
Let's implement a user-friendly representation of PlayCard:
from dataclasses import dataclass @dataclass class PlayingCard: rank: str suit: str def __str__(self): return f'{}{}'
It looks much better now, but it's still just as lengthy as before:
>>> ace_of_spades = PlayingCard('A', '♠') >>> ace_of_spades PlayingCard(rank='A', suit='♠') >>> print(ace_of_spades) ♠A >>> print(Deck()) Deck(cards=[PlayingCard(rank='2', suit='♣'), PlayingCard(rank='3', suit='♣'), ... PlayingCard(rank='K', suit='♠'), PlayingCard(rank='A', suit='♠')])
To indicate that you can add your own __repr__() method. Again, we're violating the principle that it should return code that recreates the object. After all, utility trumps simplicity. The following code adds a more concise representation of Deck:
from dataclasses import dataclass, field from typing import List @dataclass class Deck: cards: List[PlayingCard] = field(default_factory=make_french_deck) def __repr__(self): cards = ', '.join(f'{c!s}' for c in ) return f'{self.__class__.__name__}({cards})'
Note the !s descriptor in the {c!s} format string here. This means that we are explicitly using the str() representation of each PlayingCard. With the new __repr__(), the Deck representation is much easier to read.
>>> Deck() Deck(♣2, ♣3, ♣4, ♣5, ♣6, ♣7, ♣8, ♣9, ♣10, ♣J, ♣Q, ♣K, ♣A, ♢2, ♢3, ♢4, ♢5, ♢6, ♢7, ♢8, ♢9, ♢10, ♢J, ♢Q, ♢K, ♢A, ♡2, ♡3, ♡4, ♡5, ♡6, ♡7, ♡8, ♡9, ♡10, ♡J, ♡Q, ♡K, ♡A, ♠2, ♠3, ♠4, ♠5, ♠6, ♠7, ♠8, ♠9, ♠10, ♠J, ♠Q, ♠K, ♠A)
3.3 Comparing Cards
In many card games, cards are compared to each other. For example, in a typical fetch game, the highest card fetches the card. As currently implemented, the PlayingCard class does not support this comparison, as follows:
>>> queen_of_hearts = PlayingCard('Q', '♡') >>> ace_of_spades = PlayingCard('A', '♠') >>> ace_of_spades > queen_of_hearts TypeError: '>' not supported between instances of 'Card' and 'Card'
However, this is (seemingly) easily corrected:
from dataclasses import dataclass @dataclass(order=True) class PlayingCard: rank: str suit: str def __str__(self): return f'{}{}'
The @dataclass decorator comes in two forms. So far, you've seen the simple form of specifying @dataclass without using any parentheses or arguments. However, you can also parameterize the @dataclass() decorator in parentheses, as above. The supported parameters are listed below:
init: if or not to add __init__() method, (default is True)
repr: if or not to add __repr__() method, (default is True)
eq: if or not to add __eq__() method, (default is True)
order: whether to add ordering method, (default is False)
unsafe_hash: whether to force the __hash__() method, (default is False )
frozen: If True, assignment to the field raises an exception. (Default is False )
See PEP for more information on each parameter. After setting order = True, you can compare PlayingCard objects:
>>> queen_of_hearts = PlayingCard('Q', '♡') >>> ace_of_spades = PlayingCard('A', '♠') >>> ace_of_spades > queen_of_hearts False
So how do these two cards compare? There's no indication here of how they should be sorted, and there's a result? For some reason, Python seems to think that Queen should be greater than Ace. It turns out that the dataclass class compares objects as if they were tuples of fields. In other words, Queen is larger than Ace because Q comes after A in the alphabet.
>>> ('A', '♠') > ('Q', '♡') False
This doesn't work for us. Instead, we need to define some sort of sorted index that uses the RANKS and SUITS order. Something like the following:
>>> RANKS = '2 3 4 5 6 7 8 9 10 J Q K A'.split() >>> SUITS = '♣ ♢ ♡ ♠'.split() >>> card = PlayingCard('Q', '♡') >>> () * len(SUITS) + () 42
To allow PlayingCard to use this sort index for comparison, we need to add a sort_index field to the class. However, this field should be automatically calculated from the other fields rank and suit. This is where the special method __post_init__() comes in. It allows special handling after the __init__() method is called:
from dataclasses import dataclass, field RANKS = '2 3 4 5 6 7 8 9 10 J Q K A'.split() SUITS = '♣ ♢ ♡ ♠'.split() @dataclass(order=True) class PlayingCard: sort_index: int = field(init=False, repr=False) rank: str suit: str def __post_init__(self): self.sort_index = (() * len(SUITS) + ()) def __str__(self): return f'{}{}'
Note: sort_index is added as the first field of the class. This allows the comparison to be done using sort_index first, and will only work if there are other fields. With field(), you must also specify that sort_index should not be included as an argument in the __init__() method (since it is calculated from the rank and suit fields). To avoid confusing users with this implementation detail, it might also be a good idea to remove sort_index from the class repr.
>>> queen_of_hearts = PlayingCard('Q', '♡') >>> ace_of_spades = PlayingCard('A', '♠') >>> ace_of_spades > queen_of_hearts True
Now you can easily create a sorted deck of cards:
>>> Deck(sorted(make_french_deck())) Deck(♣2, ♢2, ♡2, ♠2, ♣3, ♢3, ♡3, ♠3, ♣4, ♢4, ♡4, ♠4, ♣5, ♢5, ♡5, ♠5, ♣6, ♢6, ♡6, ♠6, ♣7, ♢7, ♡7, ♠7, ♣8, ♢8, ♡8, ♠8, ♣9, ♢9, ♡9, ♠9, ♣10, ♢10, ♡10, ♠10, ♣J, ♢J, ♡J, ♠J, ♣Q, ♢Q, ♡Q, ♠Q, ♣K, ♢K, ♡K, ♠K, ♣A, ♢A, ♡A, ♠A)
Or, if you don't care about sorting, the following describes how to randomize 10 cards:
>>> from random import sample >>> Deck(sample(make_french_deck(), k=10)) Deck(♢2, ♡A, ♢10, ♣2, ♢3, ♠3, ♢A, ♠8, ♠9, ♠2)
Of course, you don't need to configure order = True here.
4. Immutable dataclass
One of the defining characteristics of a namedtuple, seen earlier, is that it is immutable. That is, the values of its fields may never change. For many types of dataclasses, this is a good idea! To make a dataclass immutable, set frozen = True when you create it. For example, here's an immutable version of the Position class you saw earlier:
from dataclasses import dataclass @dataclass(frozen=True) class Position: name: str lon: float = 0.0 lat: float = 0.0
In a dataclass with frozen=True, fields cannot be assigned values after creation.
>>> pos = Position('Oslo', 10.8, 59.9) >>> 'Oslo' >>> = 'Stockholm' : cannot assign to field 'name'
Note, however, that if your data class contains mutable fields, those fields may still change. This applies to all nested data structures in Python.
from dataclasses import dataclass from typing import List @dataclass(frozen=True) class ImmutableCard: rank: str suit: str @dataclass(frozen=True) class ImmutableDeck: cards: List[PlayingCard]
Although ImmutableCard and ImmutableDeck are immutable, the list containing the cards is not immutable. So you can still change cards.
>>> queen_of_hearts = ImmutableCard('Q', '♡') >>> ace_of_spades = ImmutableCard('A', '♠') >>> deck = ImmutableDeck([queen_of_hearts, ace_of_spades]) >>> deck ImmutableDeck(cards=[ImmutableCard(rank='Q', suit='♡'), ImmutableCard(rank='A', suit='♠')]) >>> [0] = ImmutableCard('7', '♢') >>> deck ImmutableDeck(cards=[ImmutableCard(rank='7', suit='♢'), ImmutableCard(rank='A', suit='♠')])
To avoid this, make sure that all fields of the immutable dataclass class use immutable types (but remember that types are not enforced at runtime). ImmutableDeck should be implemented using tuples rather than lists.
5. Succession
You can subclass the dataclass class very freely. For example, we will inherit the Position example with the country field and use it to record the country name:
from dataclasses import dataclass @dataclass class Position: name: str lon: float lat: float @dataclass class Capital(Position): country: str
In this simple example, everything is fine:
>>> Capital('Oslo', 10.8, 59.9, 'Norway') Capital(name='Oslo', lon=10.8, lat=59.9, country='Norway')
The country field of the Capital class is added after the three original fields (name , lon , lat ) of the Position class. If any of the fields in the base class have default values, things get a little more complicated:
from dataclasses import dataclass @dataclass class Position: name: str lon: float = 0.0 lat: float = 0.0 @dataclass class Capital(Position): country: str # Does NOT work
The code above will immediately crash and report a TypeError: "non-default argument 'country' follows default argument." The problem is: our new field: country has no default value, while the lon and lat fields have default values. The dataclass class will try to write a __init__() method like the following:
def __init__(name: str, lon: float = 0.0, lat: float = 0.0, country: str): ...
However, this is not feasible. If a parameter has a default value, then all subsequent parameters must also have default values. In other words, if a field in the base class has a default value, then all new fields added in the subclass must also have a default value.
Another thing to note is the way fields are sorted in subclasses. Starting with the base class, fields are sorted in the order in which they were first defined. If you redefine the fields in a subclass, the order does not change. For example, if you define Position and Capital as follows:
from dataclasses import dataclass @dataclass class Position: name: str lon: float = 0.0 lat: float = 0.0 @dataclass class Capital(Position): country: str = 'Unknown' lat: float = 40.0
The order of the fields in Capital is still name lon lat country. However, the default value for lat is 40.0.
>>> Capital('Madrid', country='Spain') Capital(name='Madrid', lon=0.0, lat=40.0, country='Spain')
6. Optimizing dataclass
I'm going to end this tutorial with a couple of things about Slot. Slot can be used to create classes faster and use less memory. The dataclass class doesn't have an explicit syntax for handling Slot, but the usual methods for creating Slot apply to dataclass classes as well. (They're really just regular classes!)
from dataclasses import dataclass @dataclass class SimplePosition: name: str lon: float lat: float @dataclass class SlotPosition: __slots__ = ['name', 'lon', 'lat'] name: str lon: float lat: float
Essentially, Slot is defined in the class with __slots__ and lists the variables. For variables or attributes that are not in __slots__, they will not be defined. In addition, the Slot class may not have default values.
The advantage of adding these limits is that certain optimizations can be made. For example, the Slot class takes up less memory, and this can be tested using Pympler:
>>> from pympler import asizeof >>> simple = SimplePosition('London', -0.1, 51.5) >>> slot = SlotPosition('Madrid', -3.7, 40.4) >>> (simple, slot) (440, 248)
Similarly, Slot classes are generally faster to process. In the following example, the speed of property access on the slots data class class and the regular data class class is tested using timeit from the standard library.
>>> from timeit import timeit >>> timeit('', setup="slot=SlotPosition('Oslo', 10.8, 59.9)", globals=globals()) 0.05882283499886398 >>> timeit('', setup="simple=SimplePosition('Oslo', 10.8, 59.9)", globals=globals()) 0.09207444800267695
In this particular example, the Slot class is about 35% faster.
7. Summary and further reading
The data class class is one of the new features of Python 3.7. With the DataClass class, you don't have to write sample code to get proper initialization, representation, and comparison for objects.
You've learned how to define your own data class class, as well:
data class
data class
data class
data class
If you still want to dig into all the details of the data class class, check out PEP 557 and the discussion in the GitHub repo.
summarize
The above is a small introduction to the Python3.7 new features of dataclass decorator, I hope to help you, if you have any questions please leave me a message, I will reply in a timely manner. I would also like to thank you very much for your support of my website!
If you find this article helpful, please feel free to reprint it, and please note the source, thank you!