A Python Crash Course for the Statically Typed Programmer
November 15, 2013

Python is a multi-paradigm (hybrid) language. It's fully object-oriented but has strong functional roots. Note that this isn't a [beginner's tutorial](http://learnpython.org/) but a quick reference for the language and its features that should allow you to write basic Python ASAP. If you had taken any academic course that involves programming, Python will most likely resemble pseudo code to you
def factorial(n):
    if n <= 1:
        return 1
    else:
        return n * factorial(n - 1)

# or

def factorial(n):
    res = 1
    while n:
        res *= n
        n -= 1
    return res
You'll see inheritance and class diagrams along side with constructs imported from [Haskell](http://www.haskell.org) and [LISP](http://en.wikipedia.org/wiki/Lisp_%28programming_language%29). Python is dynamically-typed (as opposed to statically-typed) but has strong-typing (as opposed to Perl or Javascript)
Syntax reference (1) ``` # printing to stdout print EXPR # assignment VAR = EXPR # conditions if COND: SUITE [elif COND: SUITE] ... [else: SUITE] # for-loops for VAR in ITERABLE: SUITE [break] [continue] # while-loops while COND: SUITE [break] [continue] ```
Syntax reference (2) ``` # try-except try: SUITE except EXCEPTION [as ex]: SUITE ... [else: # iff there was no exception SUITE] [finally: # always be performed SUITE] # raising exceptions raise EXCEPTION(...) # importing modules or attributes import MODULE from MODULE import NAME # defining functions def NAME([a, [b, ...]]): SUITE [return EXPR] [yield EXPR] # defining classes class NAME([BASE, [...]]): SUITE ```
The interactive interpreter is your friend! I mapped ``F11`` on my keyboard to fire up an interpreter... it's better than any calculator you'll ever have ``` $ python Python 2.7.5 (default, May 15 2013, 22:43:36) [MSC v.1500 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> 5 + 6 11 ```
For people with a C background: * You don't declare variables - you just assign them * And you can reassign them to different types * Python doesn't have ``do-while`` (use ``while``) nor does it have ``switch`` (use ``if``s or dispatch tables) * Assignment is a statement, **not an expression**. * You cannot write ``if (a = foo()) == 5:`` * There's no such thing as passing by-value. It's always by-reference. * So you might need to explicitly **copy** objects some times * Or (preferably) create new objects from existing ones For people with a C++/Java background: * **Everything** is a first-class object * Integers, functions, types, stack frames, tracebacks, etc. * There are no privates, only conventions. Members that start with ``_`` are not to be manipulated directly. * There's no ``new``, just *invoke* the class like a function. * ``inst = MyClass(1, 2, 3)``
Duck Typing goes Nuclear In low-level programming languages, types dictate a **memory layout**. In high-level languages, on the other hand, compile-time types are merely **constraints** on what you're allowed to do with an object. Being an interpreted language, Python gives up on type-checking and instead adopts the spirit of "it's easier to ask for forgiveness than permission". Just try and see what happens.
>>> def foo(this, that):
...     return (this + that) * 2
...
>>> foo(3, 5)
16
>>> foo("hello", "world")
'helloworldhelloworld'
Python comes with lots of useful built-in types
>>> 5+3
8
>>> 27**63
149939874158678820041423971072487610193361136600
3344657118522818557991334322919287339806483L
>>> 5+3j
(5+3j)
>>> 1/(5+3j)
(0.14705882352941177-0.08823529411764706j)
>>> [1,2,3]
[1, 2, 3]
>>> [1,2,3] + [4,5,6]
[1, 2, 3, 4, 5, 6]
>>> (1,2,3)
(1, 2, 3)
>>> d={"a":4, 7:()}
>>> d["a"]
4
>>> d[7]
()
>>> True, False, None
(True, False, None)
>>> set([2,6,2,7,2,8,6])
set([8, 2, 6, 7])
String manipulation is a pleasure with Python
>>> "hello" + "world"
'helloworld'
>>> a="this is a single line string"
>>> a[5]
'i'
>>> a[5:12]
'is a si'
>>> a.upper()
'THIS IS A SINGLE LINE STRING'
>>> a.count("i")
5
>>> a.replace("i", "j")
'thjs js a sjngle ljne strjng'
>>> a.startswith("thi")
True
>>> "single" in a
True
>>> "multiple" in a
False
>>> a.split()
['this', 'is', 'a', 'single', 'line', 'string']
>>> a.split("i")
['th', 's ', 's a s', 'ngle l', 'ne str', 'ng']
Encoding strings like a boss
>>> "hello".encode("hex")
'68656c6c6f'
>>> "hello".encode("utf16")
'\xff\xfeh\x00e\x00l\x00l\x00o\x00'
>>> "hello".encode("zlib")
'x\x9c\xcbH\xcd\xc9\xc9\x07\x00\x06,\x02\x15'
String interpolation
>>> "My name is %s. You %s, prepare to %s" % ("Inigo Montoya",
... "killed my father", "die")
'My name is Inigo Montoya. You killed my father, prepare to die'
>>>
>>> "My name is %03d" % (7,)
'My name is 007'
And joining strings is surprisingly useful
>>> ":".join(["AA", "BB", "CC"])
'AA:BB:CC'
Multi-line strings
>>> b="""this is
... a multi
... line string"""
>>> b
'this is\na multi\nline string'
>>> b.splitlines()
['this is', 'a multi', 'line string']
If I had time to show you only three functions, they will be * ``help()`` * ``dir()`` * ``type()`` Everything else you can discover on your own.
>>> help
Type help() for interactive help, or help(object) for help about object.
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__']

>>> help(dir)
    dir([object]) -> list of strings

    If called without an argument, return the names in the current scope.
    Else, return an alphabetized list of names comprising (some of) the attributes

>>> dir("hello")
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__for
_mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
nter', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'in
index', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswi']

