From bfdb76b448b2fec2079f90d1f153f77b68dd777e Mon Sep 17 00:00:00 2001
From: stamper <tbyhdgs@gmail.com>
Date: Tue, 7 May 2019 10:53:19 +0200
Subject: [PATCH] add material from dataclasses session.

---
 .gitignore                                    |   1 +
 .../dataclasses-complete.ipynb                | 563 +++++++++++++++
 .../2019-04-30-dataclasses/dataclasses.ipynb  | 657 ++++++++++++++++++
 meetings/{ => old}/ete.rst                    |   0
 meetings/{ => old}/schedule.txt               |   0
 5 files changed, 1221 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 meetings/2019/2019-04-30-dataclasses/dataclasses-complete.ipynb
 create mode 100644 meetings/2019/2019-04-30-dataclasses/dataclasses.ipynb
 rename meetings/{ => old}/ete.rst (100%)
 rename meetings/{ => old}/schedule.txt (100%)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b64427c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*ipynb_checkpoints*
diff --git a/meetings/2019/2019-04-30-dataclasses/dataclasses-complete.ipynb b/meetings/2019/2019-04-30-dataclasses/dataclasses-complete.ipynb
new file mode 100644
index 0000000..310069a
--- /dev/null
+++ b/meetings/2019/2019-04-30-dataclasses/dataclasses-complete.ipynb
@@ -0,0 +1,563 @@
+{
+ "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": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from dataclasses import dataclass"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@dataclass\n",
+    "class Location:\n",
+    "    name: str\n",
+    "    lon: float\n",
+    "    lat: float"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "here = Location(name=\"Heidelberg\", lat=49.398750, lon=8.672434)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "__main__.Location"
+      ]
+     },
+     "execution_count": 4,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "type(here)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "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": 7,
+   "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": 9,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "there = normalLocation(name=\"Redwood City\", lat=37.484779, lon=-122.228149)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "__main__.normalLocation"
+      ]
+     },
+     "execution_count": 11,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "type(there)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "<__main__.normalLocation object at 0x112953ef0>\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": 15,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "False"
+      ]
+     },
+     "execution_count": 15,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "there == normalLocation(name=\"Redwood City\", lat=37.484779, lon=-122.228149)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "True"
+      ]
+     },
+     "execution_count": 17,
+     "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": 20,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@dataclass\n",
+    "class Location:\n",
+    "    name: str\n",
+    "    lon: float\n",
+    "    lat: float"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "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": 22,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "nonsense = permissiveLocation(name=\"Alderaan\", lat=\"a long time ago\", lon=[\"a\", \"galaxy\", \"far\", \"far\", \"away\"])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 23,
+   "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": 24,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@dataclass\n",
+    "class Location:\n",
+    "    name: str\n",
+    "    lon: float=0.0\n",
+    "    lat: float=0.0"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Methods are defined in the same way as usual."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 28,
+   "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": 29,
+   "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": 30,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "9215.974289879121"
+      ]
+     },
+     "execution_count": 30,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "here.distance_to(there)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Inheritance"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 31,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "@dataclass\n",
+    "class Capital(Location):\n",
+    "    country: str=\"undefined\""
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 32,
+   "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": 49,
+   "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": 40,
+   "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": 40,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "make_french_deck()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 50,
+   "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-50-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\n",
+    "class Deck:\n",
+    "    cards: List[PlayingCard] = make_french_deck() # here be dragons!"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 46,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from dataclasses import field\n",
+    "@dataclass\n",
+    "class Deck:  # Will NOT work\n",
+    "    cards: List[PlayingCard] = field(default_factory=make_french_deck)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 47,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "deck = Deck()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 48,
+   "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)"
+   ]
+  }
+ ],
+ "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
+}
diff --git a/meetings/2019/2019-04-30-dataclasses/dataclasses.ipynb b/meetings/2019/2019-04-30-dataclasses/dataclasses.ipynb
new file mode 100644
index 0000000..714f760
--- /dev/null
+++ b/meetings/2019/2019-04-30-dataclasses/dataclasses.ipynb
@@ -0,0 +1,657 @@
+{
+ "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
+}
diff --git a/meetings/ete.rst b/meetings/old/ete.rst
similarity index 100%
rename from meetings/ete.rst
rename to meetings/old/ete.rst
diff --git a/meetings/schedule.txt b/meetings/old/schedule.txt
similarity index 100%
rename from meetings/schedule.txt
rename to meetings/old/schedule.txt
-- 
GitLab