A place to cache linked articles (think custom and personal wayback machine)
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

index.md 76KB

před 4 roky
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. title: The Ultimate Guide to Data Classes in Python 3.7
  2. url: https://realpython.com/python-data-classes/
  3. hash_url: 5f2992bab473b7241612da3c785dcb4a
  4. <p>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 <code>@dataclass</code> decorator, as follows:</p>
  5. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  6. <span class="nd">@dataclass</span>
  7. <span class="k">class</span> <span class="nc">DataClassCard</span><span class="p">:</span>
  8. <span class="n">rank</span><span class="p">:</span> <span class="nb">str</span>
  9. <span class="n">suit</span><span class="p">:</span> <span class="nb">str</span>
  10. </pre></div>
  11. <div class="alert alert-primary" role="alert">
  12. <p><strong>Note:</strong> This code, as well as all other examples in this tutorial, will only work in Python 3.7 and above.</p>
  13. </div>
  14. <p>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:</p>
  15. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span> <span class="o">=</span> <span class="n">DataClassCard</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'Hearts'</span><span class="p">)</span>
  16. <span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span><span class="o">.</span><span class="n">rank</span>
  17. <span class="go">'Q'</span>
  18. <span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span>
  19. <span class="go">DataClassCard(rank='Q', suit='Hearts')</span>
  20. <span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span> <span class="o">==</span> <span class="n">DataClassCard</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'Hearts'</span><span class="p">)</span>
  21. <span class="go">True</span>
  22. </pre></div>
  23. <p>Compare that to a regular class. A minimal regular class would look something like this:</p>
  24. <div class="highlight python"><pre><span/><span class="k">class</span> <span class="nc">RegularCard</span>
  25. <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">rank</span><span class="p">,</span> <span class="n">suit</span><span class="p">):</span>
  26. <span class="bp">self</span><span class="o">.</span><span class="n">rank</span> <span class="o">=</span> <span class="n">rank</span>
  27. <span class="bp">self</span><span class="o">.</span><span class="n">suit</span> <span class="o">=</span> <span class="n">suit</span>
  28. </pre></div>
  29. <p>While this is not much more code to write, you can already see signs of the boilerplate pain: <code>rank</code> and <code>suit</code> 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:</p>
  30. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span> <span class="o">=</span> <span class="n">RegularCard</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'Hearts'</span><span class="p">)</span>
  31. <span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span><span class="o">.</span><span class="n">rank</span>
  32. <span class="go">'Q'</span>
  33. <span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span>
  34. <span class="go">&lt;__main__.RegularCard object at 0x7fb6eee35d30&gt;</span>
  35. <span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span> <span class="o">==</span> <span class="n">RegularCard</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'Hearts'</span><span class="p">)</span>
  36. <span class="go">False</span>
  37. </pre></div>
  38. <p>Seems like data classes are helping us out behind the scenes. By default, data classes implement a <code>.__repr__()</code> method to provide a nice string representation and an <code>.__eq__()</code> method that can do basic object comparisons. For the <code>RegularCard</code> class to imitate the data class above, you need to add these methods as well:</p>
  39. <div class="highlight python"><pre><span/><span class="k">class</span> <span class="nc">RegularCard</span>
  40. <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">rank</span><span class="p">,</span> <span class="n">suit</span><span class="p">):</span>
  41. <span class="bp">self</span><span class="o">.</span><span class="n">rank</span> <span class="o">=</span> <span class="n">rank</span>
  42. <span class="bp">self</span><span class="o">.</span><span class="n">suit</span> <span class="o">=</span> <span class="n">suit</span>
  43. <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
  44. <span class="k">return</span> <span class="p">(</span><span class="n">f</span><span class="s1">'{self.__class__.__name__}'</span>
  45. <span class="n">f</span><span class="s1">'(rank={self.rank!r}, suit={self.suit!r})'</span><span class="p">)</span>
  46. <span class="k">def</span> <span class="fm">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
  47. <span class="k">if</span> <span class="n">other</span><span class="o">.</span><span class="vm">__class__</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="vm">__class__</span><span class="p">:</span>
  48. <span class="k">return</span> <span class="bp">NotImplemented</span>
  49. <span class="k">return</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">rank</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">suit</span><span class="p">)</span> <span class="o">==</span> <span class="p">(</span><span class="n">other</span><span class="o">.</span><span class="n">rank</span><span class="p">,</span> <span class="n">other</span><span class="o">.</span><span class="n">suit</span><span class="p">)</span>
  50. </pre></div>
  51. <p>In this tutorial, you will learn exactly which conveniences data classes provide. In addition to nice representations and comparisons, you’ll see:</p>
  52. <ul>
  53. <li>How to add default values to data class fields</li>
  54. <li>How data classes allow for ordering of objects</li>
  55. <li>How to represent immutable data</li>
  56. <li>How data classes handle inheritance</li>
  57. </ul>
  58. <p>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.</p>
  59. <h2 id="alternatives-to-data-classes">Alternatives to Data Classes</h2>
  60. <p>For simple data structures, you have probably already used <a href="https://dbader.org/blog/records-structs-and-data-transfer-objects-in-python">a <code>tuple</code> or a <code>dict</code></a>. You could represent the queen of hearts card in either of the following ways:</p>
  61. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts_tuple</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'Hearts'</span><span class="p">)</span>
  62. <span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts_dict</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'rank'</span><span class="p">:</span> <span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'suit'</span><span class="p">:</span> <span class="s1">'Hearts'</span><span class="p">}</span>
  63. </pre></div>
  64. <p>It works. However, it puts a lot of responsibility on you as a programmer:</p>
  65. <ul>
  66. <li>You need to remember that the <code>queen_of_hearts_...</code> variable represents a card.</li>
  67. <li>For the <code>_tuple</code> version, you need to remember the order of the attributes. Writing <code>('Spades', 'A')</code> will mess up your program but probably not give you an easily understandable error message.</li>
  68. <li>If you use the <code>_dict</code>, you must make sure the names of the attributes are consistent. For instance <code>{'value': 'A', 'suit': 'Spades'}</code> will not work as expected.</li>
  69. </ul>
  70. <p>Furthermore, using these structures is not ideal:</p>
  71. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts_tuple</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="c1"># No named access</span>
  72. <span class="go">'Q'</span>
  73. <span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts_dict</span><span class="p">[</span><span class="s1">'suit'</span><span class="p">]</span> <span class="c1"># Would be nicer with .suit</span>
  74. <span class="go">'Hearts'</span>
  75. </pre></div>
  76. <p>A better alternative is the <a href="https://dbader.org/blog/writing-clean-python-with-namedtuples"><code>namedtuple</code></a>. It has long been used to create readable small data structures. We can in fact recreate the data class example above using a <code>namedtuple</code> like this:</p>
  77. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">namedtuple</span>
  78. <span class="n">NamedTupleCard</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s1">'NamedTupleCard'</span><span class="p">,</span> <span class="p">[</span><span class="s1">'rank'</span><span class="p">,</span> <span class="s1">'suit'</span><span class="p">])</span>
  79. </pre></div>
  80. <p>This definition of <code>NamedTupleCard</code> will give the exact same output as our <code>DataClassCard</code> example did:</p>
  81. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span> <span class="o">=</span> <span class="n">NamedTupleCard</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'Hearts'</span><span class="p">)</span>
  82. <span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span><span class="o">.</span><span class="n">rank</span>
  83. <span class="go">'Q'</span>
  84. <span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span>
  85. <span class="go">NamedTupleCard(rank='Q', suit='Hearts')</span>
  86. <span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span> <span class="o">==</span> <span class="n">NamedTupleCard</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'Hearts'</span><span class="p">)</span>
  87. <span class="go">True</span>
  88. </pre></div>
  89. <p>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 <code>namedtuple</code> has some other features that are not necessarily desirable. By design, a <code>namedtuple</code> is a regular tuple. This can be seen in comparisons, for instance:</p>
  90. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span> <span class="o">==</span> <span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'Hearts'</span><span class="p">)</span>
  91. <span class="go">True</span>
  92. </pre></div>
  93. <p>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 <code>namedtuple</code> classes:</p>
  94. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">Person</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s1">'Person'</span><span class="p">,</span> <span class="p">[</span><span class="s1">'first_initial'</span><span class="p">,</span> <span class="s1">'last_name'</span><span class="p">]</span>
  95. <span class="gp">&gt;&gt;&gt; </span><span class="n">ace_of_spades</span> <span class="o">=</span> <span class="n">NamedTupleCard</span><span class="p">(</span><span class="s1">'A'</span><span class="p">,</span> <span class="s1">'Spades'</span><span class="p">)</span>
  96. <span class="gp">&gt;&gt;&gt; </span><span class="n">ace_of_spades</span> <span class="o">==</span> <span class="n">Person</span><span class="p">(</span><span class="s1">'A'</span><span class="p">,</span> <span class="s1">'Spades'</span><span class="p">)</span>
  97. <span class="go">True</span>
  98. </pre></div>
  99. <p>The <code>namedtuple</code> also comes with some restrictions. For instance, it is hard to add default values to some of the fields in a <code>namedtuple</code>. A <code>namedtuple</code> is also by nature immutable. That is, the value of a <code>namedtuple</code> can never change. In some applications, this is an awesome feature, but in other settings, it would be nice to have more flexibility:</p>
  100. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">card</span> <span class="o">=</span> <span class="n">NamedTupleCard</span><span class="p">(</span><span class="s1">'7'</span><span class="p">,</span> <span class="s1">'Diamonds'</span><span class="p">)</span>
  101. <span class="gp">&gt;&gt;&gt; </span><span class="n">card</span><span class="o">.</span><span class="n">rank</span> <span class="o">=</span> <span class="s1">'9'</span>
  102. <span class="go">AttributeError: can't set attribute</span>
  103. </pre></div>
  104. <p>Data classes will not replace all uses of <code>namedtuple</code>. For instance, if you need your data structure to behave like a tuple, then a named tuple is a great alternative!</p>
  105. <p>Another alternative, and one of the <a href="https://mail.python.org/pipermail/python-dev/2017-December/151034.html">inspirations for data classes</a>, is the <a href="http://www.attrs.org"><code>attrs</code> project</a>. With <code>attrs</code> installed (<code>pip install attrs</code>), you can write a card class as follows:</p>
  106. <div class="highlight python"><pre><span/><span class="kn">import</span> <span class="nn">attr</span>
  107. <span class="nd">@attr.s</span>
  108. <span class="k">class</span> <span class="nc">AttrsCard</span><span class="p">:</span>
  109. <span class="n">rank</span> <span class="o">=</span> <span class="n">attr</span><span class="o">.</span><span class="n">ib</span><span class="p">()</span>
  110. <span class="n">suit</span> <span class="o">=</span> <span class="n">attr</span><span class="o">.</span><span class="n">ib</span><span class="p">()</span>
  111. </pre></div>
  112. <p>This can be used in exactly the same way as the <code>DataClassCard</code> and <code>NamedTupleCard</code> examples earlier. The <code>attrs</code> project is great and does support some features that data classes do not, including converters and validators. Furthermore, <code>attrs</code> has been around for a while and is supported in Python 2.7 as well as Python 3.4 and up. However, as <code>attrs</code> 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.</p>
  113. <p>In addition to <code>tuple</code>, <code>dict</code>, <code>namedtuple</code>, and <code>attrs</code>, there are <a href="https://www.python.org/dev/peps/pep-0557/#rationale">many other similar projects</a>, including <a href="https://docs.python.org/library/typing.html#typing.NamedTuple"><code>typing.NamedTuple</code></a>, <a href="https://pypi.org/project/namedlist/"><code>namedlist</code></a>, <a href="https://pypi.org/project/attrdict/"><code>attrdict</code></a>, <a href="https://pypi.org/project/plumber/"><code>plumber</code></a>, and <a href="https://pypi.org/project/fields/"><code>fields</code></a>. 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.</p>
  114. <h2 id="basic-data-classes">Basic Data Classes</h2>
  115. <p>Let us get back to data classes. As an example, we will create a <code>Position</code> class that will represent geographic positions with a name as well as the latitude and longitude:</p>
  116. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  117. <span class="nd">@dataclass</span>
  118. <span class="k">class</span> <span class="nc">Position</span><span class="p">:</span>
  119. <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
  120. <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span>
  121. <span class="n">lat</span><span class="p">:</span> <span class="nb">float</span>
  122. </pre></div>
  123. <p>What makes this a data class is the <a href="https://realpython.com/primer-on-python-decorators/"><code>@dataclass</code> decorator</a> just above the class definition. Beneath the <code>class Position:</code> line, you simply list the fields you want in your data class. The <code>:</code> notation used for the fields is using a new feature in Python 3.6 called <a href="https://www.python.org/dev/peps/pep-0526/">variable annotations</a>. We will <a href="#type-hints">soon</a> talk more about this notation and why we specify data types like <code>str</code> and <code>float</code>.</p>
  124. <p>Those few lines of code are all you need. The new class is ready for use:</p>
  125. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">pos</span> <span class="o">=</span> <span class="n">Position</span><span class="p">(</span><span class="s1">'Oslo'</span><span class="p">,</span> <span class="mf">10.8</span><span class="p">,</span> <span class="mf">59.9</span><span class="p">)</span>
  126. <span class="gp">&gt;&gt;&gt; </span><span class="k">print</span><span class="p">(</span><span class="n">pos</span><span class="p">)</span>
  127. <span class="go">Position(name='Oslo', lon=10.8, lat=59.9)</span>
  128. <span class="gp">&gt;&gt;&gt; </span><span class="n">pos</span><span class="o">.</span><span class="n">lat</span>
  129. <span class="go">59.9</span>
  130. <span class="gp">&gt;&gt;&gt; </span><span class="k">print</span><span class="p">(</span><span class="n">f</span><span class="s1">'{pos.name} is at {pos.lat}°N, {pos.lon}°E'</span><span class="p">)</span>
  131. <span class="go">Oslo is at 59.9°N, 10.8°E</span>
  132. </pre></div>
  133. <p>You can also create data classes similarly to how named tuples are created. The following is (almost) equivalent to the definition of <code>Position</code> above:</p>
  134. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">make_dataclass</span>
  135. <span class="n">Position</span> <span class="o">=</span> <span class="n">make_dataclass</span><span class="p">(</span><span class="s1">'Position'</span><span class="p">,</span> <span class="p">[</span><span class="s1">'name'</span><span class="p">,</span> <span class="s1">'lat'</span><span class="p">,</span> <span class="s1">'lon'</span><span class="p">])</span>
  136. </pre></div>
  137. <p>A data class is a regular Python class. The only thing that sets it apart is that it has basic <a href="https://docs.python.org/reference/datamodel.html#basic-customization">data model methods</a> like <code>.__init__()</code>, <code>.__repr__()</code>, and <code>.__eq__()</code> implemented for you.</p>
  138. <h3 id="default-values">Default Values</h3>
  139. <p>It is easy to add default values to the fields of your data class:</p>
  140. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  141. <span class="nd">@dataclass</span>
  142. <span class="k">class</span> <span class="nc">Position</span><span class="p">:</span>
  143. <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
  144. <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">0.0</span>
  145. <span class="n">lat</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">0.0</span>
  146. </pre></div>
  147. <p>This works exactly as if you had specified the default values in the definition of the <code>.__init__()</code> method of a regular class:</p>
  148. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">Position</span><span class="p">(</span><span class="s1">'Null Island'</span><span class="p">)</span>
  149. <span class="go">Position(name='Null Island', lon=0.0, lat=0.0)</span>
  150. <span class="gp">&gt;&gt;&gt; </span><span class="n">Position</span><span class="p">(</span><span class="s1">'Greenwich'</span><span class="p">,</span> <span class="n">lat</span><span class="o">=</span><span class="mf">51.8</span><span class="p">)</span>
  151. <span class="go">Position(name='Greenwich', lon=0.0, lat=51.8)</span>
  152. <span class="gp">&gt;&gt;&gt; </span><span class="n">Position</span><span class="p">(</span><span class="s1">'Vancouver'</span><span class="p">,</span> <span class="o">-</span><span class="mf">123.1</span><span class="p">,</span> <span class="mf">49.3</span><span class="p">)</span>
  153. <span class="go">Position(name='Vancouver', lon=-123.1, lat=49.3)</span>
  154. </pre></div>
  155. <p><a href="#advanced-default-values">Later</a> you will learn about <code>default_factory</code>, which gives a way to provide more complicated default values.</p>
  156. <h3 id="type-hints">Type Hints</h3>
  157. <p>So far, we have not made a big fuss of the fact that data classes support <a href="https://www.youtube.com/watch?v=2xWhaALHTvU">typing</a> out of the box. You have probably noticed that we defined the fields with a type hint: <code>name: str</code> says that <code>name</code> should be a text string (<code>str</code> type).</p>
  158. <p>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 <code>typing.Any</code>:</p>
  159. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  160. <span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span>
  161. <span class="nd">@dataclass</span>
  162. <span class="k">class</span> <span class="nc">WithoutExplicitTypes</span><span class="p">:</span>
  163. <span class="n">name</span><span class="p">:</span> <span class="n">Any</span>
  164. <span class="n">value</span><span class="p">:</span> <span class="n">Any</span> <span class="o">=</span> <span class="mi">42</span>
  165. </pre></div>
  166. <p>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:</p>
  167. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">Position</span><span class="p">(</span><span class="mf">3.14</span><span class="p">,</span> <span class="s1">'pi day'</span><span class="p">,</span> <span class="mi">2018</span><span class="p">)</span>
  168. <span class="go">Position(name=3.14, lon='pi day', lat=2018)</span>
  169. </pre></div>
  170. <p>This is how typing in Python usually works: <a href="https://www.python.org/dev/peps/pep-0484/#non-goals">Python is and will always be a dynamically typed language</a>. To actually catch type errors, type checkers like <a href="http://mypy-lang.org">Mypy</a> can be run on your source code.</p>
  171. <h3 id="adding-methods">Adding Methods</h3>
  172. <p>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 <a href="https://en.wikipedia.org/wiki/Haversine_formula">the haversine formula</a>:</p>
  173. <p><a href="https://files.realpython.com/media/haversine_formula_150.fb2b87d122a4.png"><img class="img-fluid mb-3 mx-auto d-block" src="https://files.realpython.com/media/haversine_formula_150.fb2b87d122a4.png" alt="The haversine formula"/></a></p>
  174. <p>You can add a <code>.distance_to()</code> method to your data class just like you can with normal classes:</p>
  175. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  176. <span class="kn">from</span> <span class="nn">math</span> <span class="kn">import</span> <span class="n">asin</span><span class="p">,</span> <span class="n">cos</span><span class="p">,</span> <span class="n">radians</span><span class="p">,</span> <span class="n">sin</span><span class="p">,</span> <span class="n">sqrt</span>
  177. <span class="nd">@dataclass</span>
  178. <span class="k">class</span> <span class="nc">Position</span><span class="p">:</span>
  179. <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
  180. <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">0.0</span>
  181. <span class="n">lat</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">0.0</span>
  182. <span class="k">def</span> <span class="nf">distance_to</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
  183. <span class="n">r</span> <span class="o">=</span> <span class="mi">6371</span> <span class="c1"># Earth radius in kilometers</span>
  184. <span class="n">lam_1</span><span class="p">,</span> <span class="n">lam_2</span> <span class="o">=</span> <span class="n">radians</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">lon</span><span class="p">),</span> <span class="n">radians</span><span class="p">(</span><span class="n">other</span><span class="o">.</span><span class="n">lon</span><span class="p">)</span>
  185. <span class="n">phi_1</span><span class="p">,</span> <span class="n">phi_2</span> <span class="o">=</span> <span class="n">radians</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">lat</span><span class="p">),</span> <span class="n">radians</span><span class="p">(</span><span class="n">other</span><span class="o">.</span><span class="n">lat</span><span class="p">)</span>
  186. <span class="n">h</span> <span class="o">=</span> <span class="p">(</span><span class="n">sin</span><span class="p">((</span><span class="n">phi_2</span> <span class="o">-</span> <span class="n">phi_1</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span>
  187. <span class="o">+</span> <span class="n">cos</span><span class="p">(</span><span class="n">phi_1</span><span class="p">)</span> <span class="o">*</span> <span class="n">cos</span><span class="p">(</span><span class="n">phi_2</span><span class="p">)</span> <span class="o">*</span> <span class="n">sin</span><span class="p">((</span><span class="n">lam_2</span> <span class="o">-</span> <span class="n">lam_1</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span><span class="p">)</span>
  188. <span class="k">return</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">r</span> <span class="o">*</span> <span class="n">asin</span><span class="p">(</span><span class="n">sqrt</span><span class="p">(</span><span class="n">h</span><span class="p">))</span>
  189. </pre></div>
  190. <p>It works as you would expect:</p>
  191. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">oslo</span> <span class="o">=</span> <span class="n">Position</span><span class="p">(</span><span class="s1">'Oslo'</span><span class="p">,</span> <span class="mf">10.8</span><span class="p">,</span> <span class="mf">59.9</span><span class="p">)</span>
  192. <span class="gp">&gt;&gt;&gt; </span><span class="n">vancouver</span> <span class="o">=</span> <span class="n">Position</span><span class="p">(</span><span class="s1">'Vancouver'</span><span class="p">,</span> <span class="o">-</span><span class="mf">123.1</span><span class="p">,</span> <span class="mf">49.3</span><span class="p">)</span>
  193. <span class="gp">&gt;&gt;&gt; </span><span class="n">oslo</span><span class="o">.</span><span class="n">distance_to</span><span class="p">(</span><span class="n">vancouver</span><span class="p">)</span>
  194. <span class="go">7181.7841229421165</span>
  195. </pre></div>
  196. <h2 id="more-flexible-data-classes">More Flexible Data Classes</h2>
  197. <p>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 <code>@dataclass</code> decorator and the <code>field()</code> function. Together, they give you more control when creating a data class.</p>
  198. <p>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:</p>
  199. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  200. <span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
  201. <span class="nd">@dataclass</span>
  202. <span class="k">class</span> <span class="nc">PlayingCard</span><span class="p">:</span>
  203. <span class="n">rank</span><span class="p">:</span> <span class="nb">str</span>
  204. <span class="n">suit</span><span class="p">:</span> <span class="nb">str</span>
  205. <span class="nd">@dataclass</span>
  206. <span class="k">class</span> <span class="nc">Deck</span><span class="p">:</span>
  207. <span class="n">cards</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">PlayingCard</span><span class="p">]</span>
  208. </pre></div>
  209. <p>A simple deck containing only two cards can be created like this:</p>
  210. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span> <span class="o">=</span> <span class="n">PlayingCard</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'Hearts'</span><span class="p">)</span>
  211. <span class="gp">&gt;&gt;&gt; </span><span class="n">ace_of_spades</span> <span class="o">=</span> <span class="n">PlayingCard</span><span class="p">(</span><span class="s1">'A'</span><span class="p">,</span> <span class="s1">'Spades'</span><span class="p">)</span>
  212. <span class="gp">&gt;&gt;&gt; </span><span class="n">two_cards</span> <span class="o">=</span> <span class="n">Deck</span><span class="p">([</span><span class="n">queen_of_hearts</span><span class="p">,</span> <span class="n">ace_of_spades</span><span class="p">])</span>
  213. <span class="go">Deck(cards=[PlayingCard(rank='Q', suit='Hearts'),</span>
  214. <span class="go"> PlayingCard(rank='A', suit='Spades')])</span>
  215. </pre></div>
  216. <h3 id="advanced-default-values">Advanced Default Values</h3>
  217. <p>Say that you want to give a default value to the <code>Deck</code>. It would for example be convenient if <code>Deck()</code> created a <a href="https://en.wikipedia.org/wiki/French_playing_cards">regular (French) deck</a> of 52 playing cards. First, specify the different ranks and suits. Then, add a function <code>make_french_deck()</code> that creates a list of instances of <code>PlayingCard</code>:</p>
  218. <div class="highlight python"><pre><span/><span class="n">RANKS</span> <span class="o">=</span> <span class="s1">'2 3 4 5 6 7 8 9 10 J Q K A'</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
  219. <span class="n">SUITS</span> <span class="o">=</span> <span class="s1">'♣ ♢ ♡ ♠'</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
  220. <span class="k">def</span> <span class="nf">make_french_deck</span><span class="p">():</span>
  221. <span class="k">return</span> <span class="p">[</span><span class="n">PlayingCard</span><span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">s</span><span class="p">)</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">SUITS</span> <span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">RANKS</span><span class="p">]</span>
  222. </pre></div>
  223. <p>For fun, the four different suits are specified using their <a href="https://en.wikipedia.org/wiki/Playing_cards_in_Unicode">Unicode symbols</a>.</p>
  224. <blockquote>
  225. <p><strong>Note:</strong> Above, we used Unicode glyphs like <code>♠</code> directly in the source code. We could do this because <a href="https://docs.python.org/howto/unicode.html#unicode-literals-in-python-source-code">Python supports writing source code in UTF-8 by default</a>. Refer to <a href="https://en.wikipedia.org/wiki/Unicode_input">this page on Unicode input</a> for how to enter these on your system. You could also enter the Unicode symbols for the suits using <code>\N</code> named character escapes (like <code>\N{BLACK SPADE SUIT}</code>) or <code>\u</code> Unicode escapes (like <code>\u2660</code>).</p>
  226. </blockquote>
  227. <p>To simplify comparisons of cards later, the ranks and suits are also listed in their usual order.</p>
  228. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">make_french_deck</span><span class="p">()</span>
  229. <span class="go">[PlayingCard(rank='2', suit='♣'), PlayingCard(rank='3', suit='♣'), ...</span>
  230. <span class="go"> PlayingCard(rank='K', suit='♠'), PlayingCard(rank='A', suit='♠')]</span>
  231. </pre></div>
  232. <p>In theory, you could now use this function to specify a default value for <code>Deck.cards</code>:</p>
  233. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  234. <span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
  235. <span class="nd">@dataclass</span>
  236. <span class="k">class</span> <span class="nc">Deck</span><span class="p">:</span> <span class="c1"># Will NOT work</span>
  237. <span class="n">cards</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">PlayingCard</span><span class="p">]</span> <span class="o">=</span> <span class="n">make_french_deck</span><span class="p">()</span>
  238. </pre></div>
  239. <p>Don’t do this! This introduces one of the most common anti-patterns in Python: <a href="http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments">using mutable default arguments</a>. The problem is that all instances of <code>Deck</code> will use the same list object as the default value of the <code>.cards</code> property. This means that if, say, one card is removed from one <code>Deck</code>, then it disappears from all other instances of <code>Deck</code> as well. Actually, data classes try to <a href="https://www.python.org/dev/peps/pep-0557/#mutable-default-values">prevent you from doing this</a>, and the code above will raise a <code>ValueError</code>.</p>
  240. <p>Instead, data classes use something called a <code>default_factory</code> to handle mutable default values. To use <code>default_factory</code> (and many other cool features of data classes), you need to use the <code>field()</code> specifier:</p>
  241. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
  242. <span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
  243. <span class="nd">@dataclass</span>
  244. <span class="k">class</span> <span class="nc">Deck</span><span class="p">:</span>
  245. <span class="n">cards</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">PlayingCard</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="n">make_french_deck</span><span class="p">)</span>
  246. </pre></div>
  247. <p>The argument to <code>default_factory</code> can be any zero parameter callable. Now it is easy to create a full deck of playing cards:</p>
  248. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">Deck</span><span class="p">()</span>
  249. <span class="go">Deck(cards=[PlayingCard(rank='2', suit='♣'), PlayingCard(rank='3', suit='♣'), ...</span>
  250. <span class="go"> PlayingCard(rank='K', suit='♠'), PlayingCard(rank='A', suit='♠')])</span>
  251. </pre></div>
  252. <p>The <code>field()</code> 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 <code>field()</code> supports:</p>
  253. <ul>
  254. <li><code>default</code>: Default value of the field</li>
  255. <li><code>default_factory</code>: Function that returns the initial value of the field</li>
  256. <li><code>init</code>: Use field in <code>.__init__()</code> method? (Default is <code>True</code>.)</li>
  257. <li><code>repr</code>: Use field in <code>repr</code> of the object? (Default is <code>True</code>.)</li>
  258. <li><code>compare</code>: Include the field in comparisons? (Default is <code>True</code>.)</li>
  259. <li><code>hash</code>: Include the field when calculating <code>hash()</code>? (Default is to use the same as for <code>compare</code>.)</li>
  260. <li><code>metadata</code>: A mapping with information about the field</li>
  261. </ul>
  262. <p>In the <code>Position</code> example, you saw how to add simple default values by writing <code>lat: float = 0.0</code>. However, if you also want to customize the field, for instance to hide it in the <code>repr</code>, you need to use the <code>default</code> parameter: <code>lat: float = field(default=0.0, repr=False)</code>. You may not specify both <code>default</code> and <code>default_factory</code>.</p>
  263. <p>The <code>metadata</code> 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 <code>Position</code> example, you could for instance specify that latitude and longitude should be given in degrees:</p>
  264. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
  265. <span class="nd">@dataclass</span>
  266. <span class="k">class</span> <span class="nc">Position</span><span class="p">:</span>
  267. <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
  268. <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">metadata</span><span class="o">=</span><span class="p">{</span><span class="s1">'unit'</span><span class="p">:</span> <span class="s1">'degrees'</span><span class="p">})</span>
  269. <span class="n">lat</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">metadata</span><span class="o">=</span><span class="p">{</span><span class="s1">'unit'</span><span class="p">:</span> <span class="s1">'degrees'</span><span class="p">})</span>
  270. </pre></div>
  271. <p>The metadata (and other information about a field) can be retrieved using the <code>fields()</code> function (note the plural <em>s</em>):</p>
  272. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">fields</span>
  273. <span class="gp">&gt;&gt;&gt; </span><span class="n">fields</span><span class="p">(</span><span class="n">Position</span><span class="p">)</span>
  274. <span class="go">(Field(name='name',type=&lt;class 'str'&gt;,...,metadata={}),</span>
  275. <span class="go"> Field(name='lon',type=&lt;class 'float'&gt;,...,metadata={'unit': 'degrees'}),</span>
  276. <span class="go"> Field(name='lat',type=&lt;class 'float'&gt;,...,metadata={'unit': 'degrees'}))</span>
  277. <span class="gp">&gt;&gt;&gt; </span><span class="n">lat_unit</span> <span class="o">=</span> <span class="n">fields</span><span class="p">(</span><span class="n">Position</span><span class="p">)[</span><span class="mi">2</span><span class="p">]</span><span class="o">.</span><span class="n">metadata</span><span class="p">[</span><span class="s1">'unit'</span><span class="p">]</span>
  278. <span class="gp">&gt;&gt;&gt; </span><span class="n">lat_unit</span>
  279. <span class="go">'degrees'</span>
  280. </pre></div>
  281. <h3 id="you-need-representation">You Need Representation?</h3>
  282. <p>Recall that we can create decks of cards out of thin air:</p>
  283. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">Deck</span><span class="p">()</span>
  284. <span class="go">Deck(cards=[PlayingCard(rank='2', suit='♣'), PlayingCard(rank='3', suit='♣'), ...</span>
  285. <span class="go"> PlayingCard(rank='K', suit='♠'), PlayingCard(rank='A', suit='♠')])</span>
  286. </pre></div>
  287. <p>While this representation of a <code>Deck</code> 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 <code>Deck</code> takes up 22 lines! Let us add a more concise representation. In general, a Python object has <a href="https://dbader.org/blog/python-repr-vs-str">two different string representations</a>:</p>
  288. <ul>
  289. <li>
  290. <p><code>repr(obj)</code> is defined by <code>obj.__repr__()</code> and should return a developer-friendly representation of <code>obj</code>. If possible, this should be code that can recreate <code>obj</code>. Data classes do this.</p>
  291. </li>
  292. <li>
  293. <p><code>str(obj)</code> is defined by <code>obj.__str__()</code> and should return a user-friendly representation of <code>obj</code>. Data classes do not implement a <code>.__str__()</code> method, so Python will fall back to the <code>.__repr__()</code> method.</p>
  294. </li>
  295. </ul>
  296. <p>Let us implement a user-friendly representation of a <code>PlayingCard</code>:</p>
  297. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  298. <span class="nd">@dataclass</span>
  299. <span class="k">class</span> <span class="nc">PlayingCard</span><span class="p">:</span>
  300. <span class="n">rank</span><span class="p">:</span> <span class="nb">str</span>
  301. <span class="n">suit</span><span class="p">:</span> <span class="nb">str</span>
  302. <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
  303. <span class="k">return</span> <span class="n">f</span><span class="s1">'{self.suit}{self.rank}'</span>
  304. </pre></div>
  305. <p>The cards now look much nicer, but the deck is still as verbose as ever:</p>
  306. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">ace_of_spades</span> <span class="o">=</span> <span class="n">PlayingCard</span><span class="p">(</span><span class="s1">'A'</span><span class="p">,</span> <span class="s1">'♠'</span><span class="p">)</span>
  307. <span class="gp">&gt;&gt;&gt; </span><span class="n">ace_of_spades</span>
  308. <span class="go">PlayingCard(rank='A', suit='♠')</span>
  309. <span class="gp">&gt;&gt;&gt; </span><span class="k">print</span><span class="p">(</span><span class="n">ace_of_spades</span><span class="p">)</span>
  310. <span class="go">♠A</span>
  311. <span class="gp">&gt;&gt;&gt; </span><span class="k">print</span><span class="p">(</span><span class="n">Deck</span><span class="p">())</span>
  312. <span class="go">Deck(cards=[PlayingCard(rank='2', suit='♣'), PlayingCard(rank='3', suit='♣'), ...</span>
  313. <span class="go"> PlayingCard(rank='K', suit='♠'), PlayingCard(rank='A', suit='♠')])</span>
  314. </pre></div>
  315. <p>To show that it is possible to add your own <code>.__repr__()</code> method as well, we will violate the principle that it should return code that can recreate an object. <a href="https://www.python.org/dev/peps/pep-0020/">Practicality beats purity</a> after all. The following code adds a more concise representation of the <code>Deck</code>:</p>
  316. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
  317. <span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
  318. <span class="nd">@dataclass</span>
  319. <span class="k">class</span> <span class="nc">Deck</span><span class="p">:</span>
  320. <span class="n">cards</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">PlayingCard</span><span class="p">]</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">default_factory</span><span class="o">=</span><span class="n">make_french_deck</span><span class="p">)</span>
  321. <span class="k">def</span> <span class="fm">__repr__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
  322. <span class="n">cards</span> <span class="o">=</span> <span class="s1">', '</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">f</span><span class="s1">'{c!s}'</span> <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">cards</span><span class="p">)</span>
  323. <span class="k">return</span> <span class="n">f</span><span class="s1">'{self.__class__.__name__}({cards})'</span>
  324. </pre></div>
  325. <p>Note the <code>!s</code> specifier in the <code>{c!s}</code> format string. It means that we explicitly want to use the <code>str()</code> representation of <code>PlayingCard</code>s. With the new <code>.__repr__()</code>, the representation of <code>Deck</code> is easier on the eyes:</p>
  326. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">Deck</span><span class="p">()</span>
  327. <span class="go">Deck(♣2, ♣3, ♣4, ♣5, ♣6, ♣7, ♣8, ♣9, ♣10, ♣J, ♣Q, ♣K, ♣A,</span>
  328. <span class="go"> ♢2, ♢3, ♢4, ♢5, ♢6, ♢7, ♢8, ♢9, ♢10, ♢J, ♢Q, ♢K, ♢A,</span>
  329. <span class="go"> ♡2, ♡3, ♡4, ♡5, ♡6, ♡7, ♡8, ♡9, ♡10, ♡J, ♡Q, ♡K, ♡A,</span>
  330. <span class="go"> ♠2, ♠3, ♠4, ♠5, ♠6, ♠7, ♠8, ♠9, ♠10, ♠J, ♠Q, ♠K, ♠A)</span>
  331. </pre></div>
  332. <h3 id="comparing-cards">Comparing Cards</h3>
  333. <p>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 <code>PlayingCard</code> class does not support this kind of comparison:</p>
  334. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span> <span class="o">=</span> <span class="n">PlayingCard</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'♡'</span><span class="p">)</span>
  335. <span class="gp">&gt;&gt;&gt; </span><span class="n">ace_of_spades</span> <span class="o">=</span> <span class="n">PlayingCard</span><span class="p">(</span><span class="s1">'A'</span><span class="p">,</span> <span class="s1">'♠'</span><span class="p">)</span>
  336. <span class="gp">&gt;&gt;&gt; </span><span class="n">ace_of_spades</span> <span class="o">&gt;</span> <span class="n">queen_of_hearts</span>
  337. <span class="go">TypeError: '&gt;' not supported between instances of 'Card' and 'Card'</span>
  338. </pre></div>
  339. <p>This is, however, (seemingly) easy to rectify:</p>
  340. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  341. <span class="nd">@dataclass</span><span class="p">(</span><span class="n">order</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
  342. <span class="k">class</span> <span class="nc">PlayingCard</span><span class="p">:</span>
  343. <span class="n">rank</span><span class="p">:</span> <span class="nb">str</span>
  344. <span class="n">suit</span><span class="p">:</span> <span class="nb">str</span>
  345. <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
  346. <span class="k">return</span> <span class="n">f</span><span class="s1">'{self.suit}{self.rank}'</span>
  347. </pre></div>
  348. <p>The <code>@dataclass</code> decorator has two forms. So far you have seen the simple form where <code>@dataclass</code> is specified without any parentheses and parameters. However, you can also give parameters to the <code>@dataclass()</code> decorator in parentheses. The following parameters are supported:</p>
  349. <ul>
  350. <li><code>init</code>: Add <code>.__init__()</code> method? (Default is <code>True</code>.)</li>
  351. <li><code>repr</code>: Add <code>.__repr__()</code> method? (Default is <code>True</code>.)</li>
  352. <li><code>eq</code>: Add <code>.__eq__()</code> method? (Default is <code>True</code>.)</li>
  353. <li><code>order</code>: Add ordering methods? (Default is <code>False</code>.)</li>
  354. <li><code>unsafe_hash</code>: Force the addition of a <code>.__hash__()</code> method? (Default is <code>False</code>.)</li>
  355. <li><code>frozen</code>: If <code>True</code>, assigning to fields raise an exception. (Default is <code>False</code>.)</li>
  356. </ul>
  357. <p>See <a href="https://www.python.org/dev/peps/pep-0557/#id7">the original PEP</a> for more information about each parameter. After setting <code>order=True</code>, instances of <code>PlayingCard</code> can be compared:</p>
  358. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span> <span class="o">=</span> <span class="n">PlayingCard</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'♡'</span><span class="p">)</span>
  359. <span class="gp">&gt;&gt;&gt; </span><span class="n">ace_of_spades</span> <span class="o">=</span> <span class="n">PlayingCard</span><span class="p">(</span><span class="s1">'A'</span><span class="p">,</span> <span class="s1">'♠'</span><span class="p">)</span>
  360. <span class="gp">&gt;&gt;&gt; </span><span class="n">ace_of_spades</span> <span class="o">&gt;</span> <span class="n">queen_of_hearts</span>
  361. <span class="go">False</span>
  362. </pre></div>
  363. <p>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…</p>
  364. <p>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 <code>'Q'</code> comes after <code>'A'</code> in the alphabet:</p>
  365. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="p">(</span><span class="s1">'A'</span><span class="p">,</span> <span class="s1">'♠'</span><span class="p">)</span> <span class="o">&gt;</span> <span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'♡'</span><span class="p">)</span>
  366. <span class="go">False</span>
  367. </pre></div>
  368. <p>That does not really work for us. Instead, we need to define some kind of sort index that uses the order of <code>RANKS</code> and <code>SUITS</code>. Something like this:</p>
  369. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">RANKS</span> <span class="o">=</span> <span class="s1">'2 3 4 5 6 7 8 9 10 J Q K A'</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
  370. <span class="gp">&gt;&gt;&gt; </span><span class="n">SUITS</span> <span class="o">=</span> <span class="s1">'♣ ♢ ♡ ♠'</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
  371. <span class="gp">&gt;&gt;&gt; </span><span class="n">card</span> <span class="o">=</span> <span class="n">PlayingCard</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'♡'</span><span class="p">)</span>
  372. <span class="gp">&gt;&gt;&gt; </span><span class="n">RANKS</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="n">card</span><span class="o">.</span><span class="n">rank</span><span class="p">)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">SUITS</span><span class="p">)</span> <span class="o">+</span> <span class="n">SUITS</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="n">card</span><span class="o">.</span><span class="n">suit</span><span class="p">)</span>
  373. <span class="go">42</span>
  374. </pre></div>
  375. <p>For <code>PlayingCard</code> to use this sort index for comparisons, we need to add a field <code>.sort_index</code> to the class. However, this field should be calculated from the other fields <code>.rank</code> and <code>.suit</code> automatically. This is exactly what the special method <code>.__post_init__()</code> is for. It allows for special processing after the regular <code>.__init__()</code> method is called:</p>
  376. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span><span class="p">,</span> <span class="n">field</span>
  377. <span class="n">RANKS</span> <span class="o">=</span> <span class="s1">'2 3 4 5 6 7 8 9 10 J Q K A'</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
  378. <span class="n">SUITS</span> <span class="o">=</span> <span class="s1">'♣ ♢ ♡ ♠'</span><span class="o">.</span><span class="n">split</span><span class="p">()</span>
  379. <span class="nd">@dataclass</span><span class="p">(</span><span class="n">order</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
  380. <span class="k">class</span> <span class="nc">PlayingCard</span><span class="p">:</span>
  381. <span class="n">sort_index</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="n">field</span><span class="p">(</span><span class="n">init</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="nb">repr</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
  382. <span class="n">rank</span><span class="p">:</span> <span class="nb">str</span>
  383. <span class="n">suit</span><span class="p">:</span> <span class="nb">str</span>
  384. <span class="k">def</span> <span class="nf">__post_init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
  385. <span class="bp">self</span><span class="o">.</span><span class="n">sort_index</span> <span class="o">=</span> <span class="p">(</span><span class="n">RANKS</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">rank</span><span class="p">)</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">SUITS</span><span class="p">)</span>
  386. <span class="o">+</span> <span class="n">SUITS</span><span class="o">.</span><span class="n">index</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">suit</span><span class="p">))</span>
  387. <span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
  388. <span class="k">return</span> <span class="n">f</span><span class="s1">'{self.suit}{self.rank}'</span>
  389. </pre></div>
  390. <p>Note that <code>.sort_index</code> is added as the first field of the class. That way, the comparison is first done using <code>.sort_index</code> and only if there are ties are the other fields used. Using <code>field()</code>, you must also specify that <code>.sort_index</code> should not be included as a parameter in the <code>.__init__()</code> method (because it is calculated from the <code>.rank</code> and <code>.suit</code> fields). To avoid confusing the user about this implementation detail, it is probably also a good idea to remove <code>.sort_index</code> from the <code>repr</code> of the class.</p>
  391. <p>Finally, aces are high:</p>
  392. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span> <span class="o">=</span> <span class="n">PlayingCard</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'♡'</span><span class="p">)</span>
  393. <span class="gp">&gt;&gt;&gt; </span><span class="n">ace_of_spades</span> <span class="o">=</span> <span class="n">PlayingCard</span><span class="p">(</span><span class="s1">'A'</span><span class="p">,</span> <span class="s1">'♠'</span><span class="p">)</span>
  394. <span class="gp">&gt;&gt;&gt; </span><span class="n">ace_of_spades</span> <span class="o">&gt;</span> <span class="n">queen_of_hearts</span>
  395. <span class="go">True</span>
  396. </pre></div>
  397. <p>You can now easily create a sorted deck:</p>
  398. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">Deck</span><span class="p">(</span><span class="nb">sorted</span><span class="p">(</span><span class="n">make_french_deck</span><span class="p">()))</span>
  399. <span class="go">Deck(♣2, ♢2, ♡2, ♠2, ♣3, ♢3, ♡3, ♠3, ♣4, ♢4, ♡4, ♠4, ♣5,</span>
  400. <span class="go"> ♢5, ♡5, ♠5, ♣6, ♢6, ♡6, ♠6, ♣7, ♢7, ♡7, ♠7, ♣8, ♢8,</span>
  401. <span class="go"> ♡8, ♠8, ♣9, ♢9, ♡9, ♠9, ♣10, ♢10, ♡10, ♠10, ♣J, ♢J, ♡J,</span>
  402. <span class="go"> ♠J, ♣Q, ♢Q, ♡Q, ♠Q, ♣K, ♢K, ♡K, ♠K, ♣A, ♢A, ♡A, ♠A)</span>
  403. </pre></div>
  404. <p>Or, if you don’t care about sorting, this is how you draw a random hand of 10 cards:</p>
  405. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">random</span> <span class="kn">import</span> <span class="n">sample</span>
  406. <span class="gp">&gt;&gt;&gt; </span><span class="n">Deck</span><span class="p">(</span><span class="n">sample</span><span class="p">(</span><span class="n">make_french_deck</span><span class="p">(),</span> <span class="n">k</span><span class="o">=</span><span class="mi">10</span><span class="p">))</span>
  407. <span class="go">Deck(♢2, ♡A, ♢10, ♣2, ♢3, ♠3, ♢A, ♠8, ♠9, ♠2)</span>
  408. </pre></div>
  409. <p>Of course, you don’t need <code>order=True</code> for that…</p>
  410. <h2 id="immutable-data-classes">Immutable Data Classes</h2>
  411. <p>One of the defining features of the <code>namedtuple</code> you saw earlier is that it is <a href="https://medium.com/@meghamohan/mutable-and-immutable-side-of-python-c2145cf72747">immutable</a>. 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 <code>frozen=True</code> when you create it. For example, the following is an immutable version of the <code>Position</code> class <a href="#basic-data-classes">you saw earlier</a>:</p>
  412. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  413. <span class="nd">@dataclass</span><span class="p">(</span><span class="n">frozen</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
  414. <span class="k">class</span> <span class="nc">Position</span><span class="p">:</span>
  415. <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
  416. <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">0.0</span>
  417. <span class="n">lat</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">0.0</span>
  418. </pre></div>
  419. <p>In a frozen data class, you can not assign values to the fields after creation:</p>
  420. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">pos</span> <span class="o">=</span> <span class="n">Position</span><span class="p">(</span><span class="s1">'Oslo'</span><span class="p">,</span> <span class="mf">10.8</span><span class="p">,</span> <span class="mf">59.9</span><span class="p">)</span>
  421. <span class="gp">&gt;&gt;&gt; </span><span class="n">pos</span><span class="o">.</span><span class="n">name</span>
  422. <span class="go">'Oslo'</span>
  423. <span class="gp">&gt;&gt;&gt; </span><span class="n">pos</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s1">'Stockholm'</span>
  424. <span class="go">dataclasses.FrozenInstanceError: cannot assign to field 'name'</span>
  425. </pre></div>
  426. <p>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 <a href="https://www.youtube.com/watch?v=p9ppfvHv2Us">this video for further info</a>):</p>
  427. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  428. <span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
  429. <span class="nd">@dataclass</span><span class="p">(</span><span class="n">frozen</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
  430. <span class="k">class</span> <span class="nc">ImmutableCard</span><span class="p">:</span>
  431. <span class="n">rank</span><span class="p">:</span> <span class="nb">str</span>
  432. <span class="n">suit</span><span class="p">:</span> <span class="nb">str</span>
  433. <span class="nd">@dataclass</span><span class="p">(</span><span class="n">frozen</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
  434. <span class="k">class</span> <span class="nc">ImmutableDeck</span><span class="p">:</span>
  435. <span class="n">cards</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="n">PlayingCard</span><span class="p">]</span>
  436. </pre></div>
  437. <p>Even though both <code>ImmutableCard</code> and <code>ImmutableDeck</code> are immutable, the list holding <code>cards</code> is not. You can therefore still change the cards in the deck:</p>
  438. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">queen_of_hearts</span> <span class="o">=</span> <span class="n">ImmutableCard</span><span class="p">(</span><span class="s1">'Q'</span><span class="p">,</span> <span class="s1">'♡'</span><span class="p">)</span>
  439. <span class="gp">&gt;&gt;&gt; </span><span class="n">ace_of_spades</span> <span class="o">=</span> <span class="n">ImmutableCard</span><span class="p">(</span><span class="s1">'A'</span><span class="p">,</span> <span class="s1">'♠'</span><span class="p">)</span>
  440. <span class="gp">&gt;&gt;&gt; </span><span class="n">deck</span> <span class="o">=</span> <span class="n">ImmutableDeck</span><span class="p">([</span><span class="n">queen_of_hearts</span><span class="p">,</span> <span class="n">ace_of_spades</span><span class="p">])</span>
  441. <span class="gp">&gt;&gt;&gt; </span><span class="n">deck</span>
  442. <span class="go">ImmutableDeck(cards=[ImmutableCard(rank='Q', suit='♡'), ImmutableCard(rank='A', suit='♠')])</span>
  443. <span class="gp">&gt;&gt;&gt; </span><span class="n">deck</span><span class="o">.</span><span class="n">cards</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">ImmutableCard</span><span class="p">(</span><span class="s1">'7'</span><span class="p">,</span> <span class="s1">'♢'</span><span class="p">)</span>
  444. <span class="gp">&gt;&gt;&gt; </span><span class="n">deck</span>
  445. <span class="go">ImmutableDeck(cards=[ImmutableCard(rank='7', suit='♢'), ImmutableCard(rank='A', suit='♠')])</span>
  446. </pre></div>
  447. <p>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 <code>ImmutableDeck</code> should be implemented using a tuple instead of a list.</p>
  448. <h2 id="inheritance">Inheritance</h2>
  449. <p>You can <a href="https://realpython.com/python3-object-oriented-programming/">subclass</a> data classes quite freely. As an example, we will extend our <code>Position</code> example with a <code>country</code> field and use it to record capitals:</p>
  450. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  451. <span class="nd">@dataclass</span>
  452. <span class="k">class</span> <span class="nc">Position</span><span class="p">:</span>
  453. <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
  454. <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span>
  455. <span class="n">lat</span><span class="p">:</span> <span class="nb">float</span>
  456. <span class="nd">@dataclass</span>
  457. <span class="k">class</span> <span class="nc">Capital</span><span class="p">(</span><span class="n">Position</span><span class="p">):</span>
  458. <span class="n">country</span><span class="p">:</span> <span class="nb">str</span>
  459. </pre></div>
  460. <p>In this simple example, everything works without a hitch:</p>
  461. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">Capital</span><span class="p">(</span><span class="s1">'Oslo'</span><span class="p">,</span> <span class="mf">10.8</span><span class="p">,</span> <span class="mf">59.9</span><span class="p">,</span> <span class="s1">'Norway'</span><span class="p">)</span>
  462. <span class="go">Capital(name='Oslo', lon=10.8, lat=59.9, country='Norway')</span>
  463. </pre></div>
  464. <p>The <code>country</code> field of <code>Capital</code> is added after the three original fields in <code>Position</code>. Things get a little more complicated if any fields in the base class have default values:</p>
  465. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  466. <span class="nd">@dataclass</span>
  467. <span class="k">class</span> <span class="nc">Position</span><span class="p">:</span>
  468. <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
  469. <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">0.0</span>
  470. <span class="n">lat</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">0.0</span>
  471. <span class="nd">@dataclass</span>
  472. <span class="k">class</span> <span class="nc">Capital</span><span class="p">(</span><span class="n">Position</span><span class="p">):</span>
  473. <span class="n">country</span><span class="p">:</span> <span class="nb">str</span> <span class="c1"># Does NOT work</span>
  474. </pre></div>
  475. <p>This code will immediately crash with a <code>TypeError</code> complaining that “non-default argument ‘country’ follows default argument.” The problem is that our new <code>country</code> field has no default value, while the <code>lon</code> and <code>lat</code> fields have default values. The data class will try to write an <code>.__init__()</code> method with the following signature:</p>
  476. <div class="highlight python"><pre><span/><span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">0.0</span><span class="p">,</span> <span class="n">lat</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">0.0</span><span class="p">,</span> <span class="n">country</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
  477. <span class="o">...</span>
  478. </pre></div>
  479. <p>However, this is not valid Python. <a href="https://docs.python.org/reference/compound_stmts.html#function-definitions">If a parameter has a default value, all following parameters must also have a default value</a>. 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.</p>
  480. <p>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 <code>Position</code> and <code>Capital</code> as follows:</p>
  481. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  482. <span class="nd">@dataclass</span>
  483. <span class="k">class</span> <span class="nc">Position</span><span class="p">:</span>
  484. <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
  485. <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">0.0</span>
  486. <span class="n">lat</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">0.0</span>
  487. <span class="nd">@dataclass</span>
  488. <span class="k">class</span> <span class="nc">Capital</span><span class="p">(</span><span class="n">Position</span><span class="p">):</span>
  489. <span class="n">country</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s1">'Unknown'</span>
  490. <span class="n">lat</span><span class="p">:</span> <span class="nb">float</span> <span class="o">=</span> <span class="mf">40.0</span>
  491. </pre></div>
  492. <p>Then the order of the fields in <code>Capital</code> will still be <code>name</code>, <code>lon</code>, <code>lat</code>, <code>country</code>. However, the default value of <code>lat</code> will be <code>40.0</code>.</p>
  493. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="n">Capital</span><span class="p">(</span><span class="s1">'Madrid'</span><span class="p">,</span> <span class="n">country</span><span class="o">=</span><span class="s1">'Spain'</span><span class="p">)</span>
  494. <span class="go">Capital(name='Madrid', lon=0.0, lat=40.0, country='Spain')</span>
  495. </pre></div>
  496. <h2 id="optimizing-data-classes">Optimizing Data Classes</h2>
  497. <p>I’m going to end this tutorial with a few words about <a href="https://docs.python.org/reference/datamodel.html#slots">slots</a>. 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!)</p>
  498. <div class="highlight python"><pre><span/><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
  499. <span class="nd">@dataclass</span>
  500. <span class="k">class</span> <span class="nc">SimplePosition</span><span class="p">:</span>
  501. <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
  502. <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span>
  503. <span class="n">lat</span><span class="p">:</span> <span class="nb">float</span>
  504. <span class="nd">@dataclass</span>
  505. <span class="k">class</span> <span class="nc">SlotPosition</span><span class="p">:</span>
  506. <span class="vm">**slots**</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'name'</span><span class="p">,</span> <span class="s1">'lon'</span><span class="p">,</span> <span class="s1">'lat'</span><span class="p">]</span>
  507. <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
  508. <span class="n">lon</span><span class="p">:</span> <span class="nb">float</span>
  509. <span class="n">lat</span><span class="p">:</span> <span class="nb">float</span>
  510. </pre></div>
  511. <p>Essentially, slots are defined using <code>.__slots__</code> to list the variables on a class. Variables or attributes not present in <code>.__slots__</code> may not be defined. Furthermore, a slots class may not have default values.</p>
  512. <p>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 <a href="https://pythonhosted.org/Pympler/">Pympler</a>:</p>
  513. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">pympler</span> <span class="kn">import</span> <span class="n">asizeof</span>
  514. <span class="gp">&gt;&gt;&gt; </span><span class="n">simple</span> <span class="o">=</span> <span class="n">SimplePosition</span><span class="p">(</span><span class="s1">'London'</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.1</span><span class="p">,</span> <span class="mf">51.5</span><span class="p">)</span>
  515. <span class="gp">&gt;&gt;&gt; </span><span class="n">slot</span> <span class="o">=</span> <span class="n">SlotPosition</span><span class="p">(</span><span class="s1">'Madrid'</span><span class="p">,</span> <span class="o">-</span><span class="mf">3.7</span><span class="p">,</span> <span class="mf">40.4</span><span class="p">)</span>
  516. <span class="gp">&gt;&gt;&gt; </span><span class="n">asizeof</span><span class="o">.</span><span class="n">asizesof</span><span class="p">(</span><span class="n">simple</span><span class="p">,</span> <span class="n">slot</span><span class="p">)</span>
  517. <span class="go">(440, 248)</span>
  518. </pre></div>
  519. <p>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 <a href="https://docs.python.org/library/timeit.html">timeit</a> from the standard library.</p>
  520. <div class="highlight python"><pre><span/><span class="gp">&gt;&gt;&gt; </span><span class="kn">from</span> <span class="nn">timeit</span> <span class="kn">import</span> <span class="n">timeit</span>
  521. <span class="gp">&gt;&gt;&gt; </span><span class="n">timeit</span><span class="p">(</span><span class="s1">'slot.name'</span><span class="p">,</span> <span class="n">setup</span><span class="o">=</span><span class="s2">"from position import SlotPosition; slot=SlotPosition('Oslo', 10.8, 59.9)"</span><span class="p">)</span>
  522. <span class="go">0.05882283499886398</span>
  523. <span class="gp">&gt;&gt;&gt; </span><span class="n">timeit</span><span class="p">(</span><span class="s1">'simple.name'</span><span class="p">,</span> <span class="n">setup</span><span class="o">=</span><span class="s2">"from position import SimplePosition; simple=SimplePosition('Oslo', 10.8, 59.9)"</span><span class="p">)</span>
  524. <span class="go">0.09207444800267695</span>
  525. </pre></div>
  526. <p>In this particular example, the slot class is about 35% faster.</p>
  527. <h2 id="conclusion-further-reading">Conclusion &amp; Further Reading</h2>
  528. <p>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. </p>
  529. <p>You have seen how to define your own data classes, as well as:</p>
  530. <ul>
  531. <li>How to add default values to the fields in your data class</li>
  532. <li>How to customize the ordering of data class objects</li>
  533. <li>How to work with immutable data classes</li>
  534. <li>How inheritance works for data classes</li>
  535. </ul>
  536. <p>If you want to dive into all the details of data classes, have a look at <a href="https://www.python.org/dev/peps/pep-0557/">PEP 557</a> as well as the discussions in the original <a href="https://github.com/ericvsmith/dataclasses/issues?utf8=%E2%9C%93&amp;q=">GitHub repo</a>.</p>
  537. <p>In addition, Raymond Hettinger’s PyCon 2018 talk <a href="https://www.youtube.com/watch?v=T-TwcmT6Rcw">Dataclasses: The code generator to end all code generators</a> is well worth watching.</p>
  538. <p>If you do not yet have Python 3.7, there is also a <a href="https://github.com/ericvsmith/dataclasses">data classes backport for Python 3.6</a>. And now, go forth and write less code!</p>