diff --git a/chapters/chap01.ipynb b/chapters/chap01.ipynb index bd3875d..8b4f5a4 100644 --- a/chapters/chap01.ipynb +++ b/chapters/chap01.ipynb @@ -1,15 +1,5 @@ { "cells": [ - { - "cell_type": "markdown", - "id": "1331faa1", - "metadata": {}, - "source": [ - "You can order print and ebook versions of *Think Python 3e* from\n", - "[Bookshop.org](https://bookshop.org/a/98697/9781098155438) and\n", - "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)." - ] - }, { "cell_type": "markdown", "id": "a14edb7e", @@ -100,10 +90,21 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "id": "2568ec84", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "30 + 12" ] @@ -118,10 +119,21 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "c4e75456", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "43 - 1" ] @@ -476,10 +488,21 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 3, "id": "bd8ae45f", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "'Hello'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "'Hello'" ] @@ -512,10 +535,21 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 4, "id": "0295acab", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "\"it's a small \"" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "\"it's a small \"" ] @@ -548,10 +582,21 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 5, "id": "aefe6af1", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "\"Well, it's a small world.\"" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "'Well, ' + \"it's a small \" + 'world.'" ] @@ -586,10 +631,21 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 6, "id": "a5e837db", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "4" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "len('Spam')" ] @@ -607,12 +663,20 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 7, "id": "e3f65f19", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "UsageError: Cell magic `%%expect` not found.\n" + ] + } + ], "source": [ "%%expect SyntaxError\n", "\n", @@ -1092,20 +1156,42 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": 21, "id": "5d358f37", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "42" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "round(42.5)" ] }, { "cell_type": "code", - "execution_count": 42, + "execution_count": 10, "id": "12aa59a3", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "44" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "round(43.5)" ] @@ -1136,6 +1222,14 @@ "3. If you call a function like `round(42.5)`, what happens if you leave out one or both parentheses?" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "e3282418", + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "markdown", "id": "1fb0adfe", @@ -1164,6 +1258,27 @@ "* `type`" ] }, + { + "cell_type": "code", + "execution_count": 26, + "id": "f8cf3c24", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "float" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "type(abs(-7.1))" + ] + }, { "cell_type": "markdown", "id": "23762eec", @@ -1285,7 +1400,7 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -1299,7 +1414,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.11" + "version": "3.12.10" } }, "nbformat": 4, diff --git a/chapters/chap03.ipynb b/chapters/chap03.ipynb index 5f2cb1d..39cd490 100644 --- a/chapters/chap03.ipynb +++ b/chapters/chap03.ipynb @@ -12,7 +12,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 27, "id": "103cbe3c", "metadata": { "tags": [] @@ -64,14 +64,27 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 28, "id": "d28f5c1a", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "def print_lyrics():\n", " print(\"I'm a lumberjack, and I'm okay.\")\n", - " print(\"I sleep all night and I work all day.\")" + " print(\"I sleep all night and I work all day.\")\n", + "\n", + "print_lyrics" ] }, { @@ -94,10 +107,21 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 29, "id": "2850a402", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "print_lyrics" ] @@ -115,10 +139,19 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 30, "id": "9a048657", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "I'm a lumberjack, and I'm okay.\n", + "I sleep all night and I work all day.\n" + ] + } + ], "source": [ "print_lyrics()" ] @@ -146,7 +179,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 31, "id": "e5d00488", "metadata": {}, "outputs": [], @@ -168,10 +201,19 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 32, "id": "a3ad5f46", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dennis Moore, \n", + "Dennis Moore, \n" + ] + } + ], "source": [ "print_twice('Dennis Moore, ')" ] @@ -186,10 +228,19 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 33, "id": "042dfec1", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dennis Moore, \n", + "Dennis Moore, \n" + ] + } + ], "source": [ "string = 'Dennis Moore, '\n", "print(string)\n", @@ -206,10 +257,19 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 34, "id": "8f078ad0", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dennis Moore, \n", + "Dennis Moore, \n" + ] + } + ], "source": [ "line = 'Dennis Moore, '\n", "print_twice(line)" @@ -244,7 +304,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 35, "id": "e86bb32c", "metadata": {}, "outputs": [], @@ -263,10 +323,18 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 36, "id": "ec117999", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Spam, Spam, Spam, Spam, \n" + ] + } + ], "source": [ "spam = 'Spam, '\n", "repeat(spam, 4)" @@ -282,7 +350,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 8, "id": "3731ffd8", "metadata": {}, "outputs": [], @@ -302,10 +370,19 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 37, "id": "6792e63b", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Spam, Spam, Spam, Spam, \n", + "Spam, Spam, Spam, Spam, \n" + ] + } + ], "source": [ "first_two_lines()" ] @@ -320,7 +397,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 38, "id": "2dcb020a", "metadata": {}, "outputs": [], @@ -333,10 +410,20 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 39, "id": "9ff8c60e", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Spam, Spam, \n", + "(Lovely Spam, Wonderful Spam!)\n", + "Spam, Spam, \n" + ] + } + ], "source": [ "last_three_lines()" ] @@ -351,7 +438,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 40, "id": "78bf3a7b", "metadata": {}, "outputs": [], @@ -363,10 +450,22 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 41, "id": "ba5da431", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Spam, Spam, Spam, Spam, \n", + "Spam, Spam, Spam, Spam, \n", + "Spam, Spam, \n", + "(Lovely Spam, Wonderful Spam!)\n", + "Spam, Spam, \n" + ] + } + ], "source": [ "print_verse()" ] @@ -395,10 +494,19 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 14, "id": "29b7eff3", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0\n", + "1\n" + ] + } + ], "source": [ "for i in range(2):\n", " print(i)" @@ -427,10 +535,31 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 42, "id": "038ad592", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Verse 0\n", + "Spam, Spam, Spam, Spam, \n", + "Spam, Spam, Spam, Spam, \n", + "Spam, Spam, \n", + "(Lovely Spam, Wonderful Spam!)\n", + "Spam, Spam, \n", + "\n", + "Verse 1\n", + "Spam, Spam, Spam, Spam, \n", + "Spam, Spam, Spam, Spam, \n", + "Spam, Spam, \n", + "(Lovely Spam, Wonderful Spam!)\n", + "Spam, Spam, \n", + "\n" + ] + } + ], "source": [ "for i in range(2):\n", " print(\"Verse\", i)\n", @@ -449,7 +578,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 43, "id": "8887637a", "metadata": {}, "outputs": [], @@ -482,7 +611,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 44, "id": "0db8408e", "metadata": {}, "outputs": [], @@ -502,10 +631,19 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 45, "id": "1c556e48", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Always look on the bright side of life.\n", + "Always look on the bright side of life.\n" + ] + } + ], "source": [ "line1 = 'Always look on the '\n", "line2 = 'bright side of life.'\n", @@ -523,12 +661,21 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 46, "id": "73f03eea", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'cat' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31mNameError\u001b[39m\u001b[31m:\u001b[39m name 'cat' is not defined\n" + ] + } + ], "source": [ "%%expect NameError\n", "\n", @@ -564,7 +711,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 47, "id": "83df4e32", "metadata": { "tags": [] @@ -593,12 +740,23 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 48, "id": "bcd5e1df", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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o48+6pYtW+Y1j59++umo56jzjBkzElXPhJ793HPP2TOTkkceecRVrFjR3XPPPbHOhfd9eL0OHDjgGjRoYHPr1VdfPad1lJRkTLaSRYozcOBA16VLF5clSxaXVlmyZInLlSuX27Bhg9u2bZsrWrRoSlcpzbNjxw43btw416FDhxSrw+nTp91bb711zuV8//33bs6cOa558+aJui8pnu3L3r173cyZM93OnTtdhgwZvOu1ePFily1bNrdgwQKXmlCkkYb48ssvXcOGDV21atVc1apV3dy5c80rql27tnkjjRo1cps3b7Zru3XrZj85xrl9+/alWL0x9pMnT3YbN250Z86ciffavHnzxvg+ceJE165dO9eiRQs3adKkqPf06dPHvfbaa/b7Z5995nLmzBnqB0STZwNGkr6i7zAye/bsseNPPvlk6H7g3uuvv96dOnXKffrpp3Y9fVi5cmXr84SYMmWK3RM8Z9euXSFD+fzzz1s5fHr06OFOnjwZ6/5Vq1aZV7pu3bpY5xjHNm3auCpVqlgZGP9w7/KVV15x9erVs+hs8ODBUevH3KCNtOnuu+8OHf/kk0+i3ks/MQZ16tSx5/bt2zfOth85csTKpP7MPaKaIAq57bbb3L333mtlfPXVV65x48Zm8OG3335zd9xxh93Hz/bt28fw9qlDy5Yt7XyTJk3MC6cv+vXrF4r0gjkfTlzjF/nsO++808q+/fbbzbgH/P333+7FF1+0tlNGu3bt3MGDB73H/dChQ9ZuIv5atWq5IUOGxNl34fVCMF544QXrJ57Ld/r2scces7rwjK5du0adP9HWUVIi0UgjsEgIbV966SUzKitXrrSJ0717d7d06VL7/uCDD7qnnnrKrh86dKj9nD9/vp3Lnz9/itWdZ+MxUZcJEybEKx60JbzNixYtMsFo27atGf9o97GIiEiAxVWpUqXQd37WrVvXfh80aJCVv3r1ahPeAQMGhFJD48ePN6MO77zzjhmtjBkzupdfftn6kj7kvoTSULSNxY5nyfUYKlIO8O6775oQkH6gvK1bt8ZKSXFfz5493fTp012FChVilc+54sWLuy+++MKMC2mLNWvWhM7/8ccf1me0+8033wwJVji0hzKow9SpUxO8t2PHju6hhx6y4ytWrHDffPONmzVrVtT2Uy9EZe3atSYaGLYADCAGmGvol3CYt4wb940e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IxqffPKJ6969e4LXHTp0yA0cODBZ67Jnzx7373//21WtWtXdcMMNbtKkSS4tULp06Rjfz54968qUKeOaNm0a4/jy5ctd9erVXWogKerSqVMnN2LECHc+2L59uxs7dmysfl+/fv05lfv++++71q1be1//6KOPumXLliV4Xf/+/d3TTz8d9Rx1njFjRqLqmdCzn3vuOXtmUvLII4+4ihUrunvuuSfWufC+D6/XgQMHXIMGDWxuvfrqq+e0jtIaF8WfETl16pS7/fbb7eMrGs8880yy1ofJf9NNN7lff/3V3Xjjja5Zs2Yua9asLi2xZMkSlytXLrdhwwa3bds2V7Ro0ZSuUppnx44dbty4ca5Dhw4pVofTp0+7t95665zL+f77792cOXNc8+bNE3VfUjzbl71797qZM2e6nTt3ugwZMnjXa/HixS5btmxuwYIF7mIjTUca6dKlc88//7wrX768u/baa82bCj/34osvmgfRq1cvN378eHfnnXeGjB1q36VLF1euXDlXqlQp99VXX4W8Sv6OFBEARj05KFKkSKjsv/76y6VPn94+KQHGfvLkyW7jxo3uzJkz8V6bN2/eGN8nTpzo2rVr51q0aBFntNSnTx/32muv2e+fffaZy5kzp9u8ebN9p/95NmAka9eubdEXRoZoDJ588snQ/cC9119/vQnvp59+atfj7VWuXNnNnTs3wfZOmTLF7gmes2vXrpChZC5RDp8ePXq4kydPxrp/1apVNqfWrVsX69y+fftcmzZtXJUqVawMjH8A8+2VV15x9erVs+hs8ODBUevXrVs3ayNtuvvuu2NEytHupZ8Ygzp16thz+/btG2fbmdeUSf0bNWpkUQ2wbm677TZ37733WhmshcaNG5vBh99++83dcccddh8/27dvH8Pbpw4tW7a0802aNDEvnL7o169fKNKjXZHENX6Rz2bdUjZOH8Y94O+//7Y1Ttspg344ePCg97jjINLuP//809WqVcsNGTIkzr4Lr9fixYvdCy+8YP3Ec/lO3z722GNWF57RtWvXqPMn2jpKa6Rp0QjE4ZtvvnHz58+3QcMIBuA5rF27Nmr4+N///tcm2XfffWf34fnDqFGj7A+EffvttyEhSS5YWCxiFmCWLFlcSpA/f37zmOi/CRMmxCseS5cuDf2OYVi0aJEJRtu2bc34R7uPRYRIA4urUqVKoe/8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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from diagram import diagram, adjust\n", "\n", @@ -638,7 +796,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 49, "id": "886519cf", "metadata": {}, "outputs": [], @@ -658,12 +816,20 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 50, "id": "1fe8ee82", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exception reporting mode: Verbose\n" + ] + } + ], "source": [ "# This cell tells Jupyter to provide detailed debugging information\n", "# when a runtime error occurs, including a traceback.\n", @@ -673,12 +839,26 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 51, "id": "d9082f88", "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'cat' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[51]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mcat_twice\u001b[49m\u001b[43m(\u001b[49m\u001b[43mline1\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mline2\u001b[49m\u001b[43m)\u001b[49m\n line1 \u001b[34m= \u001b[39m\u001b[34m'Always look on the '\u001b[39m\n line2 \u001b[34m= \u001b[39m\u001b[34m'bright side of life.'\u001b[39m", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[44]\u001b[39m\u001b[32m, line 3\u001b[39m, in \u001b[36mcat_twice\u001b[39m\u001b[34m(part1='Always look on the ', part2='bright side of life.')\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mcat_twice\u001b[39m(part1, part2):\n\u001b[32m 2\u001b[39m cat = part1 + part2\n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m \u001b[43mprint_twice\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcat\u001b[49m\u001b[43m)\u001b[49m\n cat \u001b[34m= \u001b[39m\u001b[34m'Always look on the bright side of life.'\u001b[39m", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[49]\u001b[39m\u001b[32m, line 2\u001b[39m, in \u001b[36mprint_twice\u001b[39m\u001b[34m(string='Always look on the bright side of life.')\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mprint_twice\u001b[39m(string):\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[43mcat\u001b[49m) \u001b[38;5;66;03m# NameError\u001b[39;00m\n\u001b[32m 3\u001b[39m \u001b[38;5;28mprint\u001b[39m(cat)\n", + "\u001b[31mNameError\u001b[39m: name 'cat' is not defined" + ] + } + ], "source": [ "%%expect NameError\n", "\n", @@ -1068,7 +1248,7 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -1082,7 +1262,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.11" + "version": "3.12.10" } }, "nbformat": 4, diff --git a/chapters/chap03_stack_diagram.png b/chapters/chap03_stack_diagram.png new file mode 100644 index 0000000..525b0d9 Binary files /dev/null and b/chapters/chap03_stack_diagram.png differ diff --git a/chapters/diagram.py b/chapters/diagram.py new file mode 100644 index 0000000..b33f97d --- /dev/null +++ b/chapters/diagram.py @@ -0,0 +1,386 @@ +import matplotlib.pyplot as plt +import matplotlib.patches as patches + +from matplotlib.transforms import Bbox, TransformedBbox + +# TODO: Study this https://matplotlib.org/stable/tutorials/text/annotations.html#sphx-glr-tutorials-text-annotations-py + + +def override(d1, **d2): + """Add key-value pairs to d. + + d1: dictionary + d2: keyword args to add to d + + returns: new dict + """ + d = d1.copy() + d.update(d2) + return d + +def underride(d1, **d2): + """Add key-value pairs to d only if key is not in d. + + d1: dictionary + d2: keyword args to add to d + + returns: new dict + """ + d = d2.copy() + d.update(d1) + return d + +def diagram(width=5, height=1, **options): + fig, ax = plt.subplots(**options) + + # TODO: dpi in the notebook should be 100, in the book it should be 300 or 600 + # fig.set_dpi(100) + + # Set figure size + fig.set_size_inches(width, height) + + plt.rc('font', size=8) + + # Set axes position + ax.set_position([0, 0, 1, 1]) + + # Set x and y limits + ax.set_xlim(0, width) + ax.set_ylim(0, height) + + # Remove the spines, ticks, and labels + despine(ax) + return ax + +def despine(ax): + # Remove the spines + ax.spines['right'].set_visible(False) + ax.spines['top'].set_visible(False) + ax.spines['bottom'].set_visible(False) + ax.spines['left'].set_visible(False) + + # Remove the axis labels + ax.set_xticklabels([]) + ax.set_yticklabels([]) + + # Remove the tick marks + ax.tick_params(axis='both', which='both', length=0, width=0) + +def adjust(x, y, bbox): + """Adjust the coordinates of a point based on a bounding box. + + x: x coordinate + y: y coordinate + bbox: Bbox object + + returns: tuple of coordinates + """ + width = bbox.width + height = bbox.height + 0.2 + t = width, height, x - bbox.x0, y - bbox.y0 + 0.1 + return [round(x, 2) for x in t] + +def get_bbox(ax, handle): + bbox = handle.get_window_extent() + transformed = TransformedBbox(bbox, ax.transData.inverted()) + return transformed + +def draw_bbox(ax, bbox, **options): + options = underride(options, facecolor='gray', alpha=0.1, linewidth=0) + rect = patches.Rectangle((bbox.xmin, bbox.ymin), bbox.width, bbox.height, **options) + handle = ax.add_patch(rect) + bbox = get_bbox(ax, handle) + return bbox + +def draw_box_around(ax, bboxes, **options): + bbox = Bbox.union(bboxes) + return draw_bbox(ax, padded(bbox), **options) + +def padded(bbox, dx=0.1, dy=0.1): + """Add padding to a bounding box. + """ + [x0, y0], [x1, y1] = bbox.get_points() + return Bbox([[x0-dx, y0-dy], [x1+dx, y1+dy]]) + +def make_binding(name, value, **options): + """Make a binding between a name and a value. + + name: string + value: any type + + returns: Binding object + """ + if not isinstance(value, Frame): + value = Value(repr(value)) + + return Binding(Value(name), value, **options) + +def make_mapping(key, value, **options): + """Make a binding between a key and a value. + + key: any type + value: any type + + returns: Binding object + """ + return Binding(Value(repr(key)), Value(repr(value)), **options) + +def make_dict(d, name='dict', **options): + """Make a Frame that represents a dictionary. + + d: dictionary + name: string + options: passed to Frame + """ + mappings = [make_mapping(key, value) for key, value in d.items()] + return Frame(mappings, name=name, **options) + +def make_frame(d, name='frame', **options): + """Make a Frame that represents a stack frame. + + d: dictionary + name: string + options: passed to Frame + """ + bindings = [make_binding(key, value) for key, value in d.items()] + return Frame(bindings, name=name, **options) + +class Binding(object): + def __init__(self, name, value=None, **options): + """ Represents a binding between a name and a value. + + name: Value object + value: Value object + """ + self.name = name + self.value = value + self.options = options + + def draw(self, ax, x, y, **options): + options = override(self.options, **options) + dx = options.pop('dx', 0.4) + dy = options.pop('dy', 0) + draw_value = options.pop('draw_value', True) + + bbox1 = self.name.draw(ax, x, y, ha='right') + bboxes = [bbox1] + + arrow = Arrow(dx=dx, dy=dy, **options) + bbox2 = arrow.draw(ax, x, y) + + if draw_value: + bbox3 = self.value.draw(ax, x+dx, y+dy) + # only include the arrow if we drew the value + bboxes.extend([bbox2, bbox3]) + + bbox = Bbox.union(bboxes) + # draw_bbox(ax, self.bbox) + self.bbox = bbox + return bbox + + +class Element(object): + def __init__(self, index, value, **options): + """ Represents a an element of a list. + + index: integer + value: Value object + """ + self.index = index + self.value = value + self.options = options + + def draw(self, ax, x, y, dx=0.15, **options): + options = override(self.options, **options) + draw_value = options.pop('draw_value', True) + + bbox1 = self.index.draw(ax, x, y, ha='right', fontsize=6, color='gray') + bboxes = [bbox1] + + if draw_value: + bbox2 = self.value.draw(ax, x+dx, y) + bboxes.append(bbox2) + + bbox = Bbox.union(bboxes) + self.bbox = bbox + # draw_bbox(ax, self.bbox) + return bbox + + +class Value(object): + def __init__(self, value): + self.value = value + self.options = dict(ha='left', va='center') + self.bbox = None + + def draw(self, ax, x, y, **options): + options = override(self.options, **options) + + handle = ax.text(x, y, self.value, **options) + bbox = self.bbox = get_bbox(ax, handle) + # draw_bbox(ax, bbox) + self.bbox = bbox + return bbox + + +class Arrow(object): + def __init__(self, **options): + # Note for the future about dotted arrows + # self.arrowprops = dict(arrowstyle="->", ls=':') + arrowprops = dict(arrowstyle="->", color='gray') + options = underride(options, arrowprops=arrowprops) + self.options = options + + def draw(self, ax, x, y, **options): + options = override(self.options, **options) + dx = options.pop('dx', 0.5) + dy = options.pop('dy', 0) + shim = options.pop('shim', 0.02) + + handle = ax.annotate("", [x+dx, y+dy], [x+shim, y], **options) + bbox = get_bbox(ax, handle) + self.bbox = bbox + return bbox + + +class ReturnArrow(object): + def __init__(self, **options): + style = "Simple, tail_width=0.5, head_width=4, head_length=8" + options = underride(options, arrowstyle=style, color="gray") + self.options = options + + def draw(self, ax, x, y, **options): + options = override(self.options, **options) + value = options.pop('value', None) + dx = options.pop('dx', 0) + dy = options.pop('dy', 0.4) + shim = options.pop('shim', 0.02) + + x += shim + arrow = patches.FancyArrowPatch((x, y), (x+dx, y+dy), + connectionstyle="arc3,rad=.6", **options) + handle = ax.add_patch(arrow) + bbox = get_bbox(ax, handle) + + if value is not None: + handle = plt.text(x+0.15, y+dy/2, str(value), ha='left', va='center') + bbox2 = get_bbox(ax, handle) + bbox = Bbox.union([bbox, bbox2]) + + self.bbox = bbox + return bbox + + +class Frame(object): + def __init__(self, bindings, **options): + self.bindings = bindings + self.options = options + + def draw(self, ax, x, y, **options): + options = override(self.options, **options) + name = options.pop('name', '') + value = options.pop('value', None) + dx = options.pop('dx', 0) + dy = options.pop('dy', 0) + offsetx = options.pop('offsetx', 0) + offsety = options.pop('offsety', 0) + shim = options.pop('shim', 0) + loc = options.pop('loc', 'top') + box_around = options.pop('box_around', None) + + x += offsetx + y += offsety + save_y = y + + if len(self.bindings) == 0: + bbox = Bbox([[x, y], [x, y]]) + bboxes = [bbox] + else: + bboxes = [] + + # draw the bindings + for binding in self.bindings: + bbox = binding.draw(ax, x, y) + bboxes.append(bbox) + x += dx + y += dy + + if box_around: + bbox1 = draw_bbox(ax, box_around, **options) + else: + bbox1 = draw_box_around(ax, bboxes, **options) + bboxes.append(bbox1) + + if value is not None: + arrow = ReturnArrow(value=value) + x = bbox1.xmax + shim + bbox2 = arrow.draw(ax, x, save_y, value=value) + bboxes.append(bbox2) + + if name: + if loc == 'top': + x = bbox1.xmin + y = bbox1.ymax + 0.02 + handle = plt.text(x, y, name, ha='left', va='bottom') + elif loc == 'left': + x = bbox1.xmin - 0.1 + y = save_y + handle = plt.text(x, y, name, ha='right', va='center') + bbox3 = get_bbox(ax, handle) + bboxes.append(bbox3) + + bbox = Bbox.union(bboxes) + self.bbox = bbox + return bbox + + +class Stack(object): + def __init__(self, frames, **options): + self.frames = frames + self.options = options + + def draw(self, ax, x, y, **options): + options = override(self.options, **options) + dx = options.pop('dx', 0) + dy = options.pop('dy', -0.4) + + # draw the frames + bboxes = [] + for frame in self.frames: + bbox = frame.draw(ax, x, y) + bboxes.append(bbox) + x += dx + y += dy + + bbox = Bbox.union(bboxes) + self.bbox = bbox + return bbox + +def make_rebind(name, seq): + bindings = [] + for i, value in enumerate(seq): + dy = dy=-0.3*i + if i == len(seq)-1: + binding = make_binding(name, value, dy=dy) + else: + arrowprops = dict(arrowstyle="->", color='gray', ls=':') + binding = make_binding('', value, dy=dy, arrowprops=arrowprops) + bindings.append(binding) + + return bindings + +def make_element(index, value): + return Element(Value(index), Value(repr(value))) + +def make_list(seq, name='list', **options): + elements = [make_element(index, value) for index, value in enumerate(seq)] + return Frame(elements, name=name, **options) + +def draw_bindings(bindings, ax, x, y): + bboxes = [] + for binding in bindings: + bbox = binding.draw(ax, x, y) + bboxes.append(bbox) + + bbox = Bbox.union(bboxes) + return bbox \ No newline at end of file diff --git a/chapters/thinkpython.py b/chapters/thinkpython.py new file mode 100644 index 0000000..3f08885 --- /dev/null +++ b/chapters/thinkpython.py @@ -0,0 +1,93 @@ +import contextlib +import io +import re + + +def extract_function_name(text): + """Find a function definition and return its name. + + text: String + + returns: String or None + """ + pattern = r"def\s+(\w+)\s*\(" + match = re.search(pattern, text) + if match: + func_name = match.group(1) + return func_name + else: + return None + + +# the functions that define cell magic commands are only defined +# if we're running in Jupyter. + +try: + from IPython.core.magic import register_cell_magic + from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring + + @register_cell_magic + def add_method_to(args, cell): + + # get the name of the function defined in this cell + func_name = extract_function_name(cell) + if func_name is None: + return f"This cell doesn't define any new functions." + + # get the class we're adding it to + namespace = get_ipython().user_ns + class_name = args + cls = namespace.get(class_name, None) + if cls is None: + return f"Class '{class_name}' not found." + + # save the old version of the function if it was already defined + old_func = namespace.get(func_name, None) + if old_func is not None: + del namespace[func_name] + + # Execute the cell to define the function + get_ipython().run_cell(cell) + + # get the newly defined function + new_func = namespace.get(func_name, None) + if new_func is None: + return f"This cell didn't define {func_name}." + + # add the function to the class and remove it from the namespace + setattr(cls, func_name, new_func) + del namespace[func_name] + + # restore the old function to the namespace + if old_func is not None: + namespace[func_name] = old_func + + @register_cell_magic + def expect_error(line, cell): + try: + get_ipython().run_cell(cell) + except Exception as e: + get_ipython().run_cell("%tb") + + @magic_arguments() + @argument("exception", help="Type of exception to catch") + @register_cell_magic + def expect(line, cell): + args = parse_argstring(expect, line) + exception = eval(args.exception) + try: + get_ipython().run_cell(cell) + except exception as e: + get_ipython().run_cell("%tb") + + def traceback(mode): + """Set the traceback mode. + + mode: string + """ + with contextlib.redirect_stdout(io.StringIO()): + get_ipython().run_cell(f"%xmode {mode}") + + traceback("Minimal") +except (ImportError, NameError): + print("Warning: IPython is not available, cell magic not defined.")