{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Introduction to Python Epiphanies " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Introduction" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", " - The target audience is intermediate Python users looking for a\n", " deeper understanding of the language. It attempts to correct some\n", " common misperceptions of how Python works. While similar to many\n", " other programming languages, Python is quite different from some\n", " in subtle and important ways.\n", " \n", " - Almost all of the material in the video is presented in the\n", " interactive Python prompt (aka the Read Eval Print Loop or REPL).\n", " I'll be using an IPython notebook but you can use Python without\n", " IPython just fine.\n", " \n", " - I'm using Python 3.4 and I suggest you do the same unless you're\n", " familiar with the differences between Python versions 2 and 3 and\n", " prefer to use Python 2.x.\n", " \n", " - There are some intentional code errors in both the regular\n", " presentation material and the exercises. The purpose of the\n", " intentional errors is to foster learning from how things fail.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 1 Objects " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1.1 Back to the Basics: Objects" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Let's go back to square one and be sure we understand the basics about objects in Python." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " ###### Objects can be created via literals." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "3.14" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "3.14j" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "'a string literal'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "b'a bytes literal'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "(1, 2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[1, 2]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "{'one': 1, 'two': 2}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "{'one', 'two'}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " ###### Some constants are created on startup and have names." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "False, True" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "None" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "NotImplemented, Ellipsis" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " ###### There are also some built-in types and functions." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "int, list" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "any, len" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Everything (*everything*) in Python (at runtime) is an object. \n", "\n", "Every object has:\n", "- a single *value*,\n", "- a single *type*,\n", "- some number of *attributes*,\n", "- one or more *base classes*,\n", "- a single unique *id*, and\n", "- (zero or) one or more *names*, in one or more namespaces.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Let's explore each of these in turn.\n", "\n", "###### Every object has a single type." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(3.14)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(3.14j)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type('a string literal')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(b'a bytes literal')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type((1, 2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type([1, 2])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type({'one': 1, 'two': 2})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type({'one', 'two'})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(None)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " ###### Every object has some number of attributes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "True.__doc__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "'a string literal'.__add__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "callable('a string literal'.__add__)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "'a string literal'.__add__('!')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " ###### Every object has one or more *base classes*, accessible via attributes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "True.__class__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "True.__class__.__bases__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "True.__class__.__bases__[0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "True.__class__.__bases__[0].__bases__[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " The method resolution order for classes is stored in `__mro__` by\n", "the class's `mro` method, which can be overridden." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bool.__mro__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import inspect" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "inspect.getmro(True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "inspect.getmro(type(True))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "inspect.getmro(type(3))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "inspect.getmro(type('a string literal'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " ###### Every object has a single unique *id*, which in CPython is a memory address." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "id(3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "id(3.14)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "id('a string literal')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "id(True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " ###### We can create objects by calling other *callable* objects (usually functions, methods, and classes)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "callable(len)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len('a string literal')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "'a string literal'.__len__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "'a string literal'.__len__()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "callable(int)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "int(3.14)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "int()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dict" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dict()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dict(pi=3.14, e=2.71)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "callable(True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "True()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bool()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1.2 Instructions for Completing Exercises" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Most sections include a set of exercises.\n", "- Sometimes they reinforce learning\n", "- Sometimes they introduce new material.\n", "- Within each section exercises start out easy and get progressively harder.\n", "- To maximize your learning:\n", " - Type the code in yourself instead of copying and pasting it.\n", " - Before you hit Enter try to predict what Python will do.\n", "- A few of the exercises have intentional typos or code that is\n", " supposed to raise an exception. See what you can learn from them.\n", "- Don't worry if you get stuck - I will go through the exercises and explain them in the video." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1.3 Exercises: Objects" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "5.0" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir(5.0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "5.0.__add__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "callable(5.0.__add__)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "5.0.__add__()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "5.0.__add__(4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "4.__add__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "(4).__add__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "(4).__add__(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import sys\n", "size = sys.getsizeof\n", "print('Size of w is', size('w'), 'bytes.')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "size('walk')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "size(2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "size(2**30 - 1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "size(2**30)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "size(2**60-1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "size(2**60)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "size(2**1000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 2 Names " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2.1 Back to the Basics: Names" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Every object has (zero or) one or more *names*, in one or more namespaces. \n", "Understanding names is foundational to understanding Python and using it effectively" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Names refer to objects. Namespaces are like dictionaries." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " IPython adds a lot of names to the global namespace! Let's\n", "workaround that." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%writefile dirp.py\n", "def _dir(obj='__secret', _CLUTTER=dir()):\n", " \"\"\"\n", " A version of dir that excludes clutter and private names.\n", " \"\"\"\n", " if obj == '__secret':\n", " names = globals().keys()\n", " else:\n", " names = dir(obj)\n", " return [n for n in names if n not in _CLUTTER and not n.startswith('_')]\n", " \n", "def _dirn(_CLUTTER=dir()):\n", " \"\"\"\n", " Display the current global namespace, ignoring old names.\n", " \"\"\"\n", " return dict([\n", " (n, v) for (n, v) in globals().items()\n", " if not n in _CLUTTER and not n.startswith('_')])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load dirp" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_dirn()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a = 300" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_dirn()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Python has *variables* in the mathematical sense - names that can\n", "vary, but not in the sense of boxes that hold values like you may be\n", "thinking about them. Imagine instead names or labels that you can\n", "add to an object or move to another object." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a = 400" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Simple name assignment and re-assignment are not operations on\n", "objects, they are namespace operations!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_dirn()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "b = a" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "b" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_dirn()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "id(a), id(b)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "id(a) == id(b)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a is b" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "del a" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_dirn()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " The `del` statement on a name is a namespace operation, i.e. it does\n", "not delete the object. Python will delete objects when they have no\n", "more names (when their reference count drops to zero).\n", "\n", "Of course, given that the name `b` is just a name for an object and it's\n", "objects that have types, not names, there's no restriction on the\n", "type of object that the name `b` refers to." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "b = 'walk'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "b" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "id(b)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "del b" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_dirn()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Object attributes are also like dictionaries, and \"in a sense the\n", "set of attributes of an object also form a namespace.\"\n", "(https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class SimpleNamespace:\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " `SimpleNamespace` was added to the `types` module in Python 3.3 " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import sys\n", "if (sys.version_info.major, sys.version_info.minor) >= (3, 3):\n", " from types import SimpleNamespace" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p = SimpleNamespace()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p.__dict__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p.x, p.y = 1.0, 2.0" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p.__dict__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p.x, p.y" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i = 10\n", "j = 10\n", "i is j" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i == j" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i = 500\n", "j = 500\n", "i is j" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i == j" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Use `==` to check for equality. Only use `is` if you want to check\n", "identity, i.e. if two object references or names refer to the same\n", "object." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " The reason `==` and `is` don't always match with `int` as shown\n", "above is that CPython pre-creates some frequently used `int` objects\n", "to increase performance. Which ones are documented in the source\n", "code, or we can figure out which ones by looking at their `id`s." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import itertools\n", "for i in itertools.chain(range(-7, -3), range(254, 259)):\n", " print(i, id(i))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2.2 Exercises: Names" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_dir = dir" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " If `dir()` returns too many names define and use _dir instead. Or\n", "use `dirp.py` from above. If you're running Python without the\n", "IPython notebook plain old `dir` should be fine." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def _dir(_CLUTTER=dir()):\n", " \"\"\"\n", " Display the current global namespace, ignoring old names.\n", " \"\"\"\n", " return [n for n in globals()\n", " if n not in _CLUTTER and not n.startswith('_')]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "v = 1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "v" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_dir()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(v)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "w = v" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "v is w" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " ---" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = [1, 2, 3]\n", "m" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n = m\n", "n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_dir()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m is n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m[1] = 'two'\n", "m, n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "int.__add__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "int.__add__ = int.__sub__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sys import getrefcount as refs" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "refs(None)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "refs(object)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sentinel_value = object()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "refs(sentinel_value)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Use `object()` to create a unique object which is not equal to any other object, for example to use as a sentinel value." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sentinel_value == object()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sentinel_value == sentinel_value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "refs(1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "refs(2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "refs(25)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[(i, refs(i)) for i in range(100)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i, j = 1, 2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i, j" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i, j = j, i" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i, j" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i, j, k = (1, 2, 3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i, j, k = 1, 2, 3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i, j, k = [1, 2, 3]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i, j, k = 'ijk'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Extended iterable unpacking is only in Python 3:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i, j, k, *rest = 'ijklmnop'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i, j, k, rest" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "first, *middle, second_last, last = 'abcdefg'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "first, middle, second_last, last" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i, *middle, j = 'ij'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i, middle, j" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 3 More About Namespaces " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.1 Namespace Scopes and Search Order" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Review:\n", "- A *namespace* is a mapping from valid identifier names to objects.\n", " Think of it as a dictionary.\n", "\n", "- Simple assignment (`=`) and `del` are namespace operations, not operations on objects.\n", "\n", "Terminology and Definitions:\n", "- A *scope* is a section of Python code where a namespace is *directly*\n", " accessible.\n", "\n", "- For an *indirectly* accessible namespace you access values via dot\n", " notation, e.g. `p.x` or `sys.version_info.major`.\n", "\n", "- The (*direct*) namespace search order is (from http://docs.python.org/3/tutorial):\n", "\n", " - The innermost scope contains local names\n", "\n", " - The namespaces of enclosing functions, searched starting\n", " with the nearest enclosing scope; (or the module if outside any\n", " function)\n", "\n", " - The middle scope contains the current module's global names\n", "\n", " - The outermost scope is the namespace containing built-in\n", " names\n", "\n", "- All namespace *changes* happen in the local scope (i.e. in the current scope in\n", " which the namespace-changing code executes):\n", "\n", " - *name* `=` i.e. assignment\n", " - `del` *name*\n", " - `import` *name*\n", " - `def` *name*\n", " - `class` *name*\n", " - function parameters: `def foo`(*name*)`:`\n", " - `for` loop: `for` *name* `in ...`\n", " - except clause: `Exception as` *name*`:`\n", " - with clause: `with open(filename) as` *name*`:`\n", " - docstrings: `__doc__`\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " You should never reassign built-in names..., but let's do so to\n", "explore how name scopes work." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def f1():\n", " def len():\n", " len = range(3)\n", " print(\"In f1's local len(), len is {}\".format(len))\n", " return len\n", " print('In f1(), len = {}'.format(len))\n", " result = len()\n", " print('Returning result: {!r}'.format(result))\n", " return result" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f1()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def f2():\n", " def len():\n", " # len = range(3)\n", " print(\"In f1's local len(), len is {}\".format(len))\n", " return len\n", " print('In f1(), len = {}'.format(len))\n", " result = len()\n", " print('Returning result: {!r}'.format(result))\n", " return result" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f2()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len = 99" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def print_len(s):\n", " print('len(s) == {}'.format(len(s)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print_len('walk')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "del len" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print_len('walk')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pass = 3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Keywords at https://docs.python.org/3/reference/lexical_analysis.html#keywords\n", "\n", " False class finally is return\n", " None continue for lambda try\n", " True def from nonlocal while\n", " and del global not with\n", " as elif if or yield\n", " assert else import pass\n", " break except in raise" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.2 Namespaces: Function Locals" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's look at some surprising behaviour." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = 1\n", "def test_outer_scope():\n", " print('In test_outer_scope x ==', x)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_outer_scope()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def test_local():\n", " x = 2\n", " print('In test_local x ==', x)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_local()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def test_unbound_local():\n", " print('In test_unbound_local ==', x)\n", " x = 3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_unbound_local()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Let's introspect the function `test_unbound_local` to help us understand this error." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_unbound_local.__code__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_unbound_local.__code__.co_argcount # count of positional args" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_unbound_local.__code__.co_name # function name" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_unbound_local.__code__.co_names # names used in bytecode" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_unbound_local.__code__.co_nlocals # number of locals" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_unbound_local.__code__.co_varnames # names of locals" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " See \"Code objects\" at https://docs.python.org/3/reference/datamodel.html?highlight=co_nlocals#the-standard-type-hierarchy" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import dis" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dis.dis(test_unbound_local.__code__.co_code)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " The use of `x` by LOAD_FAST happens before it's set by STORE_FAST." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "> \"This is because when you make an assignment to a variable in a\n", "> scope, that variable becomes local to that scope and shadows any\n", "> similarly named variable in the outer scope. Since the last\n", "> statement in foo assigns a new value to x, the compiler recognizes\n", "> it as a local variable. Consequently when the earlier print x\n", "> attempts to print the uninitialized local variable and an error\n", "> results.\" --\n", "> https://docs.python.org/3/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " To explore this further on your own compare these two:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " `dis.dis(codeop.compile_command('def t1(): a = b; b = 7'))` \n", "`dis.dis(codeop.compile_command('def t2(): b = 7; a = b'))`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def test_global():\n", " global x\n", " print('In test_global before, x ==', x)\n", " x = 4\n", " print('In test_global after, x ==', x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_global()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_global.__code__.co_varnames" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def test_nonlocal():\n", " x = 5\n", " def test6():\n", " nonlocal x\n", " print('test6 before x ==', x)\n", " x = 6\n", " print('test6 after x ==', x)\n", " print('test_nonlocal before x ==', x)\n", " test6()\n", " print('test_nonlocal after x ==', x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = 1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_nonlocal()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.3 The Built-ins Namespace" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Restart Python to unclutter the namespace." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%javascript\n", "IPython.notebook.kernel.restart();" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[n for n in dir() if not n.startswith('_')]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " There are lots of built-in names that `dir()` doesn't show us.\n", "Let's use some Python to explore all the builtin names by category." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import builtins, collections, inspect, textwrap\n", "fill = textwrap.TextWrapper(width=60).fill\n", "def pfill(pairs):\n", " \"\"\"Sort and print first of every pair\"\"\"\n", " print(fill(' '.join(list(zip(*sorted(pairs)))[0])))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Collect all members of `builtins`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "members = set([\n", " m for m in inspect.getmembers(builtins)\n", " if not m[0].startswith('_')])\n", "len(members)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Pull out just the `exception`s:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "exceptions = [\n", " (name, obj) for (name, obj) in members\n", " if inspect.isclass(obj) and\n", " issubclass(obj, BaseException)]\n", "members -= set(exceptions)\n", "len(exceptions), len(members)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pfill(exceptions)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "https://docs.python.org/3/library/exceptions.html#exception-hierarchy:\n", "\n", " BaseException\n", " +-- SystemExit\n", " +-- KeyboardInterrupt\n", " +-- GeneratorExit\n", " +-- Exception\n", " +-- StopIteration\n", " +-- ArithmeticError\n", " | +-- FloatingPointError\n", " | +-- OverflowError\n", " | +-- ZeroDivisionError\n", " +-- AssertionError\n", " +-- AttributeError\n", " +-- BufferError\n", " +-- EOFError\n", " +-- ImportError\n", " +-- LookupError\n", " | +-- IndexError\n", " | +-- KeyError\n", " +-- MemoryError\n", " +-- NameError\n", " | +-- UnboundLocalError\n", " +-- OSError\n", " | +-- BlockingIOError\n", " | +-- ChildProcessError\n", " | +-- ConnectionError\n", " | | +-- BrokenPipeError\n", " | | +-- ConnectionAbortedError\n", " | | +-- ConnectionRefusedError\n", " | | +-- ConnectionResetError\n", " | +-- FileExistsError\n", " | +-- FileNotFoundError\n", " | +-- InterruptedError\n", " | +-- IsADirectoryError\n", " | +-- NotADirectoryError\n", " | +-- PermissionError\n", " | +-- ProcessLookupError\n", " | +-- TimeoutError\n", " +-- ReferenceError\n", " +-- RuntimeError\n", " | +-- NotImplementedError\n", " +-- SyntaxError\n", " | +-- IndentationError\n", " | +-- TabError\n", " +-- SystemError\n", " +-- TypeError\n", " +-- ValueError\n", " | +-- UnicodeError\n", " | +-- UnicodeDecodeError\n", " | +-- UnicodeEncodeError\n", " | +-- UnicodeTranslateError\n", " +-- Warning\n", " +-- DeprecationWarning\n", " +-- PendingDeprecationWarning\n", " +-- RuntimeWarning\n", " +-- SyntaxWarning\n", " +-- UserWarning\n", " +-- FutureWarning\n", " +-- ImportWarning\n", " +-- UnicodeWarning\n", " +-- BytesWarning\n", " +-- ResourceWarning" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pfill(members)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Most are one of these two types:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(int), type(len)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Print them:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bnames = collections.defaultdict(set)\n", "for name, obj in members:\n", " bnames[type(obj)].add((name, obj))\n", "for typ in [type(int), type(len)]:\n", " pairs = bnames.pop(typ)\n", " print(typ)\n", " pfill(pairs)\n", " print()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " The leftovers:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for typ, pairs in bnames.items():\n", " print('{}: {}'.format(typ, ' '.join((n for (n, o) in pairs))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.4 Exercises: The Built-ins Namespace" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[k for k in locals().keys() if not k.startswith('_')]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[k for k in globals().keys() if not k.startswith('_')]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " In the REPL these are the same:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "locals() == globals()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " The following code is *not* recommended but it reminds us that\n", "namespaces are like dictionaries." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = 0" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "locals()['x']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "locals()['x'] = 1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "locals()['x']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " If you're tempted to use it, try this code which due to \"fast\n", "locals\" doesn't do what you might expect:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def f():\n", " locals()['x'] = 5\n", " print(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 4 Import " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.1 The import Statement" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Remember, these change or modify a namespace:\n", "\n", "- Simple assignment (`=`) and `del`\n", "- [`globals()` and `locals()`]\n", "- `import`\n", "- `def`\n", "- `class`\n", "- [Also function parameters, `for`, `except`, `with`, and docstrings.]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Next we'll explore `import`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load dirp" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_dir()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pprint\n", "_dir()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_dir(pprint)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint.pformat" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint.pprint" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint.foo" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint.foo = 'Python is dangerous'\n", "pprint.foo" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pprint import pformat as pprint_pformat\n", "_dir()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint.pformat is pprint_pformat" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint.pformat" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "del pprint\n", "import pprint as pprint_module\n", "_dir()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint_module.pformat is pprint_pformat" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "math" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir(math)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "del math" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Why doesn't `import math` give a `NameError`?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "math" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "del math" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " What if we don't know the name of the module until run-time?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import importlib" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "importlib.import_module('math')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "math_module = importlib.import_module('math')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "math_module.pi" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "math" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "module_name = 'math'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import module_name" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import 'math'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import math" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.2 Exercises: The import Statement" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pprint" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir(pprint)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint.__doc__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint.__file__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint.__name__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pprint import *" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[n for n in dir() if not n.startswith('_')]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import importlib" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "help(importlib.reload)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "importlib.reload(csv)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "importlib.reload('csv')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import csv" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "importlib.reload('csv')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "importlib.reload(csv)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import sys" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sys.path" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 5 Functions " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5.1 Functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def f():\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f.__name__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f.__name__ = 'g'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f.__name__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f.__qualname__ # Only in Python >= 3.3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f.__qualname__ = 'g'\n", "f" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f.__dict__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f.foo = 'bar'\n", "f.__dict__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def f(a, b, k1='k1', k2='k2',\n", " *args, **kwargs):\n", " print('a: {!r}, b: {!r}, '\n", " 'k1: {!r}, k2: {!r}'\n", " .format(a, b, k1, k2))\n", " print('args:', repr(args))\n", " print('kwargs:', repr(kwargs))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f.__defaults__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(1, 2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(a=1, b=2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(b=1, a=2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(1, 2, 3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(1, 2, k2=4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(1, k1=3) # Fails" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(1, 2, 3, 4, 5, 6)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(1, 2, 3, 4, keya=7, keyb=8)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(1, 2, 3, 4, 5, 6, keya=7, keyb=8)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(1, 2, 3, 4, 5, 6, keya=7, keyb=8, 9)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def g(a, b, *args, c=None):\n", " print('a: {!r}, b: {!r}, '\n", " 'args: {!r}, c: {!r}'\n", " .format(a, b, args, c))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g.__defaults__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g.__kwdefaults__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g(1, 2, 3, 4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g(1, 2, 3, 4, c=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Keyword-only arguments in Python 3, i.e. named parameters occurring\n", "after `*args` (or `*`) in the parameter list must be specified using\n", "keyword syntax in the call. This lets a function take a varying\n", "number of arguments *and* also take options in the form of keyword\n", "arguments." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def h(a=None, *args, keyword_only=None):\n", " print('a: {!r}, args: {!r}, '\n", " 'keyword_only: {!r}'\n", " .format(a, args, keyword_only))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h.__defaults__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h.__kwdefaults__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h(1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h(1, 2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h(1, 2, 3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h(*range(15))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h(1, 2, 3, 4, keyword_only=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h(1, keyword_only=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h(keyword_only=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def h2(a=None, *, keyword_only=None):\n", " print('a: {!r}, '\n", " 'keyword_only: {!r}'\n", " .format(a, keyword_only))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h2()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h2(1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h2(keyword_only=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "h2(1, 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5.2 Exercises: Functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def f(*args, **kwargs):\n", " print(repr(args), repr(kwargs))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(1, 2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(1, a=3, b=4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def f2(k1, k2):\n", " print('f2({}, {})'.format(k1, k2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t = 1, 2\n", "t" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d = dict(k1=3, k2=4)\n", "d" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f2(*t)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f2(**d)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f2(*d)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "list(d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(*t, **d)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = 'one two'.split()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f(1, 2, *m)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "father = 'Dad'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "locals()['father']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "'Hi {father}'.format(**locals()) # A convenient hack. Only for throwaway code." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def f2(a: 'x', b: 5, c: None, d:list) -> float:\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f2.__annotations__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(f2.__annotations__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5.3 Augmented Assignment Statements" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create two names for the `str` object `123`, then from it create `1234`\n", "and reassign one of the names:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s1 = s2 = '123'\n", "s1 is s2, s1, s2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s2 = s2 + '4'\n", "s1 is s2, s1, s2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " We can see this reassigns the second name so it refers to a new\n", "object. This works similarly if we start with two names for one\n", "`list` object and then reassign one of the names." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m1 = m2 = [1, 2, 3]\n", "m1 is m2, m1, m2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m2 = m2 + [4]\n", "m1 is m2, m1, m2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " If for the `str` objects we instead use an *augmented assignment\n", "statement*, specifically *in-place add* **+=**, we get the same\n", "behaviour." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s1 = s2 = '123'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s2 += '4'\n", "s1 is s2, s1, s2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " However, for the `list` objects the behaviour changes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m1 = m2 = [1, 2, 3]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m2 += [4]\n", "m1 is m2, m1, m2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " The **+=** in **foo += 1** is not just syntactic sugar for **foo = foo +\n", "1**. **+=** and other augmented assignment statements have their\n", "own bytecodes and methods." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Let's look at the bytecode to confirm this. Notice BINARY_ADD\n", "vs. INPLACE_ADD. Note the runtime types of the objects referred to\n", "my `s` and `v` is irrelevant to the bytecode that gets produced." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import codeop, dis" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dis.dis(codeop.compile_command(\"a = a + b\"))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dis.dis(codeop.compile_command(\"a += b\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m2 = [1, 2, 3]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Notice that `__iadd__` returns a value" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m2.__iadd__([4])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " and it also changed the list" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s2.__iadd__('4')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "So what happened when `INPLACE_ADD` ran against the `str` object?\n", "\n", "If `INPLACE_ADD` doesn't find `__iadd__` it instead calls `__add__` and\n", "reassigns `s1`, i.e. it falls back to `__add__`.\n", "\n", "https://docs.python.org/3/reference/datamodel.html#object.__iadd__:\n", "\n", "> These methods are called to implement the augmented arithmetic\n", "> assignments (+=, etc.). These methods should attempt to do the\n", "> operation in-place (modifying self) and return the result (which\n", "> could be, but does not have to be, self). If a specific method is\n", "> not defined, the augmented assignment falls back to the normal\n", "> methods.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Here's similar behaviour with tuples, but a bit more surprising:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t1 = (7,)\n", "t1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t1[0] += 1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t1[0] = t1[0] + 1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t2 = ([7],)\n", "t2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t2[0] += [8]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " What value do we expect t2 to have?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Let's simulate the steps to see why this behaviour makes sense." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = [7]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t2 = (m,)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temp = m.__iadd__([8])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temp == m" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temp is m" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "temp" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t2[0] = temp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " For a similar explanation see https://docs.python.org/3/faq/programming.html#faq-augmented-assignment-tuple-error" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 5.4 Function Arguments are Passed by Assignment" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Can functions modify the arguments passed in to them?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " When a caller passes an argument to a function, the function starts\n", "execution with a local name (the parameter from its signature)\n", "referring to the argument object passed in." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def test_1a(s):\n", " print('Before:', s)\n", " s += ' two'\n", " print('After:', s)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s1 = 'one'\n", "s1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_1a(s1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " To see more clearly why `s1` is still a name for 'one', consider\n", "this version which is functionally equivalent but has two changes\n", "highlighted in the comments:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def test_1b(s):\n", " print('Before:', s)\n", " s = s + ' two' # Changed from +=\n", " print('After:', s)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_1b('one') # Changed from s1 to 'one'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " In both cases the name `s` at the beginning of `test_1a` and\n", "`test_1b` was a name that referred to the `str` object `'one'`,\n", "and in both the function-local name `s` was reassigned to refer to\n", "the new `str` object `'hello there'`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Let's try this with a `list`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def test_2a(m):\n", " print('Before:', m)\n", " m += [4] # list += list is shorthand for list.extend(list)\n", " print('After:', m)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m1 = [1, 2, 3]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_2a(m1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 6 Decorators " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 6.1 Decorators Simplified" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Conceptually a decorator changes or adds to the functionality of a\n", "function either by modifying its arguments before the function is\n", "called, or changing its return values afterwards, or both." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " First let's look at a simple example of a function that returns another function." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def add(first, second):\n", " return first + second" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add(2, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def create_adder(first):\n", " def adder(second):\n", " return add(first, second)\n", " return adder" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add_to_2 = create_adder(2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add_to_2(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Next let's look at a function that receives a function as an argument." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def trace_function(f):\n", " \"\"\"Add tracing before and after a function\"\"\"\n", " def new_f(*args):\n", " \"\"\"The new function\"\"\"\n", " print(\n", " 'Called {}({!r})'\n", " .format(f, *args))\n", " result = f(*args)\n", " print('Returning', result)\n", " return result\n", " return new_f" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " This `trace_function` wraps the functionality of whatever existing\n", "function is passed to it by returning a new function which calls the\n", "original function, but prints some trace information before and\n", "after." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "traced_add = trace_function(add)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "traced_add(2, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " We could instead reassign the original name." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add = trace_function(add)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add(2, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Or we can use the decorator syntax to do that for us: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@trace_function" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def add(first, second):\n", " \"\"\"Return the sum of two arguments.\"\"\"\n", " return first + second" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add(2, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add.__qualname__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add.__doc__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Use `@wraps` to update the metadata of the returned function and make it more useful." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import functools\n", "def trace_function(f):\n", " \"\"\"Add tracing before and after a function\"\"\"\n", " @functools.wraps(f) # <-- Added\n", " def new_f(*args):\n", " \"\"\"The new function\"\"\"\n", " print(\n", " 'Called {}({!r})'\n", " .format(f, *args))\n", " result = f(*args)\n", " print('Returning', result)\n", " return result\n", " return new_f" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@trace_function\n", "def add(first, second):\n", " \"\"\"Return the sum of two arguments.\"\"\"\n", " return first + second" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add.__qualname__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add.__doc__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Here's another common example of the utility of\n", "decorators. *Memoization* is \"an optimization technique... storing\n", "the results of expensive function calls and returning the cached\n", "result when the same inputs occur again.\" --\n", "https://en.wikipedia.org/wiki/Memoization" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def memoize(f):\n", " print('Called memoize({!r})'.format(f))\n", " cache = {}\n", " @functools.wraps(f)\n", " def memoized_f(*args):\n", " print('Called memoized_f({!r})'.format(args))\n", " if args in cache:\n", " print('Cache hit!')\n", " return cache[args]\n", " if args not in cache:\n", " result = f(*args)\n", " cache[args] = result\n", " return result\n", " return memoized_f" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@memoize\n", "def add(first, second):\n", " \"\"\"Return the sum of two arguments.\"\"\"\n", " return first + second" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add(2, 3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add(4, 5)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add(2, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Note that this not a full treatment of decorators, only an\n", "introduction, and primarily from the perspective of how they\n", "intervene in the namespace operation of function definition. For\n", "example it leaves out entirely how to handle decorators that take\n", "more than one argument." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 6.2 Exercises: Decorators" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " A decorator is a function that takes a function as an argument\n", "and *typically* returns a new function, but it can return anything.\n", "The following code misuses decorators to help you focus on their\n", "mechanics, which are really quite simple." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "del x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def return_3(f):\n", " print('Called return_3({!r})'.format(f))\n", " return 3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def x():\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = return_3(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " What object will `x` refer to now?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Here's equivalent code using `@decorator` syntax:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@return_3\n", "def x():\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 7 How Classes Work " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 7.1 Deconstructing the Class Statement" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "- The `class` statement starts a block of code and creates a new\n", " namespace. All namespace changes in the block, e.g. assignment and\n", " function definitions, are made in that new namespace. Finally it\n", " adds the class name to the namespace where the class statement\n", " appears.\n", "\n", "- Instances of a class are created by calling the class:\n", " `ClassName()` or `ClassName(args)`.\n", "\n", "- `ClassName.__init__(, ...)` is called automatically, and\n", " is passed the instance of the class already created by a call to the\n", " `__new__` method.\n", "\n", "- Accessing an attribute `method_name` on a class instance returns\n", " a *method object*, if `method_name` references a method (in\n", " `ClassName` or its superclasses). A method object binds the class\n", " instance as the first argument to the method.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Number: # In Python 2.x use \"class Number(object):\"\n", " \"\"\"A number class.\"\"\"\n", " __version__ = '1.0'\n", " \n", " def __init__(self, amount):\n", " self.amount = amount\n", " \n", " def add(self, value):\n", " \"\"\"Add a value to the number.\"\"\"\n", " print('Call: add({!r}, {})'.format(self, value))\n", " return self.amount + value" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.__version__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.__doc__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "help(Number)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.__init__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.add" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir(Number)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def dir_public(obj):\n", " return [n for n in dir(obj) if not n.startswith('__')]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir_public(Number)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number2 = Number(2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number2.amount" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number2.__init__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number2.add" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir_public(number2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "set(dir(number2)) ^ set(dir(Number)) # symmetric_difference" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number2.__dict__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.__dict__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "'add' in Number.__dict__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number2.add" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number2.add(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Here's some unusual code ahead which will help us think carefully\n", "about how Python works." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.add" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Will this work? Here's the gist of the method `add` defined above:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", " def add(self, value):\n", " return self.amount + value\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.add(2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.add(2, 3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.add(number2, 3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number2.add(3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Remember, here's how `__init__` was defined above:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", " def __init__(self, amount):\n", " self.amount = amount" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.__init__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "help(Number.__init__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Here's some code that's downright risky, but instructive. You\n", "should never need to do this in your code." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def set_double_amount(number, amount):\n", " number.amount = 2 * amount" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.__init__ = set_double_amount" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.__init__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "help(Number.__init__)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4 = Number(2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.amount" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.add(5)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.__init__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number2.__init__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def multiply_by(number, value):\n", " return number.amount * value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Let's add a `mul` method. However, I will intentionally make a mistake." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.mul = multiply_by" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.mul" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.mul(5)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.mul(number4, 5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Where's the mistake?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number10 = Number(5)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number10.mul" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir_public(number10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir_public(Number)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir_public(number4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.mul = multiply_by" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number10.mul(5)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.mul(5)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir_public(number4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.__dict__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "del number4.mul" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.__dict__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir_public(number4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.mul" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.mul" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.mul(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Let's look behind the curtain to see how class instances work in Python." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number.add" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.add" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Bound methods are handy." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add_to_4 = number4.add" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "add_to_4(6)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir_public(number4)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir(number4.add)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dir_public(number4.add)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "set(dir(number4.add)) - set(dir(Number.add))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.add.__self__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.add.__self__ is number4" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.add.__func__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.add.__func__ is Number.add" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.add.__func__ is number10.add.__func__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.add(5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " So here's approximately how Python executes `number4.add(5)`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number4.add.__func__(number4.add.__self__, 5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 7.2 Creating Classes with the type Function" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\"The class statement is just a way to call a function, take the\n", "result, and put it into a namespace.\" -- Glyph Lefkowitz in *Turtles\n", "All The Way Down: Demystifying Deferreds, Decorators, and\n", "Declarations* at PyCon 2010\n", "\n", "`type(name, bases, dict)` is the default function that gets called\n", "when Python read a `class` statement." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(type.__doc__)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Let's use the type function to build a class." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def init(self, amount):\n", " self.amount = amount" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def add(self, value):\n", " \"\"\"Add a value to the number.\"\"\"\n", " print('Call: add({!r}, {})'.format(self, value))\n", " return self.amount + value" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Number = type(\n", " 'Number', (object,),\n", " dict(__version__='1.0', __init__=init, add=add))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number3 = Number(3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(number3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number3.__class__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number3.__dict__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number3.amount" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "number3.add(4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Remember, here's the normal way to create a class:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Number:\n", " __version__='1.0'\n", " \n", " def __init__(self, amount):\n", " self.amount = amount\n", " \n", " def add(self, value):\n", " return self.amount + value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "We can customize how classes get created. \n", "https://docs.python.org/3/reference/datamodel.html#customizing-class-creation\n", "\n", "> By default, classes are constructed using type(). The class body is\n", "> executed in a new namespace and the class name is bound locally to\n", "> the result of type(name, bases, namespace).\n", "\n", "> The class creation process can be customised by passing the\n", "> metaclass keyword argument in the class definition line, or by\n", "> inheriting from an existing class that included such an argument.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " The following makes explicit that the `metaclass`, i.e. the\n", "callable that Python should use to create a class, is the built-in\n", "function `type`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Number(metaclass=type):\n", " def __init__(self, amount):\n", " self.amount = amount" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 7.3 Exercises: The Class Statement" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Test your understanding of the mechanics of class creation with some\n", "very unconventional uses of those mechanics." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " What does the following code do? Note that `return_5` ignores\n", "arguments passed to it." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def return_5(name, bases, namespace):\n", " print('Called return_5({!r})'.format((name, bases, namespace)))\n", " return 5 " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "return_5(None, None, None)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x = return_5(None, None, None)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " The syntax for specifying a metaclass changed in Python 3 so choose appropriately." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class y(object): # Python 2.x\n", " __metaclass__ = return_5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class y(metaclass=return_5): # Python 3.x\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "y" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " We saw how decorators are applied to functions. They can also be\n", "applied to classes. What does the following code do?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def return_6(klass):\n", " print('Called return_6({!r})'.format(klass))\n", " return 6" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "return_6(None)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@return_6\n", "class z:\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "z" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "type(z)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 7.4 Class Decorator Example" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " This is not a robust decorator" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def class_counter(klass):\n", " \"\"\"Modify klass to count class instantiations\"\"\"\n", " klass.count = 0\n", " klass.__init_orig__ = klass.__init__\n", " def new_init(self, *args, **kwargs):\n", " klass.count += 1\n", " klass.__init_orig__(self, *args, **kwargs)\n", " klass.__init__ = new_init\n", " return klass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "@class_counter\n", "class TC:\n", " pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TC.count" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TC()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TC()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "TC.count" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 8 Special Methods " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 8.1 Special Methods of Classes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Python implements operator overloading and many other features via\n", "special methods, the \"dunder\" methods that start and end with double\n", "underscores. Here's a very brief summary of them, more information at\n", "https://docs.python.org/3/reference/datamodel.html?highlight=co_nlocals#special-method-names.\n", "\n", "- basic class customization: `__new__`, `__init__`, `__del__`,\n", " `__repr__`, `__str__`, `__bytes__`, `__format__`\n", "\n", "- rich comparison methods: `__lt__`, `__le__`, `__eq__`, `__ne__`,\n", " `__gt__`, `__ge__`\n", "\n", "- attribute access and descriptors: `__getattr__`, `__getattribute__`,\n", " `__setattr__`, `__delattr__`, `__dir__`, `__get__`, `__set__`,\n", " `__delete__`\n", "\n", "- callables: `__call__`\n", "\n", "- container types: `__len__`, `__length_hint__`, `__getitem__`,\n", " `__missing__`, `__setitem__`, `__delitem__`, `__iter__`, (`__next__`),\n", " `__reversed__`, `__contains__`\n", "\n", "- numeric types: `__add__`, `__sub__`, `__mul__`, `__truediv__`,\n", " `__floordiv__`, `__mod__`, `__divmod__`, `__pow__`, `__lshift__`,\n", " `__rshift__`, `__and__`, `__xor__`, `__or__`\n", "\n", "- reflected operands: `__radd__`, `__rsub__`, `__rmul__`,\n", " `__rtruediv__`, `__rfloordiv__`, `__rmod__`, `__rdivmod__`,\n", " `__rpow__`, `__rlshift__`, `__rrshift__`, `__rand__`, `__rxor__`,\n", " `__ror__`\n", "\n", "- inplace operations: `__iadd__`, `__isub__`, `__imul__`,\n", " `__trueidiv__`, `__ifloordiv__`, `__imod__`, `__ipow__`,\n", " `__ilshift__`, `__irshift__`, `__iand__`, `__ixor__`, `__xor__`\n", "\n", "- unary arithmetic: `__neg__`, `__pos__`, `__abs__`, `__invert__`\n", "\n", "- implementing built-in functions: `__complex__`, `__int__`, `__float__`, `__round__`, `__bool__`, `__hash__`\n", "\n", "- context managers: `__enter__`, `__exit__`\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Let's look at a simple example of changing how a class handles attribute access." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class UppercaseAttributes:\n", " \"\"\"\n", " A class that returns uppercase values on uppercase attribute access.\n", " \"\"\"\n", " # Called (if it exists) if an attribute access fails:\n", " def __getattr__(self, name):\n", " if name.isupper():\n", " if name.lower() in self.__dict__:\n", " return self.__dict__[\n", " name.lower()].upper()\n", " raise AttributeError(\n", " \"'{}' object has no attribute {}.\"\n", " .format(self, name))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d = UppercaseAttributes()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d.__dict__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d.foo = 'bar'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d.foo" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d.__dict__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d.FOO" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d.baz" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " To add behaviour to specific attributes you can also use properties." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class PropertyEg:\n", " \"\"\"@property example\"\"\"\n", " def __init__(self):\n", " self._x = 'Uninitialized'\n", " \n", " @property\n", " def x(self):\n", " \"\"\"The 'x' property\"\"\"\n", " print('called x getter()')\n", " return self._x\n", " \n", " @x.setter\n", " def x(self, value):\n", " print('called x.setter()')\n", " self._x = value\n", " \n", " @x.deleter\n", " def x(self):\n", " print('called x.deleter')\n", " self.__init__()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p = PropertyEg()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p._x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p.x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p.x = 'bar'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p.x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "del p.x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p.x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p.x = 'bar'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Usually you should just expose attributes and add properties later\n", "if you need some measure of control or change of behaviour." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 8.2 Exercises: Special Methods of Classes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Try the following:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class Get:\n", " def __getitem__(self, key):\n", " print('called __getitem__({} {})'\n", " .format(type(key), repr(key)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g = Get()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g[1]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g[-1]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g[0:3]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g[0:10:2]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g['Jan']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "g[g]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = list('abcdefghij')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m[0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m[-1]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m[::2]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s = slice(3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m[s]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m[slice(1, 3)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m[slice(0, 2)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m[slice(0, len(m), 2)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m[::2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 9 Iterators and Generators " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 9.1 Iterables, Iterators, and the Iterator Protocol" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "- A `for` loop evaluates an expression to get an *iterable* and then\n", " calls `iter()` to get an iterator.\n", "\n", "- The iterator's `__next__()` method is called repeatedly until\n", " `StopIteration` is raised.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for i in 'abc':\n", " print(i)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iterator = iter('ab')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iterator.__next__()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iterator.__next__()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iterator.__next__()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iterator.__next__()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iterator = iter('ab')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " `next()` just calls `__next__()`, but you can pass it a second argument:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iterator = iter('ab')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator, 'z')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator, 'z')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator, 'z')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator, 'z')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "- `iter(foo)`\n", "\n", " - checks for `foo.__iter__()` and calls it if it exists\n", "\n", " - else checks for `foo.__getitem__()` and returns an object which\n", " calls it starting at zero and handles `IndexError` by raising\n", " `StopIteration`.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class MyList:\n", " \"\"\"Demonstrate the iterator protocol\"\"\"\n", " def __init__(self, sequence):\n", " self.items = sequence\n", " \n", " def __getitem__(self, key):\n", " print('called __getitem__({})'\n", " .format(key))\n", " return self.items[key]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = MyList('ab')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.__getitem__(0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.__getitem__(1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.__getitem__(2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m[0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m[1]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m[2]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hasattr(m, '__iter__')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hasattr(m, '__getitem__')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iterator = iter(m)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "list(m)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for item in m:\n", " print(item)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 9.2 Exercises: Iterables, Iterators, and the Iterator Protocol" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = [1, 2, 3]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "it = iter(m)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(it)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(it)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(it)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(it)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for n in m:\n", " print(n)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d = {'one': 1, 'two': 2, 'three':3}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "it = iter(d)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "list(it)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m1 = [2 * i for i in range(3)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m2 = (2 * i for i in range(3))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "list(m2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "list(m2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 9.3 Generator Functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def list123():\n", " print('Before first yield')\n", " yield 1\n", " print('Between first and second yield')\n", " yield 2\n", " print('Between second and third yield')\n", " yield 3\n", " print('After third yield')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "list123" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "list123()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iterator = list123()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for i in list123():\n", " print(i)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def even(limit):\n", " for i in range(0, limit, 2):\n", " print('Yielding', i)\n", " yield i\n", " print('done loop, falling out')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iterator = even(3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iterator" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "next(iterator)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for i in even(3):\n", " print(i)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "list(even(10))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Compare these versions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def even_1(limit):\n", " for i in range(0, limit, 2):\n", " yield i" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def even_2(limit):\n", " result = []\n", " for i in range(0, limit, 2):\n", " result.append(i)\n", " return result" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[i for i in even_1(10)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[i for i in even_2(10)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def paragraphs(lines):\n", " result = ''\n", " for line in lines:\n", " if line.strip() == '':\n", " yield result\n", " result = ''\n", " else:\n", " result += line\n", " yield result" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%writefile eg.txt\n", "This is some sample\n", "text. It has a couple\n", "of paragraphs.\n", "\n", "Each paragraph has at\n", "least one sentence.\n", "\n", "Most paragraphs have\n", "two." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "list(paragraphs(open('eg.txt')))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len(list(paragraphs(open('eg.txt'))))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 9.4 Exercises: Generator Functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Write a generator `double(val, n=3)` that takes a value and returns\n", "that value doubled n times. below are test cases to clarify." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load solve_double # To display the solution in IPython" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from solve_double import double\n", "def test_double():\n", " assert list(double('.')) == ['..', '....', '........']\n", " assert list(double('s.', 2)) == ['s.s.', 's.s.s.s.']\n", " assert list(double(1)) == [2, 4, 8]\n", "test_double()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " A few miscellaneous items:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "months = ['jan', 'feb', 'mar', 'apr', 'may']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "months[0:2]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "months[0:100]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "month_num_pairs = list(zip(months, range(1, 100)))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "month_num_pairs" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "list(zip(*month_num_pairs))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "{letter: num for letter, num in zip(months, range(1, 100))}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "{letter.upper() for letter in 'mississipi'}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 10 Taking Advantage of First Class Objects " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 10.1 First Class Objects" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Python exposes many language features and places\n", "almost no constraints on what types data\n", "structures can hold.\n", "\n", "Here's an example of using a dictionary of functions to create a\n", "simple calculator. In some languages the only reasonable solution\n", "would require a `case` or `switch` statement, or a series of `if`\n", "statements. If you've been using such a language for a while, this\n", "example may help you expand the range of solutions you can imagine in\n", "Python.\n", "\n", "Let's iteratively write code to get this behaviour:\n", "\n", " assert calc('7+3') == 10\n", " assert calc('9-5') == 4\n", " assert calc('9/3') == 3\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "7+3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "expr = '7+3'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "lhs, op, rhs = expr" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "lhs, op, rhs" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "lhs, rhs = int(lhs), int(rhs)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "lhs, op, rhs" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "op, lhs, rhs" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def perform_operation(op, lhs, rhs):\n", " if op == '+':\n", " return lhs + rhs\n", " if op == '-':\n", " return lhs - rhs\n", " if op == '/':\n", " return lhs / rhs" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "perform_operation('+', 7, 3) == 10" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " The `perform_operation` function has a lot of boilerplate repetition.\n", "Let's use a data structure instead to use less code and make it easier to extend." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import operator" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "operator.add(7, 3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "OPERATOR_MAPPING = {\n", " '+': operator.add,\n", " '-': operator.sub,\n", " '/': operator.truediv,\n", " }" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "OPERATOR_MAPPING['+']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "OPERATOR_MAPPING['+'](7, 3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def perform_operation(op, lhs, rhs):\n", " return OPERATOR_MAPPING[op](lhs, rhs)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "perform_operation('+', 7, 3) == 10" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def calc(expr):\n", " lhs, op, rhs = expr\n", " lhs, rhs = int(lhs), int(rhs)\n", " return perform_operation(op, lhs, rhs)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "calc('7+3')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "calc('9-5')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "calc('9/3')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "calc('3*4')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "OPERATOR_MAPPING['*'] = operator.mul" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "calc('3*4')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's look at another example. Suppose we have data where every\n", "line is fixed length with fixed length records in it and we want to\n", "pull fields out of it by name:\n", "\n", " PYTHON_RELEASES = [\n", " 'Python 3.4.0 2014-03-17',\n", " 'Python 3.3.0 2012-09-29',\n", " 'Python 3.2.0 2011-02-20',\n", " 'Python 3.1.0 2009-06-26',\n", " 'Python 3.0.0 2008-12-03',\n", " 'Python 2.7.9 2014-12-10',\n", " 'Python 2.7.8 2014-07-02',\n", " ]\n", "\n", " release34 = PYTHON_RELEASES[0]\n", "\n", " release = ReleaseFields(release34) # 3.4.0\n", " assert release.name == 'Python'\n", " assert release.version == '3.4.0'\n", " assert release.date == '2014-03-17'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " This works:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class ReleaseFields:\n", " def __init__(self, data):\n", " self.data = data\n", " \n", " @property\n", " def name(self):\n", " return self.data[0:6]\n", " \n", " @property\n", " def version(self):\n", " return self.data[7:12]\n", " \n", " @property\n", " def date(self):\n", " return self.data[13:23]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "release34 = 'Python 3.4.0 2014-03-17'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "release = ReleaseFields(release34)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "assert release.name == 'Python'\n", "assert release.version == '3.4.0'\n", "assert release.date == '2014-03-17'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " However, the following is better especially if there are many fields\n", "or as part of a libary which handle lots of different record formats:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class ReleaseFields:\n", " slices = {\n", " 'name': slice(0, 6),\n", " 'version': slice(7, 12),\n", " 'date': slice(13, 23)\n", " }\n", " \n", " def __init__(self, data):\n", " self.data = data\n", " \n", " def __getattr__(self, attribute):\n", " if attribute in self.slices:\n", " return self.data[self.slices[attribute]]\n", " raise AttributeError(\n", " \"{!r} has no attribute {!r}\"\n", " .format(self, attribute))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "release = ReleaseFields(release34)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "assert release.name == 'Python'\n", "assert release.version == '3.4.0'\n", "assert release.date == '2014-03-17'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Confirm that trying to access an attribute that doesn't exist fails\n", "correctly. (Note they won't in Python 2.x unless you add `(object)`\n", "after `class ReleaseFields`)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "release.foo == 'exception'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " If you find yourself writing lots of boilerplate code as in the\n", "first versions of the calculator and fixed length record class\n", "above, you may want to try changing it to use a Python data\n", "structure with first class objects." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 10.2 Binding Data with Functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is often useful to bind data to a function. A method clearly\n", "does that, binding the instance's attributes with the method behaviour,\n", "but it's not the only way." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def log(severity, message):\n", " print('{}: {}'.format(severity.upper(), message))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "log('warning', 'this is a warning')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "log('error', 'this is an error')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Create a new function that specifies one argument." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def warning(message):\n", " log('warning', message)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "warning('this is a warning')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Create a closure from a function that specifies an argument." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def create_logger(severity):\n", " def logger(message):\n", " log(severity, message)\n", " return logger" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "warning2 = create_logger('warning')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "warning2('this is a warning')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Create a partial function." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import functools" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "warning3 = functools.partial(log, 'warning')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "warning3" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "warning3.func is log" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "warning3.args, warning3.keywords" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "warning3('this is a warning')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Use a bound method." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SENTENCE_PUNCUATION = '.?!'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sentence = 'This is a sentence!'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sentence[-1] in SENTENCE_PUNCUATION" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "'.' in SENTENCE_PUNCUATION" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SENTENCE_PUNCUATION.__contains__('.')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "SENTENCE_PUNCUATION.__contains__(',')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "is_end_of_a_sentence = SENTENCE_PUNCUATION.__contains__" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "is_end_of_a_sentence('.')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "is_end_of_a_sentence(',')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Create a class with a `__call__` method." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class SentenceEndsWith:\n", " def __init__(self, characters):\n", " self.punctuation = characters\n", " \n", " def __call__(self, sentence):\n", " return sentence[-1] in self.punctuation" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "is_end_of_a_sentence_dot1 = SentenceEndsWith('.')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "is_end_of_a_sentence_dot1('This is a test.')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "is_end_of_a_sentence_dot1('This is a test!')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "is_end_of_a_sentence_any = SentenceEndsWith('.!?')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "is_end_of_a_sentence_any('This is a test.')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "is_end_of_a_sentence_any('This is a test!')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Another way that mutable data can be bound to a function is with\n", "parameter evaluation, which is sometimes done by mistake." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def f1(parameter=print('The parameter is initialized now!')):\n", " if parameter is None:\n", " print('The parameter is None')\n", " return parameter" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f1()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f1() is None" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f1('Not None')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def f2(parameter=[0]):\n", " parameter[0] += 1\n", " return parameter[0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f2()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f2()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f2()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f2()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 10.3 Exercises: Binding Data with Functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import collections" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Month = collections.namedtuple(\n", " 'Month', 'name number days',\n", " verbose=True) # So it prints the definition" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Month" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "jan = Month('January', 1, 31)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "jan.name, jan.days" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "jan[0]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "feb = Month('February', 2, 28)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mar = Month('March', 3, 31)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "apr = Month('April', 4, 30)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "months = [jan, feb, mar, apr]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def month_days(month):\n", " return month.days" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "month_days(feb)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import operator" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "month_days = operator.attrgetter('days')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "month_days(feb)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "month_name = operator.itemgetter(0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "month_name(feb)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sorted(months, key=operator.itemgetter(0))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sorted(months, key=operator.attrgetter('name'))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sorted(months, key=operator.attrgetter('days'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "'hello'.upper()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "to_uppercase = operator.methodcaller('upper')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "to_uppercase('hello')" ] } ], "metadata": {}, "nbformat": 4, "nbformat_minor": 0 }