{
 "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": [
    "![](images/Pug_and_Snake_small.jpeg)"
   ]
  },
  {
   "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
}