>>> type(5)
<type 'int'>
>>> type("hello")
<type 'str'>
Next, let's meet some types and learn how to convert (not **cast**) values from one type to the other
>>> int
<type 'int'>
>>> str
<type 'str'>
>>> list
<type 'list'>

>>> int(5.1)
5
>>> int("5")
5
>>> str(5)
'5'
>>> list("hello")
['h', 'e', 'l', 'l', 'o']
Types matter
>>> 5 + "6"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
>>> 5 + int("6")
11
>>> str(5) + "6"
'56'
Repr(esenation)
>>> repr(5)
'5'
>>> repr("hello")
"'hello'"
>>> print "Hello %s" % ("foo\n\tbar",)
Hello foo
        bar
>>> print "Hello %r" % ("foo\n\tbar",)
Hello 'foo\n\tbar'
Lists
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(10,20)
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> range(10,20,2)
[10, 12, 14, 16, 18]
>>> range(20,10,-2)
[20, 18, 16, 14, 12]
>>> len([2,5,2,6,2,6])
6
>>> len("hello")
5
>>> a=[2,5,2,6,2,6]
>>> max(a)
6
>>> min(a)
2
>>> b=["what", "is", "this", "thing"]
>>> max(b)
'what'
>>> max(b, key = len)
'thing'
Slicing
>>> a=range(10,20)
>>> a[0]
10
>>> a[-1]
19
>>> a[3:7]
[13, 14, 15, 16]
>>> a[7:]
[17, 18, 19]
>>> a[-3:]
[17, 18, 19]
>>> a[:-3]
[10, 11, 12, 13, 14, 15, 16]
Lambda functions
>>> lambda x: x * 2
<function <lambda> at 0x0297EEB0>
>>> f = lambda x: x * 2
>>> f(6)
12
>>> map(f, range(10))
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> filter(lambda x: x % 2 == 0, range(10))
[0, 2, 4, 6, 8]
Working with files
>>> f = open("/etc/profile")
>>> f.read(100)
'# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))\n# and Bourne compatible shell'
>>> f.readline()
's (bash(1), ksh(1), ash(1), ...).\n'
>>> f.readline()
'\n'
>>> f.readline()
'if [ "$PS1" ]; then\n'
>>> list(f)[:4]
['  if [ "$BASH" ] && [ "$BASH" != "/bin/sh" ]; then\n',
 '    # The file bash.bashrc already sets the default PS1.\n',
 "    # PS1='\\h:\\w\\$ '\n",
 '    if [ -f /etc/bash.bashrc ]; then\n']
>>> f.close()
Working with resources
>>> with open("/etc/profile") as f:
...     f.read(100)
...
'# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))\n# and Bourne compatible shell'
The ``with`` block automatically closes the file. It's more general than this, of course, as it's not limited to files. It simply ensures that the necessary cleanup that's associated with the object will be performed when you finish the ``with`` block. For example, you can use ``with`` to commit a transaction to a DB or do a rollback on failure, etc. It's a very useful pattern.
List comprehension: remember ``map`` and ``filter``? Well, it's time to forget about them
>>> [x for x in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [x * 2 for x in range(10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> [x for x in range(10) if x % 2 == 0]
[0, 2, 4, 6, 8]
>>> [x * 2 for x in range(10) if x % 2 == 0]
[0, 4, 8, 12, 16]
Yo comprendo!
>>> [i * j for i in range(1,5) for j in range(1,5)]
[1, 2, 3, 4, 2, 4, 6, 8, 3, 6, 9, 12, 4, 8, 12, 16]
>>> [[i * j for i in range(1,5)] for j in range(1,5)]
[[1, 2, 3, 4], [2, 4, 6, 8], [3, 6, 9, 12], [4, 8, 12, 16]]
>>> [" ".join(["%3d" % (i * j,) for i in range(1,5)])
...     for j in range(1,5)]
['  1   2   3   4', '  2   4   6   8', '  3   6   9  12',
 '  4   8  12  16']
And the whole multiplication table in just one line!
>>> print "\n".join([" ".join(["%3d" % (i * j,) for i in range(1,11)])
...     for j in range(1,11)])
  1   2   3   4   5   6   7   8   9  10
  2   4   6   8  10  12  14  16  18  20
  3   6   9  12  15  18  21  24  27  30
  4   8  12  16  20  24  28  32  36  40
  5  10  15  20  25  30  35  40  45  50
  6  12  18  24  30  36  42  48  54  60
  7  14  21  28  35  42  49  56  63  70
  8  16  24  32  40  48  56  64  72  80
  9  18  27  36  45  54  63  72  81  90
 10  20  30  40  50  60  70  80  90 100
Chillex! Be lazy
>>> def myfunc():
...     yield 1
...     yield 2
...     yield 3
...
>>> g=myfunc()
>>> g
<generator object myfunc at 0x02A3A8F0>
>>> g.next()
1
>>> g.next()
2
>>> g.next()
3
>>> g.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
Generators let us be as general as we wish while paying only for what we're actually using (lazy evaluation)
>>> def fib():
...     a = b = 1
...     while True:
...         yield a
...         a, b = b, a + b
...
>>> g = fib()
>>> g.next(), g.next(), g.next(), g.next(), g.next(), g.next(), g.next()
(1, 1, 2, 3, 5, 8, 13)
The ``itertools`` module has some nifty utilities that we can use
>>> import itertools
>>> list(itertools.islice(fib(),10))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
>>>
>>> list(itertools.takewhile(lambda x: x < 50, fib))
[1, 1, 2, 3, 5, 8, 13, 21, 34]
>>>
>>> list(itertools.combinations([1,2,3,4],2))
[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
>>>
>>> list(itertools.permutations([1,2,3]))
[(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
Remember list comprehensions? Forget them too. Generator comprehensions are the new black!
>>> (x for x in range(10))
<generator object <genexpr> at 0x02A37F30>
>>> (x * 2 for x in range(10))
<generator object <genexpr> at 0x02A34670>
>>> (x * 2 for x in range(10) if x % 2 == 0)
<generator object <genexpr> at 0x02A37918>
Huh? Of course you won't see anything... you have to consume the generator in order for it to produce values.
>>> list(x * 2 for x in range(10) if x % 2 == 0)
[0, 4, 8, 12, 16]
In fact, list comprehensions are a syntactic sugar for ``list(``generator comprehension``)``
List comprehensions, as the name suggests, **build a list**. This can be expensive some times, especially when you don't need the intermediate values. E.g., if you just want to get the sum of the elements, there's no need to actually hold all of them in memory. Generators are the key to efficient programming. For example, ``xrange`` is like ``range`` but returns a generator instead.
>>> sum(range(1000000000))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
MemoryError
>>> sum(xrange(1000000000))
499999999500000000L
## Object Oriented, Too ## Don't worry though. Python isn't all geared for functional programming. We have classes too!
>>> class Animal(object):
...     def __init__(self, name):
...         self.name = name
...     def drink(self):
...         print "*Gulp gulp gulp*"
...
>>> class Dog(Animal):
...     def drink(self):
...         Animal.drink(self)         # or super(Dog, self).drink()
...         print "*Tongue drips*"
...     def bark(self):
...         print "Woof woof woof"
...
>>> rex = Dog("Rex t3h Dawg")
>>> rex.drink()
*Gulp gulp gulp*
*Tongue drips*
>>> rex.bark()
Woof woof woof
Some notes for Java folks: * Constructors are inherited! Why wouldn't they?! * You can implement an exception class in Python in just one line: ``class MyException(Exception): pass`` * In Java/C# you have to reimplement all constructors as well * Python doesn't have function/method overloading * But given that most of the code is duck-typed anyway (and can introspect the received object at runtime) it doesn't make a real difference
But if we stopped here you'd think it's just a variation of Java. We're way cooler than Java! For example, instances (such as ``rex`` above) are actually just dictionaries that hold the instance's attributes.
>>> rex.__dict__
{'name' : 'Rex t3h Dawg'}
That's why we can add attributes on the fly
>>> rex.tail = "recursion"
>>> rex.__dict__
{'name' : 'Rex t3h Dawg', 'tail' : 'recursion'}
But classes ain't no different! They are just dictionaries of their methods
>>> Animal.__dict__.keys()
['__module__', 'drink', '__dict__', '__init__', '__weakref__', '__doc__']
>>>
>>> def sleep(self):
...     print "Zzzzzz"
...
>>> Animal.sleep = sleep      # monkey-patching
>>> rex.sleep()
Zzzzzz
Now it's time to discuss **special methods**. You've seen them already when we invoked ``dir("hello")`` -- they take the form of ``__xxx__``, and surprisingly or not, they make up most of what we've seen so far. Virtually all language constructs map to special methods underneath: * ``a + b`` is actually ``a.__add__(b)`` * ``a[x]`` maps to ``a.__getitem__(x)`` * ``str(a)`` invokes ``a.__str__()`` * ``a.b`` translates to ``a.__getattr__("b")`` * ``f(a,b,c)`` runs ``f.__call__(a, b, c)`` * So yes, ``f.__call__.__call__.__call__(a, b, c)`` as well * ``Dog("foo")`` creates the instance using ``__new__`` and initializes it in ``__init__`` You can invoke them yourself, of course, they're just methods:
>>> (5).__add__(6)
11
But why would you do that? Don't be silly.
Remember I said **everything** is an object? Well, I meant it.
>>> Animal.__base__
<type 'object'>
>>> Dog.__base__
<class '__main__.Animal'>
>>> Dog.__mro__
(<class '__main__.Dog'>, <class '__main__.Animal'>, <type 'object'>)
>>> type(Dog)
<type 'type'>
And it does gets mindboggling
>>> type
<type 'type'>
>>> type.__base__
<type 'object'>
>>> type(type)
<type 'type'>
>>> type(object)
<type 'type'>
The MRO (method resolution order) is actually very important. It determines what happens when you resolve attributes (``__getattr__``) on an object. For instance, ``rex.foo`` will first try ``rex.__dict__``, move up to ``Dog.__dict__`` and then to ``Animal.__dict__``, until we reach ``object.__dict__`` where we'll fail. And that's the whole object model. Dictionaries are also crucial to functions. A function is basically just code that evaluates in a local scope and has access to a global scope (module-level). Each of these scopes is... a dictionary. When you assign a variable, you basically insert an element into the scope dictionary.
>>> globals()
{'__builtins__': <module '__builtin__' (built-in)>}
>>> def f(a, b):
...     print locals()
...     c = a+b
...     print locals()
...
>>> globals()
{'__builtins__': <module '__builtin__' (built-in)>, 'f': <function f at 0x028CEEB0>}
>>>
>>> f(6,7)
{'a': 6, 'b': 7}
{'a': 6, 'c': 13, 'b': 7}
And no Python tutorial can do without
## When in Doubt ##
>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.