{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# EMBL Python User Group\n", "## 2019-04-30 `dataclasses`\n", "\n", "__Toby Hodges__ ([toby.hodges@embl.de](mailto:toby.hodges@embl.de))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Material largely based on [this tutorial blogpost from RealPython](https://realpython.com/python-data-classes/)." ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [], "source": [ "from dataclasses import dataclass" ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [], "source": [ "@dataclass\n", "class Location:\n", " name: str\n", " lon: float\n", " lat: float" ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [], "source": [ "here = Location(name=\"Heidelberg\", lat=49.398750, lon=8.672434)" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [], "source": [ "here = Location(name=\"Heidelberg\", lat=49.398750, lon=8.672434)" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "__main__.Location" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(here)" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Location(name='Heidelberg', lon=8.672434, lat=49.39875)\n" ] } ], "source": [ "print(here)" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [], "source": [ "class NormalLocation:\n", " def __init__(self, name, lat, lon):\n", " self.name = name\n", " self.lat = lat\n", " self.lon = lon" ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [], "source": [ "there = NormalLocation(name=\"Redwood City\", lat=37.484779, lon=-122.228149)" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "__main__.NormalLocation" ] }, "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ "type(there)" ] }, { "cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<__main__.NormalLocation object at 0x112dc6eb8>\n" ] } ], "source": [ "print(there)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "__Note__: think decorators are weird? No worries - you can also import the `dataclasses.make_dataclass` function and create your dataclass with that instead of using the `@dataclass`+`class` approach." ] }, { "cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 63, "metadata": {}, "output_type": "execute_result" } ], "source": [ "there == normalLocation(name=\"Redwood City\", lat=37.484779, lon=-122.228149)" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "here == Location(name=\"Heidelberg\", lat=49.398750, lon=8.672434)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Type Annotations" ] }, { "cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [], "source": [ "@dataclass\n", "class Location:\n", " name: str\n", " lon: float\n", " lat: float" ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [], "source": [ "from typing import Any\n", "@dataclass\n", "class permissiveLocation:\n", " name: str\n", " lon: Any\n", " lat: Any" ] }, { "cell_type": "code", "execution_count": 67, "metadata": {}, "outputs": [], "source": [ "nonsense = permissiveLocation(name=\"Alderaan\", lat=\"a long time ago\", lon=[\"a\", \"galaxy\", \"far\", \"far\", \"away\"])" ] }, { "cell_type": "code", "execution_count": 68, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "permissiveLocation(name='Alderaan', lon=['a', 'galaxy', 'far', 'far', 'away'], lat='a long time ago')\n" ] } ], "source": [ "print(nonsense)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Defaults" ] }, { "cell_type": "code", "execution_count": 81, "metadata": {}, "outputs": [], "source": [ "@dataclass\n", "class Location:\n", " name: str\n", " lon: float=0.0\n", " lat: float=0.0" ] }, { "cell_type": "code", "execution_count": 74, "metadata": {}, "outputs": [], "source": [ "def mulitplier(a, b, c, d):\n", " return a*b*c*d" ] }, { "cell_type": "code", "execution_count": 75, "metadata": {}, "outputs": [], "source": [ "def mulitplier(a, b, c, d=1):\n", " return a*b*c*d" ] }, { "cell_type": "code", "execution_count": 76, "metadata": {}, "outputs": [ { "ename": "SyntaxError", "evalue": "non-default argument follows default argument (<ipython-input-76-2de8e556e2da>, line 1)", "output_type": "error", "traceback": [ "\u001b[0;36m File \u001b[0;32m\"<ipython-input-76-2de8e556e2da>\"\u001b[0;36m, line \u001b[0;32m1\u001b[0m\n\u001b[0;31m def mulitplier(a=1, b, c, d):\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m non-default argument follows default argument\n" ] } ], "source": [ "def mulitplier(a=1, b, c, d):\n", " return a*b*c*d" ] }, { "cell_type": "code", "execution_count": 71, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Location(name='somewhere near the middle of the atlantic', lon=0.0, lat=0.0)\n" ] } ], "source": [ "default_loc = Location(\"somewhere near the middle of the atlantic\")\n", "print(default_loc)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Methods are defined in the same way as usual." ] }, { "cell_type": "code", "execution_count": 85, "metadata": {}, "outputs": [], "source": [ "from math import asin, cos, radians, sin, sqrt\n", "\n", "@dataclass\n", "class Location:\n", " name: str\n", " lon: float=0.0\n", " lat: float=0.0\n", " def distance_to(self, other):\n", " r = 6371 # Earth radius in kilometers\n", " lam_1, lam_2 = radians(self.lon), radians(other.lon)\n", " phi_1, phi_2 = radians(self.lat), radians(other.lat)\n", " h = (sin((phi_2 - phi_1) / 2)**2\n", " + cos(phi_1) * cos(phi_2) * sin((lam_2 - lam_1) / 2)**2)\n", " return 2 * r * asin(sqrt(h))" ] }, { "cell_type": "code", "execution_count": 86, "metadata": {}, "outputs": [], "source": [ "here = Location(name=\"Heidelberg\", lat=49.398750, lon=8.672434)\n", "there = Location(name=\"Redwood City\", lat=37.484779, lon=-122.228149)" ] }, { "cell_type": "code", "execution_count": 87, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "9215.974289879121" ] }, "execution_count": 87, "metadata": {}, "output_type": "execute_result" } ], "source": [ "here.distance_to(there)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Inheritance" ] }, { "cell_type": "code", "execution_count": 83, "metadata": {}, "outputs": [], "source": [ "@dataclass\n", "class Location:\n", " name: str\n", " lon: float=0.0\n", " lat: float=0.0\n", "\n", "@dataclass\n", "class Capital(Location):\n", " country: str=\"undefined\"" ] }, { "cell_type": "code", "execution_count": 84, "metadata": {}, "outputs": [], "source": [ "capital_of_germany = Capital(\"berlin\", lat=52.520008, lon=13.404954, country=\"Germany\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Fields" ] }, { "cell_type": "code", "execution_count": 88, "metadata": {}, "outputs": [], "source": [ "from typing import List\n", "\n", "@dataclass\n", "class PlayingCard:\n", " rank: str\n", " suit: str\n", "\n", "@dataclass\n", "class Deck:\n", " cards: List[PlayingCard]\n", "\n", "RANKS = '2 3 4 5 6 7 8 9 10 J Q K A'.split()\n", "SUITS = '♣ ♢ ♡ ♠'.split()\n", "def make_french_deck():\n", " return [PlayingCard(r, s) for s in SUITS for r in RANKS]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "(See [here](https://en.wikipedia.org/wiki/Unicode_input) for more about using Unicode input...)" ] }, { "cell_type": "code", "execution_count": 89, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[PlayingCard(rank='2', suit='♣'),\n", " PlayingCard(rank='3', suit='♣'),\n", " PlayingCard(rank='4', suit='♣'),\n", " PlayingCard(rank='5', suit='♣'),\n", " PlayingCard(rank='6', suit='♣'),\n", " PlayingCard(rank='7', suit='♣'),\n", " PlayingCard(rank='8', suit='♣'),\n", " PlayingCard(rank='9', suit='♣'),\n", " PlayingCard(rank='10', suit='♣'),\n", " PlayingCard(rank='J', suit='♣'),\n", " PlayingCard(rank='Q', suit='♣'),\n", " PlayingCard(rank='K', suit='♣'),\n", " PlayingCard(rank='A', suit='♣'),\n", " PlayingCard(rank='2', suit='♢'),\n", " PlayingCard(rank='3', suit='♢'),\n", " PlayingCard(rank='4', suit='♢'),\n", " PlayingCard(rank='5', suit='♢'),\n", " PlayingCard(rank='6', suit='♢'),\n", " PlayingCard(rank='7', suit='♢'),\n", " PlayingCard(rank='8', suit='♢'),\n", " PlayingCard(rank='9', suit='♢'),\n", " PlayingCard(rank='10', suit='♢'),\n", " PlayingCard(rank='J', suit='♢'),\n", " PlayingCard(rank='Q', suit='♢'),\n", " PlayingCard(rank='K', suit='♢'),\n", " PlayingCard(rank='A', suit='♢'),\n", " PlayingCard(rank='2', suit='♡'),\n", " PlayingCard(rank='3', suit='♡'),\n", " PlayingCard(rank='4', suit='♡'),\n", " PlayingCard(rank='5', suit='♡'),\n", " PlayingCard(rank='6', suit='♡'),\n", " PlayingCard(rank='7', suit='♡'),\n", " PlayingCard(rank='8', suit='♡'),\n", " PlayingCard(rank='9', suit='♡'),\n", " PlayingCard(rank='10', suit='♡'),\n", " PlayingCard(rank='J', suit='♡'),\n", " PlayingCard(rank='Q', suit='♡'),\n", " PlayingCard(rank='K', suit='♡'),\n", " PlayingCard(rank='A', suit='♡'),\n", " PlayingCard(rank='2', suit='♠'),\n", " PlayingCard(rank='3', suit='♠'),\n", " PlayingCard(rank='4', suit='♠'),\n", " PlayingCard(rank='5', suit='♠'),\n", " PlayingCard(rank='6', suit='♠'),\n", " PlayingCard(rank='7', suit='♠'),\n", " PlayingCard(rank='8', suit='♠'),\n", " PlayingCard(rank='9', suit='♠'),\n", " PlayingCard(rank='10', suit='♠'),\n", " PlayingCard(rank='J', suit='♠'),\n", " PlayingCard(rank='Q', suit='♠'),\n", " PlayingCard(rank='K', suit='♠'),\n", " PlayingCard(rank='A', suit='♠')]" ] }, "execution_count": 89, "metadata": {}, "output_type": "execute_result" } ], "source": [ "make_french_deck()" ] }, { "cell_type": "code", "execution_count": 90, "metadata": {}, "outputs": [ { "ename": "ValueError", "evalue": "mutable default <class 'list'> for field cards is not allowed: use default_factory", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m<ipython-input-90-c0e6c8958259>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;34m@\u001b[0m\u001b[0mdataclass\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;32mclass\u001b[0m \u001b[0mDeck\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mcards\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mList\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mPlayingCard\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmake_french_deck\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# here be dragons!\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/anaconda2/envs/Py37/lib/python3.7/dataclasses.py\u001b[0m in \u001b[0;36mdataclass\u001b[0;34m(_cls, init, repr, eq, order, unsafe_hash, frozen)\u001b[0m\n\u001b[1;32m 989\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 990\u001b[0m \u001b[0;31m# We're called as @dataclass without parens.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 991\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mwrap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_cls\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 992\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 993\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/anaconda2/envs/Py37/lib/python3.7/dataclasses.py\u001b[0m in \u001b[0;36mwrap\u001b[0;34m(cls)\u001b[0m\n\u001b[1;32m 981\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 982\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mwrap\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 983\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0m_process_class\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcls\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mrepr\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0meq\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0morder\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0munsafe_hash\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfrozen\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 984\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 985\u001b[0m \u001b[0;31m# See if we're being called as @dataclass or @dataclass().\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/anaconda2/envs/Py37/lib/python3.7/dataclasses.py\u001b[0m in \u001b[0;36m_process_class\u001b[0;34m(cls, init, repr, eq, order, unsafe_hash, frozen)\u001b[0m\n\u001b[1;32m 832\u001b[0m \u001b[0;31m# we can.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 833\u001b[0m cls_fields = [_get_field(cls, name, type)\n\u001b[0;32m--> 834\u001b[0;31m for name, type in cls_annotations.items()]\n\u001b[0m\u001b[1;32m 835\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mf\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mcls_fields\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 836\u001b[0m \u001b[0mfields\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/anaconda2/envs/Py37/lib/python3.7/dataclasses.py\u001b[0m in \u001b[0;36m<listcomp>\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 832\u001b[0m \u001b[0;31m# we can.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 833\u001b[0m cls_fields = [_get_field(cls, name, type)\n\u001b[0;32m--> 834\u001b[0;31m for name, type in cls_annotations.items()]\n\u001b[0m\u001b[1;32m 835\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mf\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mcls_fields\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 836\u001b[0m \u001b[0mfields\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/anaconda2/envs/Py37/lib/python3.7/dataclasses.py\u001b[0m in \u001b[0;36m_get_field\u001b[0;34m(cls, a_name, a_type)\u001b[0m\n\u001b[1;32m 725\u001b[0m \u001b[0;31m# For real fields, disallow mutable defaults for known types.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 726\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_field_type\u001b[0m \u001b[0;32mis\u001b[0m \u001b[0m_FIELD\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mf\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdefault\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mlist\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mset\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 727\u001b[0;31m raise ValueError(f'mutable default {type(f.default)} for field '\n\u001b[0m\u001b[1;32m 728\u001b[0m f'{f.name} is not allowed: use default_factory')\n\u001b[1;32m 729\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mValueError\u001b[0m: mutable default <class 'list'> for field cards is not allowed: use default_factory" ] } ], "source": [ "@dataclass(frozen=True)\n", "class Deck:\n", " cards: List[PlayingCard] = make_french_deck() # here be dragons!" ] }, { "cell_type": "code", "execution_count": 91, "metadata": {}, "outputs": [], "source": [ "from dataclasses import field\n", "@dataclass\n", "class Deck:\n", " cards: List[PlayingCard] = field(default_factory=make_french_deck)" ] }, { "cell_type": "code", "execution_count": 92, "metadata": {}, "outputs": [], "source": [ "deck = Deck()" ] }, { "cell_type": "code", "execution_count": 93, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[PlayingCard(rank='2', suit='♣'), PlayingCard(rank='3', suit='♣'), PlayingCard(rank='4', suit='♣'), PlayingCard(rank='5', suit='♣'), PlayingCard(rank='6', suit='♣'), PlayingCard(rank='7', suit='♣'), PlayingCard(rank='8', suit='♣'), PlayingCard(rank='9', suit='♣'), PlayingCard(rank='10', suit='♣'), PlayingCard(rank='J', suit='♣'), PlayingCard(rank='Q', suit='♣'), PlayingCard(rank='K', suit='♣'), PlayingCard(rank='A', suit='♣'), PlayingCard(rank='2', suit='♢'), PlayingCard(rank='3', suit='♢'), PlayingCard(rank='4', suit='♢'), PlayingCard(rank='5', suit='♢'), PlayingCard(rank='6', suit='♢'), PlayingCard(rank='7', suit='♢'), PlayingCard(rank='8', suit='♢'), PlayingCard(rank='9', suit='♢'), PlayingCard(rank='10', suit='♢'), PlayingCard(rank='J', suit='♢'), PlayingCard(rank='Q', suit='♢'), PlayingCard(rank='K', suit='♢'), PlayingCard(rank='A', suit='♢'), PlayingCard(rank='2', suit='♡'), PlayingCard(rank='3', suit='♡'), PlayingCard(rank='4', suit='♡'), PlayingCard(rank='5', suit='♡'), PlayingCard(rank='6', suit='♡'), PlayingCard(rank='7', suit='♡'), PlayingCard(rank='8', suit='♡'), PlayingCard(rank='9', suit='♡'), PlayingCard(rank='10', suit='♡'), PlayingCard(rank='J', suit='♡'), PlayingCard(rank='Q', suit='♡'), PlayingCard(rank='K', suit='♡'), PlayingCard(rank='A', suit='♡'), PlayingCard(rank='2', suit='♠'), PlayingCard(rank='3', suit='♠'), PlayingCard(rank='4', suit='♠'), PlayingCard(rank='5', suit='♠'), PlayingCard(rank='6', suit='♠'), PlayingCard(rank='7', suit='♠'), PlayingCard(rank='8', suit='♠'), PlayingCard(rank='9', suit='♠'), PlayingCard(rank='10', suit='♠'), PlayingCard(rank='J', suit='♠'), PlayingCard(rank='Q', suit='♠'), PlayingCard(rank='K', suit='♠'), PlayingCard(rank='A', suit='♠')]\n" ] } ], "source": [ "print(deck.cards)" ] }, { "cell_type": "code", "execution_count": 94, "metadata": {}, "outputs": [ { "ename": "ModuleNotFoundError", "evalue": "No module named 'attrs'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m<ipython-input-94-680b1dd3aa84>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mimport\u001b[0m \u001b[0mattrs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'attrs'" ] } ], "source": [ "class Location:\n", " __slots__(name, lon, lat)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.3" } }, "nbformat": 4, "nbformat_minor": 2 }