python-logo.png

Introduction to Python

Python is a neat programming language and fun to learn. It’s very popular, too.

Overview

Python

As of October, 2018, the current production versions are 3.5.6, 3.6.6, and 3.7.0. Believe it or not, the legacy 2.7 system is still supported (with 2.7.15 being the last version). Check out the list of versions, Wikipedia’s Python History and information about Python 2 versus Python 3.

NOTE: Python 2 and 3 are quite different. It is not generally possible to write code that works the same in both dialects. Python 2 is legacy, you should not be using it! However it seems to not want to die.

These notes will only cover Python 3. If you need to learn Python 2, you have come to the wrong place.

Other excellent sources you will want to browse:

Getting Started

You can use Python in two ways:

  1. Write a program (script) in a text file and then pass it to the python interpreter, or
  2. Just enter the interactive mode and start typing.

Writing simple scripts

Our first script is a one-line file:

hello.py
print('Hello, world')

Run it like this:

$ python hello.py Hello, world

Another simple script:

triple.py
for c in range(1, 100):
    for b in range(1, c):
        for a in range(1, b):
            if a * a + b * b == c * c:
                print(f'({a},{b},{c})')

And another

fib.py
"""Writes Fibonacci numbers up to and including the first commandline argument."""

import sys

n = int(sys.argv[1])

a, b = 0, 1
while b <= n:
    print(b, end=' ')
    a, b = b, a+b
print()
$ python fib.py 300 1 1 2 3 5 8 13 21 34 55 89 144 233 $ python fib.py blah blah Traceback (most recent call last): File "fib.py", line 10, in ? n = int(sys.argv[1]) ValueError: invalid literal for int(): blah

Using the REPL

In the Python REPL, the prompt is >>>. The continuation line is .... For multiline inputs, enter a blank line to finish.

