Cara menggunakan python dataclass enum

One new and exciting feature coming in Python 3.7 is the data class. A data class is a class typically containing mainly data, although there aren’t really any restrictions. It is created using the new

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
8 decorator, as follows:

from dataclasses import dataclass

@dataclass
class DataClassCard:
    rank: str
    suit: str

Note: This code, as well as all other examples in this tutorial, will only work in Python 3.7 and above.

A data class comes with basic functionality already implemented. For instance, you can instantiate, print, and compare data class instances straight out of the box:

>>>

>>> 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 that to a regular class. A minimal regular class would look something like this:

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

While this is not much more code to write, you can already see signs of the boilerplate pain:

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
9 and
>>> queen_of_hearts_tuple[0]  # No named access
'Q'
>>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
'Hearts'
0 are both repeated three times simply to initialize an object. Furthermore, if you try to use this plain class, you’ll notice that the representation of the objects is not very descriptive, and for some reason a queen of hearts is not the same as a queen of hearts:

>>>

>>> 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

Seems like data classes are helping us out behind the scenes. By default, data classes implement a

>>> queen_of_hearts_tuple[0]  # No named access
'Q'
>>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
'Hearts'
1 method to provide a nice string representation and an
>>> queen_of_hearts_tuple[0]  # No named access
'Q'
>>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
'Hearts'
2 method that can do basic object comparisons. For the
>>> queen_of_hearts_tuple[0]  # No named access
'Q'
>>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
'Hearts'
3 class to imitate the data class above, you need to add these methods as well:

class RegularCard
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return (f'{self.__class__.__name__}'
                f'(rank={self.rank!r}, suit={self.suit!r})')

    def __eq__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.rank, self.suit) == (other.rank, other.suit)

In this tutorial, you will learn exactly which conveniences data classes provide. In addition to nice representations and comparisons, you’ll see:

  • How to add default values to data class fields
  • How data classes allow for ordering of objects
  • How to represent immutable data
  • How data classes handle inheritance

We will soon dive deeper into those features of data classes. However, you might be thinking that you have already seen something like this before.

Free Download: Get a sample chapter from Python Tricks: The Book that shows you Python’s best practices with simple examples you can apply instantly to write more beautiful + Pythonic code.

Alternatives to Data Classes

For simple data structures, you have probably already used a

>>> queen_of_hearts_tuple[0]  # No named access
'Q'
>>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
'Hearts'
4 or a
>>> queen_of_hearts_tuple[0]  # No named access
'Q'
>>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
'Hearts'
5. You could represent the queen of hearts card in either of the following ways:

>>>

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}

It works. However, it puts a lot of responsibility on you as a programmer:

  • You need to remember that the
    >>> queen_of_hearts_tuple[0]  # No named access
    'Q'
    >>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
    'Hearts'
    
    6 variable represents a card.
  • For the
    >>> queen_of_hearts_tuple[0]  # No named access
    'Q'
    >>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
    'Hearts'
    
    4 version, you need to remember the order of the attributes. Writing
    >>> queen_of_hearts_tuple[0]  # No named access
    'Q'
    >>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
    'Hearts'
    
    8 will mess up your program but probably not give you an easily understandable error message.
  • If you use the
    >>> queen_of_hearts_tuple[0]  # No named access
    'Q'
    >>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
    'Hearts'
    
    5 version, you must make sure the names of the attributes are consistent. For instance
    from collections import namedtuple
    
    NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
    
    0 will not work as expected.

Furthermore, using these structures is not ideal:

>>>

>>> queen_of_hearts_tuple[0]  # No named access
'Q'
>>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
'Hearts'

A better alternative is the

from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
1. It has long been used to create readable small data structures. We can in fact recreate the data class example above using a
from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
1 like this:

from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])

This definition of

from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
3 will give the exact same output as our
from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
4 example did:

>>>

>>> 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 even bother with data classes? First of all, data classes come with many more features than you have seen so far. At the same time, the

from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
1 has some other features that are not necessarily desirable. By design, a
from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
1 is a regular tuple. This can be seen in comparisons, for instance:

>>>

>>> queen_of_hearts == ('Q', 'Hearts')
True

While this might seem like a good thing, this lack of awareness about its own type can lead to subtle and hard-to-find bugs, especially since it will also happily compare two different

from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
1 classes:

>>>

>>> 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
0

The

from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
1 also comes with some restrictions. For instance, it is hard to add default values to some of the fields in a
from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
1. A
from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
1 is also by nature immutable. That is, the value of a
from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
1 can never change. In some applications, this is an awesome feature, but in other settings, it would be nice to have more flexibility:

>>>

>>> 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
1

Data classes will not replace all uses of

from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
1. For instance, if you need your data structure to behave like a tuple, then a named tuple is a great alternative!

Another alternative, and one of the inspirations for data classes, is the

>>> 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
3 project. With
>>> 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
3 installed (
>>> 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
5), you can write a card class as follows:

>>> 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
2

This can be used in exactly the same way as the

from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
4 and
from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
3 examples earlier. The
>>> 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
3 project is great and does support some features that data classes do not, including converters and validators. Furthermore,
>>> 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
3 has been around for a while and is supported in Python 2.7 as well as Python 3.4 and up. However, as
>>> 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
3 is not a part of the standard library, it does add an external dependency to your projects. Through data classes, similar functionality will be available everywhere.

In addition to

>>> queen_of_hearts_tuple[0]  # No named access
'Q'
>>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
'Hearts'
4,
>>> queen_of_hearts_tuple[0]  # No named access
'Q'
>>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
'Hearts'
5,
from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
1, and
>>> 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
3, there are , including ,
>>> queen_of_hearts == ('Q', 'Hearts')
True
6,
>>> queen_of_hearts == ('Q', 'Hearts')
True
7,
>>> queen_of_hearts == ('Q', 'Hearts')
True
8, and
>>> queen_of_hearts == ('Q', 'Hearts')
True
9. While data classes are a great new alternative, there are still use cases where one of the older variants fits better. For instance, if you need compatibility with a specific API expecting tuples or need functionality not supported in data classes.

Remove ads

Basic Data Classes

Let us get back to data classes. As an example, we will create a

>>> 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
00 class that will represent geographic positions with a name as well as the latitude and longitude:

>>> 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
3

What makes this a data class is the

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
8 decorator just above the class definition. Beneath the
>>> 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
02 line, you simply list the fields you want in your data class. The
>>> 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
03 notation used for the fields is using a new feature in Python 3.6 called variable annotations. We will talk more about this notation and why we specify data types like
>>> 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
04 and
>>> 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
05.

Those few lines of code are all you need. The new class is ready for use:

>>>

>>> 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
4

You can also create data classes similarly to how named tuples are created. The following is (almost) equivalent to the definition of

>>> 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
00 above:

>>> 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
5

A data class is a regular Python class. The only thing that sets it apart is that it has basic like

>>> 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
07,
>>> queen_of_hearts_tuple[0]  # No named access
'Q'
>>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
'Hearts'
1, and
>>> queen_of_hearts_tuple[0]  # No named access
'Q'
>>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
'Hearts'
2 implemented for you.

Default Values

It is easy to add default values to the fields of your data class:

>>> 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
6

This works exactly as if you had specified the default values in the definition of the

>>> 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
07 method of a regular class:

>>>

>>> 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
7

you will learn about

>>> 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
11, which gives a way to provide more complicated default values.

Type Hints

So far, we have not made a big fuss of the fact that data classes support typing out of the box. You have probably noticed that we defined the fields with a type hint:

>>> 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
12 says that
>>> 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
13 should be a text string (
>>> 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
04 type).

In fact, adding some kind of type hint is mandatory when defining the fields in your data class. Without a type hint, the field will not be a part of the data class. However, if you do not want to add explicit types to your data class, use

>>> 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
15:

>>> 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
8

While you need to add type hints in some form when using data classes, these types are not enforced at runtime. The following code runs without any problems:

>>>

>>> 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
9

This is how typing in Python usually works: . To actually catch type errors, type checkers like Mypy can be run on your source code.

Remove ads

Adding Methods

You already know that a data class is just a regular class. That means that you can freely add your own methods to a data class. As an example, let us calculate the distance between one position and another, along the Earth’s surface. One way to do this is by using the haversine formula:

Cara menggunakan python dataclass enum

You can add a

>>> 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
16 method to your data class just like you can with normal classes:

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
0

It works as you would expect:

>>>

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
1

More Flexible Data Classes

So far, you have seen some of the basic features of the data class: it gives you some convenience methods, and you can still add default values and other methods. Now you will learn about some more advanced features like parameters to the

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
8 decorator and the
>>> 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
18 function. Together, they give you more control when creating a data class.

Let us return to the playing card example you saw at the beginning of the tutorial and add a class containing a deck of cards while we are at it:

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
2

A simple deck containing only two cards can be created like this:

>>>

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
3

Advanced Default Values

Say that you want to give a default value to the

>>> 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
19. It would for example be convenient if
>>> 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
20 created a regular (French) deck of 52 playing cards. First, specify the different ranks and suits. Then, add a function
>>> 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
21 that creates a list of instances of
>>> 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
22:

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
4

For fun, the four different suits are specified using their Unicode symbols.

Note: Above, we used Unicode glyphs like

>>> 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
23 directly in the source code. We could do this because . Refer to this page on Unicode input for how to enter these on your system. You could also enter the Unicode symbols for the suits using
>>> 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
24 named character escapes (like
>>> 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
25) or
>>> 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
26 Unicode escapes (like
>>> 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
27).

To simplify comparisons of cards later, the ranks and suits are also listed in their usual order.

>>>

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
5

In theory, you could now use this function to specify a default value for

>>> 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
28:

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
6

Don’t do this! This introduces one of the most common anti-patterns in Python: . The problem is that all instances of

>>> 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
19 will use the same list object as the default value of the
>>> 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
30 property. This means that if, say, one card is removed from one
>>> 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
19, then it disappears from all other instances of
>>> 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
19 as well. Actually, data classes try to , and the code above will raise a
>>> 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
33.

Instead, data classes use something called a

>>> 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
11 to handle mutable default values. To use
>>> 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
11 (and many other cool features of data classes), you need to use the
>>> 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
18 specifier:

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
7

The argument to

>>> 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
11 can be any zero parameter callable. Now it is easy to create a full deck of playing cards:

>>>

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
8

The

>>> 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
18 specifier is used to customize each field of a data class individually. You will see some other examples later. For reference, these are the parameters
>>> 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
18 supports:

  • >>> 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
    
    40: Default value of the field
  • >>> 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
    
    11: Function that returns the initial value of the field
  • >>> 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
    
    42: Use field in
    >>> 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
    
    07 method? (Default is
    >>> 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
    
    44.)
  • >>> 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
    
    45: Use field in
    >>> 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
    
    45 of the object? (Default is
    >>> 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
    
    44.)
  • >>> 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
    
    48: Include the field in comparisons? (Default is
    >>> 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
    
    44.)
  • >>> 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
    
    50: Include the field when calculating
    >>> 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
    
    51? (Default is to use the same as for
    >>> 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
    
    48.)
  • >>> 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
    
    53: A mapping with information about the field

In the

>>> 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
00 example, you saw how to add simple default values by writing
>>> 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
55. However, if you also want to customize the field, for instance to hide it in the
>>> 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
45, you need to use the
>>> 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
40 parameter:
>>> 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
58. You may not specify both
>>> 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
40 and
>>> 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
11.

The

>>> 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
53 parameter is not used by the data classes themselves but is available for you (or third party packages) to attach information to fields. In the
>>> 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
00 example, you could for instance specify that latitude and longitude should be given in degrees:

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
9

The metadata (and other information about a field) can be retrieved using the

>>> 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
63 function (note the plural s):

>>>

>>> 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
0

Remove ads

You Need Representation?

Recall that we can create decks of cards out of thin air:

>>>

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
8

While this representation of a

>>> 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
19 is explicit and readable, it is also very verbose. I have deleted 48 of the 52 cards in the deck in the output above. On an 80-column display, simply printing the full
>>> 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
19 takes up 22 lines! Let us add a more concise representation. In general, a Python object has two different string representations:

  • >>> 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
    
    66 is defined by
    >>> 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
    
    67 and should return a developer-friendly representation of
    >>> 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
    
    68. If possible, this should be code that can recreate
    >>> 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
    
    68. Data classes do this.

  • >>> 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
    
    70 is defined by
    >>> 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
    
    71 and should return a user-friendly representation of
    >>> 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
    
    68. Data classes do not implement a
    >>> 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
    
    73 method, so Python will fall back to the
    >>> queen_of_hearts_tuple[0]  # No named access
    'Q'
    >>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
    'Hearts'
    
    1 method.

Let us implement a user-friendly representation of a

>>> 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
22:

>>> 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
2

The cards now look much nicer, but the deck is still as verbose as ever:

>>>

>>> 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
3

To show that it is possible to add your own

>>> queen_of_hearts_tuple[0]  # No named access
'Q'
>>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
'Hearts'
1 method as well, we will violate the principle that it should return code that can recreate an object. Practicality beats purity after all. The following code adds a more concise representation of the
>>> 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
19:

>>> 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
4

Note the

>>> 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
78 specifier in the
>>> 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
79 format string. It means that we explicitly want to use the
>>> 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
80 representation of each
>>> 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
22. With the new
>>> queen_of_hearts_tuple[0]  # No named access
'Q'
>>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
'Hearts'
1, the representation of
>>> 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
19 is easier on the eyes:

>>>

>>> 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
5

This is a nicer representation of the deck. However, it comes at a cost. You’re no longer able to recreate the deck by executing its representation. Often, you’d be better off implementing the same representation with

>>> 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
73 instead.

Comparing Cards

In many card games, cards are compared to each other. For instance in a typical trick taking game, the highest card takes the trick. As it is currently implemented, the

>>> 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
22 class does not support this kind of comparison:

>>>

>>> 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
6

This is, however, (seemingly) easy to rectify:

>>> 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
7

The

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
8 decorator has two forms. So far you have seen the simple form where
>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
8 is specified without any parentheses and parameters. However, you can also give parameters to the
>>> 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
88 decorator in parentheses. The following parameters are supported:

  • >>> 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
    
    42: Add
    >>> 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
    
    07 method? (Default is
    >>> 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
    
    44.)
  • >>> 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
    
    45: Add
    >>> queen_of_hearts_tuple[0]  # No named access
    'Q'
    >>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
    'Hearts'
    
    1 method? (Default is
    >>> 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
    
    44.)
  • >>> 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
    
    95: Add
    >>> queen_of_hearts_tuple[0]  # No named access
    'Q'
    >>> queen_of_hearts_dict['suit']  # Would be nicer with .suit
    'Hearts'
    
    2 method? (Default is
    >>> 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
    
    44.)
  • >>> 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
    
    98: Add ordering methods? (Default is
    >>> 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
    
    99.)
  • class RegularCard:
        def __init__(self, rank, suit):
            self.rank = rank
            self.suit = suit
    
    00: Force the addition of a
    class RegularCard:
        def __init__(self, rank, suit):
            self.rank = rank
            self.suit = suit
    
    01 method? (Default is
    >>> 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
    
    99.)
  • class RegularCard:
        def __init__(self, rank, suit):
            self.rank = rank
            self.suit = suit
    
    03: If
    >>> 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
    
    44, assigning to fields raise an exception. (Default is
    >>> 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
    
    99.)

See for more information about each parameter. After setting

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
06, instances of
>>> 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
22 can be compared:

>>>

>>> 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
8

How are the two cards compared though? You have not specified how the ordering should be done, and for some reason Python seems to believe that a Queen is higher than an Ace…

It turns out that data classes compare objects as if they were tuples of their fields. In other words, a Queen is higher than an Ace because

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
08 comes after
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
09 in the alphabet:

>>>

>>> 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
9

That does not really work for us. Instead, we need to define some kind of sort index that uses the order of

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
10 and
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
11. Something like this:

>>>

class RegularCard
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return (f'{self.__class__.__name__}'
                f'(rank={self.rank!r}, suit={self.suit!r})')

    def __eq__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.rank, self.suit) == (other.rank, other.suit)
0

For

>>> 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
22 to use this sort index for comparisons, we need to add a field
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
13 to the class. However, this field should be calculated from the other fields
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
14 and
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
15 automatically. This is exactly what the special method
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
16 is for. It allows for special processing after the regular
>>> 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
07 method is called:

class RegularCard
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return (f'{self.__class__.__name__}'
                f'(rank={self.rank!r}, suit={self.suit!r})')

    def __eq__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.rank, self.suit) == (other.rank, other.suit)
1

Note that

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
13 is added as the first field of the class. That way, the comparison is first done using
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
13 and only if there are ties are the other fields used. Using
>>> 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
18, you must also specify that
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
13 should not be included as a parameter in the
>>> 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
07 method (because it is calculated from the
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
14 and
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
15 fields). To avoid confusing the user about this implementation detail, it is probably also a good idea to remove
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
13 from the
>>> 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
45 of the class.

Finally, aces are high:

>>>

class RegularCard
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return (f'{self.__class__.__name__}'
                f'(rank={self.rank!r}, suit={self.suit!r})')

    def __eq__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.rank, self.suit) == (other.rank, other.suit)
2

You can now easily create a sorted deck:

>>>

class RegularCard
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return (f'{self.__class__.__name__}'
                f'(rank={self.rank!r}, suit={self.suit!r})')

    def __eq__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.rank, self.suit) == (other.rank, other.suit)
3

Or, if you don’t care about sorting, this is how you draw a random hand of 10 cards:

>>>

class RegularCard
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return (f'{self.__class__.__name__}'
                f'(rank={self.rank!r}, suit={self.suit!r})')

    def __eq__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.rank, self.suit) == (other.rank, other.suit)
4

Of course, you don’t need

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
06 for that…

Remove ads

Immutable Data Classes

One of the defining features of the

from collections import namedtuple

NamedTupleCard = namedtuple('NamedTupleCard', ['rank', 'suit'])
1 you saw earlier is that it is immutable. That is, the value of its fields may never change. For many types of data classes, this is a great idea! To make a data class immutable, set
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
29 when you create it. For example, the following is an immutable version of the
>>> 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
00 class :

class RegularCard
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return (f'{self.__class__.__name__}'
                f'(rank={self.rank!r}, suit={self.suit!r})')

    def __eq__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.rank, self.suit) == (other.rank, other.suit)
5

In a frozen data class, you can not assign values to the fields after creation:

>>>

class RegularCard
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return (f'{self.__class__.__name__}'
                f'(rank={self.rank!r}, suit={self.suit!r})')

    def __eq__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.rank, self.suit) == (other.rank, other.suit)
6

Be aware though that if your data class contains mutable fields, those might still change. This is true for all nested data structures in Python (see this video for further info):

class RegularCard
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return (f'{self.__class__.__name__}'
                f'(rank={self.rank!r}, suit={self.suit!r})')

    def __eq__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.rank, self.suit) == (other.rank, other.suit)
7

Even though both

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
31 and
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
32 are immutable, the list holding
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
33 is not. You can therefore still change the cards in the deck:

>>>

class RegularCard
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return (f'{self.__class__.__name__}'
                f'(rank={self.rank!r}, suit={self.suit!r})')

    def __eq__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.rank, self.suit) == (other.rank, other.suit)
