The Python Tutorial
I read through the Python 3.9 tutorial and wrote up these notes to help me remember certain things. Section headings and numberings follow those in the tutorial.
2 Using the interpreter
Arguments: import sys
, then use sys.argv
which is a list of strings.
The 0th element tells you how the script was run, e.g. 'c'
if it was
python -c
to run a command. The rest of the elements are the actual
arguments.
4 More control flow tools
If d
is a dict, d.items()
is an iterable producing (key, value)
pairs so that for k, v in d.items()
is a handy way to iterate over
key-value pairs.
In function calls, keyword arguments must come after any positional arguments. Definitions can specify which parameters are positional, positional-or-keyword, and keyword-only using forward slash and asterisk:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
(diagram copied from the tutorial). Without the /
and *
, arguments are positional-or-keyword.
Scope: variable references check
- the local symbol table, then
- the local tables of any enclosing functions, then
- the global table, then
- the built-in names table.
The parameters to a function call are added to the local symbol table
when it is called. The tutorial describes Python as “call by value” with
the value being an object reference and then says that “call by object
reference” would be a better name.
Wiki
refers to Python as call by sharing or call by object sharing. I think
the best way to understand the behaviour is as follows: all
modifications to the value of a passed variable are visible to the
caller, but for immutable types we simply can’t do those modifications.
If f(a)
is called and a
is an int, any assignment to a
inside the
body of f
creates a new local called a
and does not modify the
caller’s a
.
Docstrings are available with f.__doc__
.
Type annotations work like this:
def f(a: str, b: int = 2) -> float
return 0.0
5 Data structures
If l
is a list, l.pop()
pops the last element. You can also specify
an index.
l.index(x)
returns the index of the first x
in l
, a ValueError
otherwise.
As of Python 3.8, you can do assignments in expressions but must use the
“walrus” :=
l = [0, 1, 2]
while elt := l.pop():
print(elt)
This is a syntax error in 3.7.
Recall that x or y
has value either x
or y
. In short-circuited
expressions, including longer ones, you get the last thing evaluated.
6 Modules
Inside a module, you can get the module name with __name__
. This is
particularly useful when you want to write code usable as a module and
also as a standalone:
if __name__ == "__main__":
# do stuff with sys.argv, ...
Inside the conditional you can put everything you want to happen if the file is run on its own. When people import your code, that stuff won’t be run, so they can just use whatever functions or objects you define.
The effect of from module import *
can be controlled by creating a
list of strings called __all__
in module.py
.
7 IO
open(filename, mode)
returns a file object. The mode should be 'w'
for write only (erasing an existing file) 'a'
for appending to an
existing file, 'r'
for read only, 'r+'
for read and write.
8 Errors and exceptions
try
can have an else
clause which is run if there’s no exception.
Putting code here, rather than in the try, avoids accidentally catching
exceptions we didn’t mean to.
Exceptions can have values, called their argument. If you do
...
except Exception as inst:
print(inst.args)
then args
are the arguments to the exception, e.g. raise Exception('a', "b")
.
try
has an optional finally
clause which defines clean-up actions to
be executed under all circumstances. It will execute as the last task
before the try completes, whether or not the try raises an exception.
If you do with open(filename) as f:
then the file object provides its
own clean-up action (closing the file) which is automatically executed
after the suite of the with. That’s why with
is usually better in
these circumstances.
9 Classes
There’s some particularly interesting stuff about scope and namespaces here. First of all, a namespace is a name-object mapping usually implemented with a dict. The reason this stuff is in the class section is that the name-object mapping corresponding to the attributes of an object makes up a namespace.
Scope
The discussion of scope was a little tricky to follow for me. Mathematicians tend to read definitions as the once-and-for-all final description of some concept or thing, to be taken completely literally. That’s not usually a productive way to read texts about coding or CS. For example, the first explanation of a scope in the tutorial is “a textual region of a Python program where a namespace is directly accessible.” (which namespace??) Directly accessible means that an unqualified reference to a name will cause Python to use that namespace to try and find the associated object. So a scope is associated to a namespace, and is a region of the program text. Presumably references to searching a scope mean looking things up in the namespace corresponding to that scope. The namespace referenced by a scope comes up a lot (e.g. “Outside functions, the local scope references the same namespace as the global scope”), presumably again this just refers to the namespace associated to the scope. But later we get things like “the global scope of a function defined in a module is that module’s namespace” (my emphasis), or “…a new namespace is created, and used as the global scope” which can’t really be taken literally.
Here’s an attempt to be pedantically correct. The scope of a name-object binding is the region of program text where that binding is valid (this is lexical scope). In general lots of name-object bindings will have the same scope, therefore we talk about the scope of a namespace as the common scope of each of its name-object bindings.
We consider each name-object binding to have its own identity beyond simply which name and which object it links. If we bind x to 2 in one part of our program and x to 2 in a completely separate part, we don’t want to be forced into considering both parts to belong to the scope of the x-to-2 binding.
There are various kinds of namespace - for example, the built-in
namespace of built-in objects, the local namespace created each time a
function is called, and the namespace for a module. Ordinarily global
scope is the scope of the __main__
module namespace.
Python does name resolution by searching namespaces in the following order:
- the local namespace. This is the local namespace for a class/function if we are in a class/function, or outside these, the module’s (global) namespace.
- (if we are in a function or class) the local namespaces of any enclosing functions (classes)
- the global namespace of the current module. If a function was defined in a module, its global namespace scope is that module, no matter where the function was called.
- the namespace for built-in names, which is the global namespace for
the
builtin
module.
The docs have a nice example which I will copy here, lightly modified.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print(spam)
do_nonlocal()
print(spam)
do_global()
print(spam)
scope_test()
print(spam)
When scope_test()
is called, line 13 binds spam
to "test spam"
in
the local namespace for scope_test
.
On line 14, do_local()
is called. Its body has an assignment to
spam
, so spam
is bound to "local spam"
in the do_local
local
namespace. That doesn’t affect any other namespaces.
Each of the print calls on lines 14, 17, 19 look up spam
in the local
namespace of scope_test
. Since we know do_local
didn’t affect this
namespace, the first print call prints test spam
.
On line 16 do_nonlocal
is called. The nonlocal
keyword means that
spam
will be bound in the enclosing function scope_test
’s local
namespace. Therefore the local scope_test
namespace now binds spam
to "nonlocal spam"
. That’s what the line 17 print call will print.
On line 18 do_global
is called which uses the global
keyword. This
binds spam
in the global namespace and doesn’t change any other
namespaces, so the succeeding print call prints "nonlocal spam"
again.
Finally once we fall out of the function we have no local scope. The
name spam
is looked up in the global namespace where it refers to
"global spam"
because do_global
created this binding.
9.3 A First Look at Classes
When a class definition is completed a class object is created, containing the local namespace for that class. Local scope reverts to being whatever it was before the class definition was entered, and a mapping for the class name is added to the local namespace.
Both class instances and classes themselves can have attributes. These are can be accessed with dot notation attribute references.
Class methods are not functions. Suppose a class C
has a function f
defined inside it like this:
class C:
def f(self):
return 0
and we then do c = C()
so that c
is an instance of C
. Now C.f
is
a function, but the type of c.f
is method
. It isn’t a traditional
function since we call it with c.f()
whereas the function had a single
parameter. In fact, c.f()
is equivalent to C.f(c)
.
9.5 Inheritance
isinstance(object, class)
and issubclass(c1, c2)
can be useful.
9.6 Private variables
There’s no way in Python to restrict access to class attributes, but the
convention is that names prefixed with _
should be treated as
non-public.
10+11 A Brief Tour of the Standard Library
Some useful modules:
glob
can make lists of files from wildcard searcheszlip
does compressiondoctest
lets you embed tests in docstrings and run themunittest
does more sophisticated testingreprlib
helps display large/nested containersstring.Template
has complex templating supportthreading
includes locks, events, semaphoreslogging
bisect
works efficiently with sorted listsarray
has a homogeneous array type which is more space-efficient than listsheapq
does list-based heapsdecimal
has aDecimal
decimal float type, useful for financial applications (or exam mark rounding!). There are some good examples in the docs of how floating point and decimal arithmetic can differ.
12 Virtual environments and packages
The venv
module
handles these.
15 Floating point arithmetic
The actual stored value of a float is the nearest representable binary
fraction: “on most machines today” a 53b numerator and a power-of-two
denominator. You can see how your float x
is represented with
x.as_integer_ratio()
which returns a numerator-denominator pair.
There’s a fractions
module for exact rational arithmetic.