$ python
Python 3.6.5 (default, Jun 17 2018, 12:13:06)
[GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 5 * 3 + 6 / 1
21.0
>>> 5 / 2
2.5
>>> 5 // 2
2
>>> 5 < 9
True
>>> 'dog'
'dog'
>>> 5 if 1>2 else 3
3
>>> s = 'hello there'
>>> s.upper()
'HELLO THERE'
>>> s.split(' ')
['hello', 'there']
>>> {'x': 4, 'y': 6}
{'y': 6, 'x': 4}
>>> x = [1, True, 7.5, 'Hello', {'name':'sparky', 'breed':'shepherd'}, []]
>>> x[2]
7.5
>>> x[4]
{'name': 'sparky', 'breed': 'shepherd'}
>>> x[4]['name']
'sparky'
>>> x,y,z = 10,20,30
>>> x,y = y,x
>>> if x == 20:
...     print('dog')
...
dog
>>> def triple(x):
...     return x * 3
...
>>> triple(5)
15
>>> triple('ho')
'hohoho'
>>> x + y + 2j
(30+2j)
>>> (5-1j)*1j
(1+5j)
>>> triple(1j)
3j

Good to Know

Here are things to keep in mind. Some of these you might have already inferred; others will show up as you learn Python.

Learning With Examples

Objects

Absolutely everything in Python is an object, and every object has an id:

>>> id(8)
4487363280
>>> id(20234235324555326437)
4490931176
>>> id(False)
4486979888
>>> id(2 < -4)
4486979888
>>> id(None)
4487080232
>>> id("hello")
4491034896
>>> id(id)
4488268752

Types

Every object has a type:

>>> type(8)
<class 'int'>
>>> type(2423542342356656575983745923479274923729399899898239443)
<class 'int'>
>>> type(False)
<class 'bool'>
>>> type(None)
<class 'NoneType'>
>>> type("Hello, how are you today?")
<class 'str'>
>>> type(2.77e6)
<class 'float'>
>>> type(3-3j)
<class 'complex'>
>>> type([4,5,6])
<class 'list'>
>>> type((9,4,'blue'))
<class 'tuple'>
>>> type({'CA':'Sacramento','HI':'Honolulu','AK':'Juneau'})
<class 'dict'>
>>> type(sum)
<class 'builtin_function_or_method'>
>>> type(lambda x: x * x)
<class 'function'>
>>> type(x for x in range(10))
<class 'generator'>
>>> int
<class 'int'>
>>> type(int)
<class 'type'>

Types are callable:

>>> str(42)
'42'
>>> bool(35443)
True
>>> bool(0)
False
>>> int(False)
0
>>> int(True)
1
>>> float(92)
92.0
>>> int(89.3221)
89
>>> int(99.99999999)
99
>>> int(-99.9999)
-99
>>> float(False)
0.0
>>> complex(5)
(5+0j)
>>> complex(3.6, 99)
(3.6+99j)
>>> int(32.4E39)
32399999999999999195056519073840702160896
>>> str(8.9E5)
'890000.0'
>>> int("32")
32
>>> list((3,7,5))
[3, 7, 5]
>>> tuple([1,2,-17,"dog"])
(1, 2, -17, 'dog')
>>> list("abc")
['a', 'b', 'c']
>>> list({"x": 5, "y": 7})
['x', 'y']
>>> str({"x": 5, "y": 7})
"{'x': 5, 'y': 7}"
Exercise: What does int("cafe") return? What about int("cafe", 16)? Why?

Sequences, Sets, and Mappings

Python has a bunch of cool compound types built-in:

TypeDescription
strimmutable, ordered sequence of values representing Unicode code points
bytesimmutable, ordered sequence of 8-bit bytes (in the range 0..255)
tupleimmutable, ordered sequence of arbitrary objects
listmutable, ordered sequence of arbitrary objects
bytearraymutable, ordered sequence of 8-bit bytes (in the range 0..255)
frozensetimmutable, unordered collection of arbitrary objects
setmutable, unordered collection of arbitrary objects
dictmutable dictionary (but the keys must be immutable)

For all sequences s and t, sequence elements x, and integers i, j, k, and n, you can write:

    s[i]       s[i:j]     s[i:j:k]    s[:i]      s[i:]
    len(s)
    x in s     x not in s
    s + t      s * n
    s < t      s <= t     s == t      s != t     s >= t      s > t
    iter(x)

Examples:

>>> a = [10,20,30,40,50,60,70,80,90,100]
>>> a[3]
40
>>> a[3:5]
[40, 50]
>>> a[8:]
[90, 100]
>>> a[:2]
[10, 20]
>>> a[1:7:3]
[20, 50]
>>> 38 in a
False
>>> 98 not in a
True
>>> len(a)
10
>>> b = [16,32,64]
>>> a + b
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 16, 32, 64]
>>> a < b
True
>>> a == b
False
>>> c = [16,32,64]
>>> b == c
True
>>> b is c
False
>>> c * 5
[16, 32, 64, 16, 32, 64, 16, 32, 64, 16, 32, 64, 16, 32, 64]

See the Python reference for details on the comparison operators.

In case you haven’t heard the term immutable before:

>>> point = (3, 5)
>>> point[0]
3
>>> point[1] = 3
Traceback (most recent call last):
  File "", line 1, in 
TypeError: 'tuple' object does not support item assignment

Functions

Functions are introduced with def, and called with the usual f(x) notation. It is common, but not required, for the first statement in the function body to be a plain string to serve as the function’s documentation.

stats.py
"""This script that displays the mean and median of
an array of values, passed in on the command line.
"""

import sys
import operator

