Advanced Python
Advanced Python
Overview
This tutorial, presented at PyCon 2013 in San Jose by Stuart Williams (stuart@swilliams.ca), is intended for In-
termediate Python users looking for a deeper understanding of the language. It attempts to correct some common
misperceptions of how Python works. Python is very similar to other programming languages, but quite different in
some subtle but important ways.
You’ll learn by seeing and doing. We’ll mostly use the interactive Python interpreter prompt, aka the Read Eval Print
Loop (REPL). I’ll be using Python 2.7 but most of this will work identically in 3.x and I’ll point out where there are
differences.
Most exercise sections start out simple but increase quickly in difficulty in order to give more advanced students a
challenge. Expect to be hurried along well before everyone has completed the entire section!
I am only providing a printed copy of these exercises during the tutorial because by typing them yourselves you will
learn more.
If you’re working on this handout without having been in the tutorial, you’ll get more out of it if you watch the
video of the tutorial which has many explanations that aren’t in the handout. License: This PyCon 2013 Python
Epiphanies Tutorial by Stuart Williams is licensed under a Creative Commons Attribution-Share Alike 2.5 Canada
License (http://creativecommons.org/licenses/by-sa/2.5/ca/).
>>> my_namespace = {} 6
>>> my_namespace 7
>>> my_namespace[’a’] = 7 8
>>> my_namespace 9
>>> my_namespace[’a’] 10
>>> my_namespace[’a’] = 8 11
>>> my_namespace 12
>>> my_namespace[’a’] 13
1
>>> my_namespace[’s’] = ’March’ 14
>>> my_namespace 15
>>> my_namespace[’s’] 16
>>> del my_namespace[’a’] 17
>>> my_namespace 18
>>> my_namespace[’a’] 19
>>> dir() 20
>>> a = 7 21
>>> dir() 22
>>> a 23
>>> a = 8 24
>>> a 25
>>> s = ’March’ 26
>>> dir() 27
>>> s 28
>>> del a 29
>>> dir() 30
>>> a 31
>>> dir() 32
>>> my_namespace = {} 33
>>> my_namespace[’r’] = {} 34
>>> my_namespace[’r’][’x’] = 1.0 35
>>> my_namespace[’r’][’y’] = 2.0 36
>>> my_namespace[’r’][’x’] 37
>>> my_namespace[’r’] 38
>>> my_namespace 39
>>> class Record: 40
... pass
>>> r = Record 41
>>> r.x = 1.0 42
>>> r.y = 1.0 43
>>> r.x 44
>>> r.y 45
>>> r 46
>>> dir() 47
Objects
Everything in Python is an object and has:
∙ a single value,
∙ a single type,
∙ some number of attributes
∙ a single id,
2
∙ (zero or) one or more names (in one or more namespaces),
∙ and usually (indirectly), one or more base classes.
A single value:
>>> 1 48
>>> 1.0 49
>>> ’hello’ 50
>>> (1, 2, 3) 51
>>> [1, 2, 3] 52
A single type:
>>> type(1) 53
>>> type(1.0) 54
>>> type(’hello’) 55
>>> type((1, 2, 3)) 56
>>> type([1, 2, 3]) 57
>>> dir(1) 58
>>> 1.__doc__ 59
>>> (1).__doc__ 60
>>> (1).__class__ 61
>>> (1.0).__class__ 62
>>> 1.0.__class__ 63
>>> ’hello’.__class__ 64
>>> ’mississippi’.count 65
>>> ’mississippi’.count(’s’) 66
>>> (1, 2, 3).index 67
>>> [1, 2, 3].pop 68
A single id:
>>> id(1) 69
>>> id(1.0) 70
>>> id(’hello’) 71
>>> id((1, 2, 3)) 72
>>> id([1, 2, 3]) 73
Base classes:
3
>>> ’hello’.__class__ 76
>>> type(’hello’) is ’hello’.__class__ is str 77
>>> ’hello’.__class__.__bases__ 78
>>> ’hello’.__class__.__bases__[0] 79
>>> ’hello’.__class__.__bases__[0].__bases__ 80
>>> inspect.getmro(type(’hello’)) 81
>>> dir() 82
>>> i = 1 83
>>> i 84
>>> dir() 85
>>> type(i) 86
>>> id(i) 87
>>> j = 1 88
>>> dir() 89
>>> id(j) 90
>>> id(i) == id(j) 91
>>> i is j 92
>>> m = [1, 2, 3] 93
>>> m 94
>>> n = m 95
>>> n 96
>>> dir() 97
>>> m is n 98
>>> m[1] = ’two’ 99
>>> m 100
>>> n 101
4
>>> int.__div__ 114
>>> int.__div__ == int.__truediv__ 115
>>> int.__div__ = int.__truediv__ 116
Namespaces
A namespace is a mapping from valid identifier names to objects. Think of it as a dictionary.
Assignment is a namespace operation, not an operation on variables or objects.
A scope is a section of Python code where a namespace is directly accessible.
For a directly accessible namespace you access values in the (namespace) dictionary by key alone, e.g. s instead of
my_namespace[’s’].
For indirectly accessible namespace you access values via dot notation, e.g. dict.__doc__ or sys.version.major.
The (direct) namespace search order is (from http://docs.python.org/tutorial):
∙ Second: the namespaces of enclosing functions, searched starting with the nearest enclosing scope; (or the
module if outside any function)
∙ Third: the middle scope contains the current module’s global names
∙ Fourth: the outermost scope is the namespace containing built-in names
All namespace changes happen in the local scope (i.e. in the current scope in which the namespace-changing code
executes):
∙ assignment
∙ import
∙ def
∙ class
∙ del
5
>>> f1() 122
>>> def f2(): 123
... def len():
... print("In f2’s len(), len = {}".format(len))
... return "From f2’s len()"
... print(’In f2(), len = {}’.format(len))
... return len()
>>> x = 1 133
>>> def test1(): 134
... print(’In test1 x == {}’.format(x))
>>> x 137
>>> test2() 138
>>> x 139
>>> x 141
>>> test3() 142
>>> x 143
6
>>> def test4(): 146
... global x
... print(’In test4 before, x == {}’.format(x))
... x = 4
... print(’In test4 after, x == {}’.format(x))
>>> x 147
>>> test4() 148
>>> x 149
“If a name is declared global, then all references and assignments go directly to the middle scope containing the
module’s global names. Otherwise, all variables found outside of the innermost scope are read-only (an attempt to
write to such a variable will simply create a new local variable in the innermost scope, leaving the identically named
outer variable unchanged).” [Python tutorial section 9.2 at http://docs.python.org/tutorial]
7
>>> for typ in [type(type), type(all)]: 165
... names = bnames.pop(typ)
... print(typ)
... print(fill(’ ’.join(names), 60))
... print()
>>> x 171
>>> locals()[’x’] 172
>>> locals()[’x’] = 1 173
>>> locals()[’x’] 174
>>> x 175
>>> dir() 176
Most builtins are unsurprising cases of type exception, type built-in function, or type. Explore some of
the following suprising ones via introspection (e.g. type, inspect.getmro, and help) or the Python documen-
tation:
∙ bytes
∙ enumerate, reversed
Namespace Changes
These change or modify a namespace:
∙ assignment
8
∙ [globals() and locals()]
∙ import
∙ def
∙ class
∙ del
File structure:
9
folder1/
file1.py
module1/
__init__.py -- zero length
file1.py:
attribute1 = 1
Functions
10
... *args, **kwargs):
... """
... A function with regular and keyword arguments.
... """
... print(’arg1: {0}, arg2: {1}, ’
... ’kwarg1: {2}, kwarg2: {3}’
... .format(arg1, arg2, kwarg1, kwarg2))
... print(’args:’, repr(args))
... print(’kwargs:’, repr(kwargs))
Exercises: Functions
11
>>> locals() 266
>>> name = ’Dad’ 267
>>> ’Hi {name}’.format(**locals()) 268
12
>>> def test1(s): 291
... print(’Before:’, s)
... s += ’ there’
... print(’After:’, s)
Decorators
13
... .format(n))
... return str(function(n))
... print(’called call_str_on({})’
... .format(function))
... return new_function
14
Here’s equivalent code without using @decorator syntax:
>>> x 330
>>> x = call(x) 331
>>> x 332
∙ a single id,
∙ a single value,
∙ some number of attributes (part of its value),
∙ a single type,
∙ (zero or) one or more names (in one or more namespaces),
∙ and usually (indirectly), one or more base classes.
Many objects are instances of classes. The type of such an object is its class.
Classes are instances of metaclasses. So the type of a class is its metaclass, i.e. type(type(anObject)) is a
metaclass.
Are classes and metaclasses objects?
1. The class statement, which starts a block of code, creates a new namespace and all the name assignments in the
block, i.e. assignment and function definitions, are made in that new namespace. It also creates a name for the class in
the namespace of the module where it appears.
2. Instances of a class are created by calling the class: ClassName() or ClassName(parameters).
ClassName.__init__(<new object>, ...) is called automatically, passing as first parameter an object,
the new instance of the class, which was created by a call to __new__().
3. Accessing an attribute method_name on a class instance returns a method object, if method_name references
a method (in ClassName or its superclasses). A method object binds the class instance as the first parameter to the
method.
15
... #
... def add(self, value):
... """Add a value to the number."""
... return self.amount + value
16
>>> # Methods live in classes, not instances. 366
>>> # Let’s make a mistake. 367
>>> number4.mul = multiply_by 368
>>> number4.mul 369
>>> number4.mul(5) 370
>>> number4.mul(number4, 5) 371
17
Now at the interactive prompt, similar to the demonstration above, add the method(s) required to make the following
code work, inserting ’-> ’ at the beginning of each string in the sequence passed to the prepend method.
type(name, bases, dict) is the function that gets called when a class statement is used to create a class.
This dynamic call to type is what the class statement actually triggers.
However, ”When the class definition is read, if __metaclass__ is defined then the object it references will be called
instead of type().“
__metaclass__ ”can be any callable accepting arguments for name, bases, and dict. Upon class creation, the
callable is used instead of the built-in type().“ [Language Reference section 3.4.3]
18
Exercise: The class statement
What does the following code do? Use only one of the ”2.7“ and ”3.x“ definitions of class x.
>>> x 415
We saw how decorators are applied to functions. They can also be applied to classes. What does the following code
do?
>>> y 420
19
∙ __add__, __sub__, __mul__, __div__, __floordiv__, __mod__, __divmod__, __pow__, __and__,
__xor__, __or__, __lshift__, __rshift__, __neg__, __pos__, __abs__, __invert__, __iadd__,
__isub__, __imul__, __idiv__, __itruediv__, __ifloordiv__, __imod__, __ipow__, __iand__,
__ixor__, __ior__, __ilshift__, __irshift__
∙ __int__, __long__, __float__, __complex__, __oct__, __hex__, __coerce__
∙ __radd__, __rsub__, __rmul__, __rdiv__, etc.
∙ __enter__, __exit__, __next__
20
>>> m = list(’abcdefghij’) 437
>>> m[0] 438
>>> m[-1] 439
>>> m[::2] 440
>>> s = slice(3) 441
>>> m[s] 442
>>> m[slice(1, 3)] 443
>>> m[slice(0, 2)] 444
>>> m[slice(0, len(m), 2)] 445
>>> m[::2] 446
Properties
Iterators
∙ A for loop evaluates an expression to get an iterable and then calls iter() to get an iterator.
∙ The iterator’s next() method is called repeatedly until StopIteration is raised.
∙ iter(foo)
– checks for foo.__iter__() and calls it if it exists
– else checks for foo.__getitem__(), calls it starting at zero, and handles IndexError by raising
StopIteration.
21
>>> def iter(foo): 454
... """Pseudo code for what iter() does."""
... if hasattr(foo, ’__iter__’) and callable(foo.__iter__):
... yield foo.__iter__()
... elif hasattr(foo, ’__getitem__’) and callable(foo.__getitem__):
... index = 0
... while True:
... try:
... yield foo.__getitem__(index)
... except IndexError:
... raise StopIteration
... index += 1
... else:
... raise TypeError(’{!r} object is not iterable’.format(type(foo)))
...
22
>>> for item in m: 473
... print(item)
>>> m 489
>>> for i in m: 490
... print(i)
23
>>> help(mi) 506
>>> mi.next() 507
>>> mi.next() 508
>>> mi.next() 509
>>> mi.next() 510
Exercises: Iterators
24
Iterators continued
25
>>> list(m3it) 555
>>> list(m3it) 556
Generators
26
>>> it = even(3) 574
>>> it 575
>>> it.next() 576
>>> it.next() 577
>>> it.next() 578
Exercises: Generators
Write a generator sdouble(str) that takes a string a returns that string ”doubled“ 5 times. E.g. sdbouble(’s’)
would yield these values: [’s’, ’ss’, ’ssss’, ’ssssssss’, ’ssssssssssssssss’].
27
Re-design the earlier (iterator subclass of dict) exercise to use yield in the next method.
Write a generator that returns sentences out of a paragraph. Make some simple assumptions about how sentences start
and/or end.
Write code which reads a file and produces a histogram of the frequency of all words in the file.
Explore further the itertools module.
28
>>> class Unpacker(object): 606
... slices = {
... ’header’: slice(0, 3),
... ’trailer’: slice(12, 18),
... ’middle’: slice(6, 9)
... }
... #
... def __init__(self, record):
... self.record = record
... #
... def __getattr__(self, attr):
... if attr in self.slices:
... return self.record[self.slices[attr]]
... raise AttributeError(
... "’Unpacker’ object has no attribute ’{}’"
... .format(attr))
...
29
>>> server_log 619
>>> server_log.func is log 620
>>> server_log.keywords 621
Yet another way to bind some data is to create a class and give it a __call__ method:
30
>>> jan = Month(’January’, 1, 31) 642
>>> jan.name 643
>>> jan[0] 644
>>> apr = Month(’April’, 3, 30) 645
>>> apr.days 646
>>> apr[2] 647
>>> jul = Month(’July’, 7, 31) 648
31