8

To avoid this, make sure all fields of an immutable data class use immutable types (but remember that types are not enforced at runtime). The

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
32 should be implemented using a tuple instead of a list.

Inheritance

You can subclass data classes quite freely. As an example, we will extend our

>>> 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
00 example with a
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
36 field and use it to record capitals:

class RegularCard
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __repr__(self):
        return (f'{self.__class__.__name__}'
                f'(rank={self.rank!r}, suit={self.suit!r})')

    def __eq__(self, other):
        if other.__class__ is not self.__class__:
            return NotImplemented
        return (self.rank, self.suit) == (other.rank, other.suit)
9

In this simple example, everything works without a hitch:

>>>

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
0

The

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
36 field of
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
38 is added after the three original fields in
>>> 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
00. Things get a little more complicated if any fields in the base class have default values:

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
1

This code will immediately crash with a

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
40 complaining that “non-default argument ‘country’ follows default argument.” The problem is that our new
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
36 field has no default value, while the
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
42 and
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
43 fields have default values. The data class will try to write an
>>> 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
07 method with the following signature:

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
2

However, this is not valid Python. . In other words, if a field in a base class has a default value, then all new fields added in a subclass must have default values as well.

Another thing to be aware of is how fields are ordered in a subclass. Starting with the base class, fields are ordered in the order in which they are first defined. If a field is redefined in a subclass, its order does not change. For example, if you define

>>> 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
00 and
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
38 as follows:

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
3

Then the order of the fields in

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
38 will still be
>>> 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
13,
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
42,
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
43,
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
36. However, the default value of
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
43 will be
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
53.

>>>

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
4

Remove ads

Optimizing Data Classes

I’m going to end this tutorial with a few words about . Slots can be used to make classes faster and use less memory. Data classes have no explicit syntax for working with slots, but the normal way of creating slots works for data classes as well. (They really are just regular classes!)

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
5

Essentially, slots are defined using

class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
54 to list the variables on a class. Variables or attributes not present in
class RegularCard:
    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
54 may not be defined. Furthermore, a slots class may not have default values.

The benefit of adding such restrictions is that certain optimizations may be done. For instance, slots classes take up less memory, as can be measured using Pympler:

>>>

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
6

Similarly, slots classes are typically faster to work with. The following example measures the speed of attribute access on a slots data class and a regular data class using timeit from the standard library.

>>>

>>> queen_of_hearts_tuple = ('Q', 'Hearts')
>>> queen_of_hearts_dict = {'rank': 'Q', 'suit': 'Hearts'}
7

In this particular example, the slot class is about 35% faster.

Conclusion & Further Reading

Data classes are one of the new features of Python 3.7. With data classes, you do not have to write boilerplate code to get proper initialization, representation, and comparisons for your objects.

You have seen how to define your own data classes, as well as:

  • How to add default values to the fields in your data class
  • How to customize the ordering of data class objects
  • How to work with immutable data classes
  • How inheritance works for data classes

If you want to dive into all the details of data classes, have a look at PEP 557 as well as the discussions in the original GitHub repo.

In addition, Raymond Hettinger’s PyCon 2018 talk Dataclasses: The code generator to end all code generators is well worth watching.

If you do not yet have Python 3.7, there is also a data classes backport for Python 3.6. And now, go forth and write less code!

Mark as Completed

Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Using Data Classes in Python

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Cara menggunakan python dataclass enum

Send Me Python Tricks »

About Geir Arne Hjelle

Cara menggunakan python dataclass enum
Cara menggunakan python dataclass enum

Geir Arne is an avid Pythonista and a member of the Real Python tutorial team.

» More about Geir Arne


Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Cara menggunakan python dataclass enum

Aldren

Cara menggunakan python dataclass enum

Dan

Cara menggunakan python dataclass enum

Joanna

Master Real-World Python Skills With Unlimited Access to Real Python

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

Tweet Share Share Email

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. and get answers to common questions in our support portal.