def median(a):
    """Return the median of sequence a."""
    a = sorted(a)
    length = len(a)
    if length % 2 == 1:
        return a[length // 2]
    else:
        return (a[length // 2] + a[length // 2 - 1]) / 2

def mean(a):
    """Return the mean of the values in sequence a."""
    return sum(a) / len(a)

print("input array is:", sys.argv)
numbers = [int(x) for x in sys.argv[1:]];
print("list is:", numbers)
print("sum is:", sum(numbers))
print("mean is:", mean(numbers))
print("median is:", median(numbers))
print("list is:", numbers, "(just making sure it was not changed)")
Exercise: Find out how, given a function, you can extract its documenatation string.

Positional and Keyword Arguments

When calling functions, you can specify the names of the parameters. This is freaking awesome.

>>> def f(x, y, z):
...     return (x, y, z)
...
>>> f(x=1, y=8, z=20)
(1, 8, 20)
>>> f(z=1, y=5, x=10)
(10, 5, 1)
>>> f(20, z=9, y=7)
(20, 7, 9)
>>> f(x=1, 2, 3)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
>>> def line(x1, y1, x2, y2, color, thickness, style):
...    pass
...
>>> line(color="red", thickness=1, style="dashed", x2=9, x1=4, y1=10, y2=8)

If the argument does not include the paramter name, it’s a positional argument, otherwise it is a keyword argument, also called a kwarg.

It’s not just the flexibility of being able to specify the arguments in any order that is the most awesome; it’s just so incredibly readable. You don’t have to look elsewhere to see what the call means.

Exercise: Read Bret Victor’s Learnable Programming. What principle of learnable programming is facilitated by Python’s keyword arguments?

Default Arguments

You can specify values that a parameter will take if no matching argument is supplied.

>>> def f(x, y=5, z=10):
...     return (x, y, z)
...
>>> f(1)
(1, 5, 10)
>>> f(1, 7)
(1, 7, 10)
>>> f(1, 8, 20)
(1, 8, 20)
>>> f(2, 3, 5, 7)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: f() takes from 1 to 3 positional arguments but 4 were given
>>> f(2, z=5)
(2, 5, 5)
>>> f(x=4, y=9)
(4, 9, 10)
>>> f(z=1, x=10, y=3)
(10, 3, 1)
>>> def line(x1, y1, x2, y2, color="black", thickness=1, style="solid"):
...    pass

You can roll up excess arguments!

You can have calls with an unlimited number of arguments. The single starred parameter collects the excess positional arguments into a tuple.

>>> def g(x, *y):
...     return f'x is {x} and y is {y}'
...
>>> g(4)
'x is 4 and y is ()'
>>> g(3, 4)
'x is 3 and y is (4,)'
>>> g(8,3,4,5,6,2,3)
'x is 8 and y is (3, 4, 5, 6, 2, 3)'

The double-starred argument will become a dictionary of all excess keyword arguments:

>>> def g(x, **y):
...     return f'x is {x} and y is {y}'
...
>>> g(10)
'x is 10 and y is {}'
>>> g(1, a=3, b=5)
"x is 1 and y is {'a': 3, 'b': 5}"
>>> g(p='hello', x=8)
"x is 8 and y is {'p': 'hello'}"
Exercise: What is g(1, x=5)? Why?

Here’s what the docs say:

If the form *identifier is present, it is initialized to a tuple receiving any excess positional parameters, defaulting to the empty tuple. If the form **identifier is present, it is initialized to a new ordered mapping receiving any excess keyword arguments, defaulting to a new empty mapping of the same type. Parameters after * or *identifier are keyword-only parameters and may only be passed used keyword arguments.

Woah so that means not only can we mix positional and keyword arguments, but we can also force callers to use certain (and only those certain!) keyword arguments.

Exercise: Explain in English the intent of each of these functions, and create sample invocations for each:
  • def f1(x, *y, z): print(x, y, z)
  • def f2(x, *y, **z): print(x, y, z)
  • def f3(x, *, y, z): print(x, y, z)
  • def f4(*, x, y, z): print(x, y, z)
  • def f5(*x, **y): print(x, y)
  • def f6(*x, y, **z): print(x, y, z)

Higher-order Functions

Functions are objects too. They can be stored in variables. They can be passed as arguments and returned from other functions.

hof.py
"""Examples of higher order functions in Python.
"""

# First, some plain old NAMED functions
def isOdd(x): return x % 2 == 1
def twoXPlusY(x, y): return 2 * x + y
def square(x): return x * x

# We can also use lambdas (UNNAMED functions)
addSix = lambda x: x + 6

# Functions can accept functions as parameters and even return functions
def compose(f, g):
    return lambda x: f(g(x))

def twice(f):
    return compose(f, f)

# Let's make some calls
addSixThenSquare = compose(square, addSix)
addTwelve = twice(addSix)
assert addSixThenSquare(9) == 225
assert twice(square)(3) == 81
assert addTwelve(100) == 112

Note: lambda may look cool, and it is very common in Haskell, Lisp, Scheme, Clojure, and JavaScript, but it is not common in Python. You’ll generally see list comprehensions and explicit for-loops.

Scope

Python functions have their own local variables (including parameters), just like most lexically scoped languages. You can read non-locals but writing to them requires global or nonlocal.

scopedemo.py
dog = "Sparky"
rat = "Oreo"
def f():
    dog = "Rex"                          # This makes a new local variable
    global rat
    rat = "Cinnamon"                     # This assigns to the global variable
    print("In function:", (dog, rat))    # Uses locals when available, else looks outside

print("Outside function:", (dog, rat))
f()
print("Outside function:", (dog, rat))

Closures

A closure is an inner function that retains references to variables in an enclosing function, even after the enclosing function has gone. Simple example:

closure_demo.py
def sequence(start, delta):
    value = start
    def advance():
        nonlocal value
        current = value
        value += delta
        return current
    return advance

s = sequence(start=10, delta=3)

assert(s() == 10)
assert(s() == 13)
assert(s() == 16)

List Comprehensions

A list comprehension just a list written with an expression or expressions used to generate the list elements. They are generally used instead of map and filter.

>>> a = [1, 2, 10, 6]
>>> b = [3, 0, 4, -8]
>>> [4 * x for x in a]
[4, 8, 40, 24]
>>> [2 / x + 5 for x in a if x < 4]
[7.0, 6.0]
>>> [x + y for x in a for y in b]
[4, 1, 5, -7, 5, 2, 6, -6, 13, 10, 14, 2, 9, 6, 10, -2]
>>> [(x, x**3) for x in a]
[(1, 1), (2, 8), (10, 1000), (6, 216)]
>>> [x + y for x in a for y in b if y <= 0]
[1, -7, 2, -6, 10, 2, 6, -2]
>>> [a[i] * b[i] for i in range(len(a))]
[3, 0, 40, -48]
>>> [str(round(355/113.0, i)) for i in range(1,6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']
>>> [(i, 2**i) for i in range(10)]
[(0, 1), (1, 2), (2, 4), (3, 8), (4, 16), (5, 32), (6, 64), (7, 128), (8, 256), (9, 512)]
>>> [x for x in [y*y for y in range(10)] if x % 2 == 0]
[0, 4, 16, 36, 64]
>>> names = ["ALICE", "BOb", "cAROl", "daVE"]
>>> [(n.lower(), n.upper()) for n in names]
[('alice', 'ALICE'), ('bob', 'BOB'), ('carol', 'CAROL'), ('dave', 'DAVE')]
>>> [i for i in range(10,30) if i not in range(5, 40, 2)]
[10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
>>> [(a,b,c) for c in range(1,30) for b in range(1,c) \
      for a in range(1,b) if a*a+b*b==c*c]
[(3, 4, 5), (6, 8, 10), (5, 12, 13), (9, 12, 15), (8, 15, 17),
(12, 16, 20), (15, 20, 25), (7, 24, 25), (10, 24, 26), (20, 21, 29)]

Python list comprehensions are:

Modules

Modules are trivial in Python: no special syntax is required: the module name is just the file name. For example, put this code in roman.py:

roman.py
"""A module containing a single subroutine that returns the roman
numeral representation of an integer."""

def to_roman(n):
    """Returns a string representing the roman numeral for n"""

    if n <= 0 or n >= 4000:
        raise ValueError(f'{n} is too big or has no Roman equivalent')

    map = [
        (1000, 'M'), (900, 'CM'), (500, 'D'), (400, 'CD'), (100, 'C'), (90, 'XC'),
        (50, 'L'), (40, 'XL'), (10, 'X'), (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I')]

    result = []
    for value, name in map:
        while (value <= n):
            result.append(name)
            n -= value
    return ''.join(result)

if __name__ == "__main__":
    n = int(input('Enter an integer: '))
    print(f'{n} is {to_roman(n)}')
    print('And here are other interesting roman numerals')
    for n in [3, 89, 22, 11]:
        print(f'{n} is {to_roman(n)}')

Note the if __name__ == "__main__": that guards all the script-level code. This allows the module to be run as a top-level script if you like, since when you run a script from the commandline, __name__ will be "__main__", but when imported, __name__ will be the name of the module.

Read more about this important Python idiom at StackOverflow.

Here is a unit tester that uses it:

romantest.py
import roman
import unittest

class TestRoman(unittest.TestCase):

    def test_proper_conversions(self):
        self.assertEqual(roman.to_roman(3), 'III')
        self.assertEqual(roman.to_roman(89), 'LXXXIX')
        self.assertEqual(roman.to_roman(1002), 'MII')
        self.assertEqual(roman.to_roman(11), 'XI')
        self.assertEqual(roman.to_roman(444), 'CDXLIV')
        self.assertEqual(roman.to_roman(1), 'I')
        self.assertEqual(roman.to_roman(3999), 'MMMCMXCIX')

    def test_out_of_range(self):
        self.assertRaises(ValueError, roman.to_roman, 0)
        self.assertRaises(ValueError, roman.to_roman, 4000)

    def test_bad_types(self):
        self.assertRaises(TypeError, roman.to_roman, [1,2,3])
        self.assertRaises(TypeError, roman.to_roman, "string")
        self.assertRaises(TypeError, roman.to_roman, {})
        self.assertRaises(TypeError, roman.to_roman, (1,))

if __name__ == '__main__':
    unittest.main()
$ python romantest.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

Exceptions

Here is an illustration of the try statement

romanfilter.py
"""A simple script that reads from integers from standard
input and writes their roman equivalents to standard output.
The input file is assumed to have one integer per line.
Blank lines are allowed and will be skipped.
"""

import roman, sys, string

for line in sys.stdin.readlines():
    line = line.strip()
    if line:
        try:
            print(f'{line} is {roman.to_roman(int(line))}')
        except Exception as e:
            print(f'{type(e).__name__}: {e}')

Classes

The Basics

You can probably figure this out by example. By the way, in Python we say that instances of classes have attributes (rather than “fields,” “slots,” “members,” or “variables”).

circle.py
import math

class Circle:
    "A circle with a 2-D center point and a radius."

    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius

    def area(self):
        "Returns the area of the circle"
        return math.pi * (self.radius ** 2)

    def perimeter(self):
        "Returns the circumference of the circle"
        return math.pi * self.radius * 2

    def expand(self, factor):
        "Increases the radius by the given factor"
        self.radius *= factor
        return self

    def move(self, dx, dy):
        "Moves the center point by <dx, dy>"
        self.x += dx
        self.y += dy
        return self

    def __repr__(self):
        return f'Circle at ({self.x},{self.y}) with r={self.radius}'

Using the class:

>>> from circle import Circle
>>> c = Circle(4, 3, 10)
>>> c
Circle at (4,3) with r=10
>>> c.area()
314.1592653589793
>>> c.perimeter()
62.83185307179586
>>> c.move(3,2)
Circle at (7,5) with r=10
>>> c.move(-3,2).expand(5)
Circle at (4,7) with r=50
>>> Circle.__doc__
'A circle with a 2-D center point and a radius.'
>>> Circle.area.__doc__
'Returns the area of the circle'
>>> >>> Circle.area(c)
7853.981633974483
>>> >>> c.area()
7853.981633974483
Exercise: Try help(Circle).

The important concepts illustrated by this example are:

Inheritance and Polymorphism

Nothing too unusual here:

animals.py
"""A module with talking animals."""

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(f'{self.name} says {self.sound()}')

class Cow(Animal):
    def __init__(self, name):
        super(Cow, self).__init__(name)

    def sound(self):
        return 'moo'

class Horse(Animal):
    def __init__(self, name):
        super(Horse, self).__init__(name)

    def sound(self):
        return 'neigh'

class Sheep(Animal):
    def __init__(self, name):
        super(Sheep, self).__init__(name)

    def sound(self):
        return 'baaaaa'

if __name__ == '__main__':
    s = Horse('CJ')
    s.speak()
    c = Cow('Bessie')
    c.speak()
    Sheep('Little Lamb').speak()

Class attributes

You can create an attribute of the class object itself.

>>> class Dog(object):
...     family = 'canine'
...     def __init__(self, n):
...         self.name = n
...
>>> a = Dog('Sparky')
>>> b = Dog('Spike')
>>> (a.name, b.name, a.family, b.family, Dog.family)
('Sparky', 'Spike', 'canine', 'canine', 'canine')
>>> Dog.family = 'Canine'
>>> (a.name, b.name, a.family, b.family, Dog.family)
('Sparky', 'Spike', 'Canine', 'Canine', 'Canine')
Exercise: What would have happened if instead of changing Dog.family, we changed a.family instead? Why?

Operators

Operators are really methods of a class that get a nice syntax via rewriting rules.

>>> x = 3
>>> x.__add__(4)
7

See the Operators section in the Python Reference Manual for the complete list.

Iterators

An iterator is an on-demand, or lazy, sequence—values are generated only when needed. Iterators are space-efficient and allow for inifinite seqeunces.

for line in file.readlines():   # readlines produces a list, not good for large files
    # ...

with

for line in file:               # better to read each line as we go!
    # ...

An iterator is any object with two methods:

Any object that has an __iter__() method that returns an iterator can be used in a for statement.

iteratordemos.py
"""A couple of interesting iterators."""

class fibs:
    """Fibonacci iterator (infinite): 0, 1, 1, 2, 3, 5, 8, ..."""

    def __init__(self):
        self.a, self.b = 0, 1

    def __next__(self):
        current, self.a, self.b = self.a, self.b, self.a + self.b
        return current

    def __iter__(self):
        return self

class collatz:
    """Collatz sequence iterator, supply start value at initialization"""

    def __init__(self, start):
        self.n = start
        self.started = False

    def __next__(self):
        if not self.started:
            self.started = True
            return self.n
        elif self.n == 1:
            raise StopIteration
        elif self.n % 2 == 0:
            self.n = self.n // 2
        else:
            self.n = 3 * self.n + 1
        return self.n

    def __iter__(self):
        return self

if __name__ == "__main__":

    # Demonstrate fibonacci iterator
    f = fibs()
    for i in f:
        print(i, end=' ')
        if i > 1000000:
            break;
    print()
    print()

    # Demonstrate collatz iterator
    c = collatz(47)
    for i in c:
        print(i, end=' ')
    print()
    print()

Generators

In practice, you don’t always write iterators yourself. Instead, you make generators, which make the iterators for you.

generatordemos.py
"""A couple of interesting generators."""

def fib():
    "Fibonacci generator (infinite): 0, 1, 1, 2, 3, 5, 8, ..."
    a, b = 0, 1
    while True:
        current, a, b = a, b, a + b
        yield current

def collatz(n):
    "Collatz generator starting at n"
    while True:
        yield n
        if n == 1:
            return
        elif n % 2 == 0:
            n = n // 2
        else:
            n = 3 * n + 1

if __name__ == "__main__":

    # Show off the fibonacci generator
    for i in fib():
        print(i, end=' ')
        if i > 1000000:
            break;
    print()
    print()

    # Show off the collatz generator
    for i in collatz(47):
        print(i, end=' ')
    print()
    print()

If the generator is only going to be used once, you can use a generator expression, which looks like a list comprehension except it uses parentheses rather than brackets. Here are two cool examples from the Python documentation:

sum_of_squares = sum(i*i for i in range(10))
unique_words = set(word for line in page for word in line.split())

Decorators

A decorator is a callable that takes a function as an argument and returns, usually, another function (though technically it can return anything). They are generally invoked with the @ syntax which replaces the name of a function with the decorated version. Example:

decoratordemo.py
def traced(f):
    def log_then_call(*args, **kwargs):
        print('Calling', f.__name__)
        f(*args, **kwargs)
    return log_then_call

@traced
def greet(name):
    print('Hello,', name)

greet('Alice')

# The decorator syntax is just shorthand for greet = traced(greet)

Probably the two most common decorators you will encounter are @property and @classmethod:

rectangle.py
class Rectangle(object):
    def __init__(self, w, h):
        self.width = w
        self.height = h

    @property
    def area(self):
        return self.width * self.height

    @classmethod
    def from_string(cls, description):
        size = [int(dimension.strip()) for dimension in description.split(',')]
        return Rectangle(size[0], size[1])

if __name__ == '__main__':
    r = Rectangle.from_string(' 7 ,6')
    print(r.area)

When writing webapps, it’s common to create decorators like, say, @authenticated, so that functions that happen when a web request is received will automatically check whether there is a logged-in user. So much better than dropping if-statements into all those functions!

By the way, here is a nice intro to decorators.

Reference Material

Keywords

Keywords cannot be used as ordinary identifiers.

False      class      finally    is         return
None       continue   for        lambda     try
True       def        from       nonlocal   while
and        del        global     not        with
as         elif       if         or         yield
assert     else       import     pass
break      except     in         raise

Grammar

Types

From the Reference Manual section on the standard type hiearchy:

TypeNotes
NoneType contains only one value: None
NotImplementedTypecontains only one value: NotImplemented
ellipsis contains only one value: Ellipsis
Number all numbers are immutable
Integer integers
int range limited only by available memory
bool contains only two values: True, False
float hardware’s double-precision type
complex pair of hardware doubles (z.real, z.imag)
Sequence finite, ordered, indexed from 0. Supports len(), a[i], a[i:j], a[i:j:k]
Immutable Sequence a sequence that cannot have its value changed
str contain code units
bytes contain bytes
tuple written as (), (x,), (x,y), (x,y,z), etc.
Mutable Sequence subscripted and slice forms can be lvalues; del statement okay
list written as [], [x], [x,y], [x,y,z], etc.
bytearray contain bytes
Set unordered, finite collection of unique, immutable objects
set mutable
frozenset immutable
Mapping something indexable with the a[k] syntax, k can be almost anything, not just an integer
dict the only kind of mapping type
Callable anything supporting the call syntax
function created by a function definition (user-defined function)
method basically a function defined within a class
generator any function with a yield statement
Coroutine Function a function defined with async def
Async Generator
Function
a function defined with async def and has a yield statement
Built-in Function wrapper around a C function (e.g. len(), math.sin())
Built-in Method wrapper around a C function, w/ implicit __self__ argument
Class  a class.
Class Instance any instance of a class with a __call__() method
module a collection of definitions and statements put into a file and imported into other code
Custom class created by a class definition
Class instance an object created by invoking a constructor
I/O an open file, created with open() and related operations. sys.stdin and sys.stdout are examples.
Internal used internally, but exposed to the programmer
Code bytecode. Immutable.
Frame execution frames, occurring in traceback objects
Traceback created when an exception is thrown, accessible within the tuple returned by sys.exc_info()
Slice examples: a[i:j:step], a[i:j, k:n], a[..., i:j]
Static Method created by built-in staticmethod() constructor
Class Method created by built-in classmethod() constructor

__builtins__

These identifiers are always available. You can see these identifiers with:

>>> ' '.join(sorted(k for k in __builtins__.__dict__))

Here is the list of module contents for Python 3.6:

ArithmeticError AssertionError AttributeError BaseException BlockingIOError BrokenPipeError BufferError BytesWarning ChildProcessError ConnectionAbortedError ConnectionError ConnectionRefusedError ConnectionResetError DeprecationWarning EOFError Ellipsis EnvironmentError Exception False FileExistsError FileNotFoundError FloatingPointError FutureWarning GeneratorExit IOError ImportError ImportWarning IndentationError IndexError InterruptedError IsADirectoryError KeyError KeyboardInterrupt LookupError MemoryError ModuleNotFoundError NameError None NotADirectoryError NotImplemented NotImplementedError OSError OverflowError PendingDeprecationWarning PermissionError ProcessLookupError RecursionError ReferenceError ResourceWarning RuntimeError RuntimeWarning StopAsyncIteration StopIteration SyntaxError SyntaxWarning SystemError SystemExit TabError TimeoutError True TypeError UnboundLocalError UnicodeDecodeError UnicodeEncodeError UnicodeError UnicodeTranslateError UnicodeWarning UserWarning ValueError Warning ZeroDivisionError _ __build_class__ __debug__ __doc__ __import__ __loader__ __name__ __package__ __spec__ abs all any ascii bin bool bytearray bytes callable chr classmethod compile complex copyright credits delattr dict dir divmod enumerate eval exec exit filter float format frozenset getattr globals hasattr hash help hex id input int isinstance issubclass iter len license list locals map max memoryview min next object oct open ord pow print property quit range repr reversed round set setattr slice sorted staticmethod str sum super tuple type vars zip

Standard Modules

There are a few hundred standard modules in the Python Library.

Python in Practice

You want to get really good at Python! You want to write pythonic code. You want to be a Pythonista. How can you do this? A little advice:

When you start writing large-scale Python applications, you will need to, among other things: