In [146]:
from IPython.core.display import Image

Die Python Programmiersprache

Wᴇɪʟ ɴɪᴄʜᴛ ᴊᴇᴅᴇ Sᴘʀᴀᴄʜᴇ ʙᴇɴᴜᴛᴢᴇʀғʀᴇᴜɴᴅʟɪᴄʜ ɪsᴛ

Eine flotte 40-Minuten Python Einführung.
Bitte unterbrecht mich sofort bei Fragen, Korrekturen oder Anregungen!

lukas-prokop.at/talks/glt14-python

Vortrag von Lukas Prokop
Grazer Linuxtage // 05.04.2014

"It was nice to learn Python; a nice afternoon" (Don Knuth, SAT 2012 in Trento, Italy)

Installation

python 2.x Linux python 2.6 or 2.7 is installed. Run python in the terminal.
Windows Manual installation via python.org
py3k Linux Install py3k via package manager
Windows Manual installation via python.org

Gentoo and Arch Linux: /usr/bin/python points to py3k per default
In this presentation I am using ipython3 notebook (a different REPL)

Py3k?

We are in the progress of migrating from python version 2 to 3. See Gerald's talk last year for migration tips.

Python 2.7 is the newest python 2.x version, but development goes on in Python 3 (currently Python 3.4). We use Python 3.4 in the following slides.

Python 2 and 3 are incompatible. Do not write Python code for both versions simultaneously!

Python Tutorial

In [75]:
print("Hello World")
Hello World

In [76]:
variable = 1
print(variable)
1

In [77]:
variable = 42 - 5 * 3
print(variable)
27

In [78]:
variable = 2
variable = 3
print(variable)
print(type(variable))
3
<class 'int'>

In [79]:
# everything is an object (see OOP later) ("dir" lists all methods)
print(dir(variable))
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']

Working with simple datatypes

In [128]:
# scalars
a = None      # {None}
b = True      # {True, False}
c = 1         # optional sign, sequence of digits
d = 1.5
e = "glt14"   # " or '
f = b"glt14"  # b literal for bytes
g = 3 + 1j    # complex
print(type(a), type(b), type(c), type(d), type(e), type(f), type(g))
<class 'NoneType'> <class 'bool'> <class 'int'> <class 'float'> <class 'str'> <class 'bytes'> <class 'complex'>

In [81]:
float('inf')
Out[81]:
inf
In [82]:
# arbitrary precision integers
print(2**64)
other_approach = (2**63 - 1 << 1) | 0b1
print(bin(other_approach))
print(other_approach + 1)
18446744073709551616
0b1111111111111111111111111111111111111111111111111111111111111111
18446744073709551616

In [129]:
# these datatypes are all immutable
print(id(a) == id(True))
print(id(b) == id(1))
print(id(c) == id(1.5))
print(id(d) == id("glt14"))
print(id(e) == id(3 + 1j))
False
False
False
False
False

Working with strings (aka. unicode)

In [132]:
# The difference of repr(var) and str(var)
print(repr('Und er meinte: "Komm\' doch zu den Linuxtagen!"'))  # repr
'Und er meinte: "Komm\' doch zu den Linuxtagen!"'

In [133]:
print(str('Und er meinte: "Komm\' doch zu den Linuxtagen!"'))  # str
Und er meinte: "Komm' doch zu den Linuxtagen!"

In [86]:
multiline = """This is a rather
lengthy "{}" and therefore
break it into \
several {}
"""

print(multiline.format("message", "lines"))   # format is a *method* of the object *multiline*
print(multiline[0:13] + ".")
This is a rather
lengthy "message" and therefore
break it into several lines

This is a rat.

In [87]:
# You want unicode data internally. You get bytes via I/O.
#   bytes = sequence of 1s and 0s
filecontent = b'[user]\n\tname = "m\xCE\xB5isterluk"\n\temail = "admin@lukas-prokop.at"'

# convert to unicode data (you *have* to know the encoding)
#    str = sequence of unicode codepoints
content = filecontent.decode('utf-8')

print(len(filecontent))
print(len(content))

# now string operations on `content` work great and are well defined :)
# we remove 1 unicode point (which was *2* bytes) and add 1 new unicode point
content = content[0:17] + 'e' + content[18:]

# writing data somewhere = converting to output encoding

#with open('file.out', 'w') as fp:
#    fp.write(content.encode('utf-8'))

# or fp.write shall take care of it (-:

#with open('file.out', 'w', encoding='utf-8') as fp:
#    fp.write(content)
61
60

Complex datatypes

In [88]:
# native data structures
demo_list = [1, 2, 3, 4]           # [heterogenous] sequence, mutable
demo_set = {1, 2, 3, 4}            # [unordered] set of unique objects, mutable
demo_tuple = (1, 2, 3, 4)          # semantical unit, immutable
demo_dict = {1: "one", 3: "three"} # {key: value}, mutable, keys must be immutable

print(len(demo_list), len(demo_set), len(demo_tuple))

# Question: Which data structure is not syntactically supported by other dynamic languages?
4 4 4

In [89]:
demo_set = {1, 2, 3, 4, 3}
print(len(demo_set))
4

In [90]:
nested_list = ["defn", "method", ["this", "m"], ["str", '"Your arg is: "', '" m']]
print(nested_list[0])  # zero-based indexing
defn

In [91]:
print([i**2 for i in range(20)])
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361]

In [92]:
# pitfall #1: inclusive, exclusive

import random
# [0, 3)
print(list(range(0, 3)))
# [0, 3]
print({random.randint(0, 3) for n in range(20)})
[0, 1, 2]
{0, 1, 2, 3}

In [93]:
# operators on objects
a = 1
b = 1
c = "1"

# testing equivalence
print(a == b)
print(a == c)  # python dislikes coersion
True
False

In [113]:
forty_two = 42
answer = 42
Image(filename='python_var_names.png')
Out[113]:

Control flow statements

In [95]:
# conditional statements
if True:
    print("Hello")
else:
    print("World")
Hello

In [96]:
if False:
    print("Hello")
else:
    print("World")
World

In [97]:
cond1, cond2, cond3 = False, True, False

# No switch statement. Just elif!

if cond1:
    print("The world")
elif cond2:
    print("is")
elif cond3:
    print("not")
else:
    print("enough")
is

In [98]:
import math

if 3 <= math.pi < 4:
    print('constant PI:  {}'.format(math.pi))
    print('constant e:   {}'.format(math.e))

# chained comparisons supported by Python, Perl 6 and Mathematica
constant PI:  3.141592653589793
constant e:   2.718281828459045

In [99]:
# for loop over list elements
for i in [42, 3.14159, 666, 256]:
    print(i)

# unary "break" and "continue" statements are available
# even "else" for for-loops ^^
42
3.14159
666
256

In [100]:
# iteration over 0..20
for i in range(20):
    print(i)
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

In [101]:
# while loop
transitions = {1: 2, 2: 4, 3: 3, 4: 3}

visited = set()
current = 1
while current not in visited:    # poetic à la Shakespeare ;)
    visited.add(current)
    current = transitions[current]

print(visited)
{1, 2, 3, 4}

Working with functions

In [102]:
# functions = small processing units for repetitive tasks
def my_first_function(first_arg, second_arg):
    print(first_arg)
    print(second_arg)

# "Off-side rule": code block
#    = successive lines of code with same level of indentation
#    (please don't mix tabs and spaces; and prefer 4 spaces!)

# call the custom function
my_first_function("Hello", 0x007)
Hello
7

In [103]:
def function_with_return_value(first_arg=3):  # default value = 3
    return first_arg * 2

print(function_with_return_value(21))
print(function_with_return_value(1 + 2 + 3 + 4 + 5 + 6))
print(function_with_return_value())
42
42
6

In [104]:
def compute_speed(distance_in_m: float, time_in_sec: int) -> float:
    """Computing the speed.

    :param distance_in_m:  How many meters are we talking about?
    :param time_in_sec:    In which time span were these meters passed?
    :return:               Return the speed in km/k
    """
    # "/" is float division
    # "//" is integer division
    return (distance_in_m / time_in_sec) * 3.6

print('{} m/s are {} km/h'.format(42, compute_speed(42, 1)))
42 m/s are 151.20000000000002 km/h

In [105]:
def affine(a, b, c):
    return a + b * c

print(affine(1, 2, 3))
print(affine(b=2, a=1, c=3))  # passing by keyword
print(affine(1, 2, c=3))

print(affine(*[1, 2, 3]))
print(affine(**{'a': 1, 'b': 2, 'c':3}))
7
7
7
7
7

In [106]:
# pitfall #2: CPython implementation error: mutable default parameters
def push_one(lst=[]):
    lst.append(1)
    return lst

val = push_one()
print(val)
val = push_one()
print(val)   # WTH?

def push_one_fixed(lst=None):
    if lst is None:
        lst = []
    lst.append(1)
    return lst

val = push_one_fixed()
print(val)
val = push_one_fixed()
print(val)   # :)
[1]
[1, 1]
[1]
[1]

In [107]:
# lambdas are anonymous functions (only expressions, use for tiny functions)
add_one = lambda x: x + 1
print(add_one(3))

values = [('127.0.0.1', 80), ('83.246.69.174', 22), ("216.239.32.27", 80)]
print(list(map(lambda x: x[0], values)))
4
['127.0.0.1', '83.246.69.174', '216.239.32.27']

Generator functions

In [108]:
def generator_function():
    yield 0
    yield 1
    yield 1
    yield 2
    yield 3
    yield 5

iteration = generator_function()
print(next(iteration))
print(next(iteration))
print(next(iteration))
print(next(iteration))
print(next(iteration))
print(next(iteration))
# print(next(iteration))   # raises StopIteration
0
1
1
2
3
5

In [109]:
def infinity(start=0):
    current = start
    while True:
        yield current
        current += 1

for element in infinity():
    print(element)
    if element >= 20:
        break
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Classes and OOP

In [138]:
# Class-based object orientation
# - polymorphism
# - inheritance
# - encapsulation
#
# NO protected, private and public!
#   Python idiom: "We are all adults here!"
# name mangling for members starting with "__"

class Car:
    def __init__(self):
        self.seats = 4
        self.radio = False
    def __repr__(self):
        return "A car"
    def speed_up(self, acce):
        self.radio += acce

class Glt14Transporter(Car):
    def __init__(self):
        super().__init__()    # super() refers to all parent objects
        self.seats = 7

vehicle = Glt14Transporter()  # call constructor "__init__"
print(vehicle.seats)          # access public member "seats"
print(vehicle.radio)          # access public member "radio" inherited from "Car"

# Methods with "__" at beginning and end are special.
# "__init__" is the constructor. "Car.__repr__" is called when repr(obj) is called with obj as instance of Car
# "__len__" represents the length of an object representing a sequence. Returned by len(obj).
# "__eq__" is triggered for comparison with other objects.
#
# And so on and so on…
# See https://docs.python.org/2/reference/datamodel.html
7
False

Exceptions, I/O, importing, best practices

In [112]:
# Python has exceptions
# SyntaxError, ValueError, TypeError, OSError, …
a = "this is not an integer"
int(a)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-112-5790c76cc99e> in <module>()
      2 # SyntaxError, ValueError, TypeError, OSError, …
      3 a = "this is not an integer"
----> 4 int(a)

ValueError: invalid literal for int() with base 10: 'this is not an integer'
In []:
class ParsingError(Exception):
    def __str__(self):
        return 'My personal parsing error: ' + self.args[0]

raise ParsingError("Unexpected token '.'")
In []:
# Python idiom:
#   "Easier to ask for forgiveness than permission"
# Do it and if it fails, do it with another approach

# DO

try:
    # I/O example code: Use with statement to manage file handling in a context
    with open("/etc/passwd", "a") as fp:
        fp.write("meisterluk:x:1000:1000:meisterluk,,,:/home/meisterluk:/bin/zsh\n")
except IOError:
    print("Sorry, writing /etc/passwd went wrong")
    raise  # reraise

# DON'T

import os

if os.access('/etc/passwd', os.W_OK):
    with open("/etc/passwd", "a") as fp:
        fp.write("meisterluk:x:1000:1000:meisterluk,,,:/home/meisterluk:/bin/zsh\n")
else:
    print("Sorry, writing /etc/passwd went wrong")
In []:
try:
    1/0
except (ZeroDivisionError, TypeError) as e:
    print("ZeroDivisionError or TypeError occured")
    print(e)  # e is error object
else:
    print("No exception was raised. Nice!")
finally:
    print("Thanks for all the fish!")
In [111]:
# a simple breadth-first search algorithm as real-world example
import collections

def bfs(root, get_children, data):
    active = collections.deque([root])
    collected_data = []
    while active:
        current = active.popleft()
        for child in get_children(current):
            active.append(child)
        collected_data.append(data(current))
    return collected_data

# tree:
#         ↗ 2
#  0 → 1  → 3 → 5 → 6
#         ↘ 4 →   ↗

tree = [[1], [2,3,4], [], [5], [6], [6], []]

print(bfs(0, lambda n: tree[n], lambda n: n))
[0, 1, 2, 3, 4, 5, 6, 6]

In [127]:
# python uses duck typing
# "When I see a bird that walks like a duck and swims like
# a duck and quacks like a duck, I call that bird a duck"

class ModularArithmetic:
    def __init__(self, value, base=42):
        self.base = base
        self.value = value % base
    def __repr__(self):
        return str(self.value)
    def __eq__(self, other):
        return self.value == other
    def __add__(self, other):
        try:
            summed = (self.value + other)
        except TypeError:
            summed = (self.value + other.value)
        return ModularArithmetic(summed, self.base)

# DO

def total_sum(elements):
    total = ModularArithmetic(0)
    for elem in elements:
        total = total + elem
    return total

print(total_sum([1, 5, 9, ModularArithmetic(55), ModularArithmetic(155)]))

# DON'T

def total_sum(elements):
    total = ModularArithmetic(0)
    for elem in elements:
        if isinstance(elem, ModularArithmetic):
            # apply ModularArithmetics-specific addition
            summed = total.value + elem.value
            total += summed
        elif isinstance(elem, int):
            # apply int-specific addition
            total += elem
    return total

print(total_sum([1, 5, 9, ModularArithmetic(55), ModularArithmetic(155)]))

# Thus, do not check the type and apply specific operations.
# Just use the object and it should provide the operations itself.
15
31

In []:
# Python's import mechanism is pretty complex just like any other.
# Let's consider specific usecases.

# Import "module.py" from working directory
import module
# module.local_variable   # access local_variable in module

# Import "module" package by importing folder "module/" containing a "__init__.py" file
import module
# module.local_variable   # access local_variable in module/__init__.py

# Import "hashlib" from the stdlib
# If no file or package is found in working directory, import from any sys.path (stdlib)
import hashlib

# Packages might be nested.
# "import os" does not give you access to "os.path"
import os.path   # functions of "os" and "os.path" are available now

# You can skip the namespace, but using "from module import class"
from module import local_variable

# "from import" and "import" has semantical differences
# for details see e.g. http://lukas-prokop.at/proj/snippets/py013.html
# Long story, short: I recommend "import" syntax

# Python's stdlib is huge!
# Python idiom: "Batteries included!"
In [145]:
# summary of idioms / design principles of Python
import this

Conclusion

Handy tools

  • python3 -m http.server
  • python3 -m cProfile <file>

What is python good at

  • Readability of source code (clean and precise).
  • Need for a large set of libraries? Use python.
  • Fast prototyping / Rapid application development.
  • Web development (Django, Flask, …)
  • Scientific community (SciPy, NumPy, matplotlib, Scikit-learn, …)

What sucks about python?

  • You have to remember a small set of functions, you should use instead of methods.
  • Mutable and immutable datatypes are mixed!
  • Migration python 2.x to py3k is slow and tedious.
  • Python might be slow in some cases. Faster than ruby, slower than javascript. How to cope with that:
    • Improve data model / data structures
    • Profile your functions and methods. Interface important parts with C?!
    • pypy (faster, alternative interpreter) (no py3k)
    • Multiprocessing is useless due to Global Interpreter Lock

Where to go from here?

  • Dive into Python3 is the nicest python tutorial I know of
  • The official documentation as tutorial and library reference
  • Stackoverflow for any questions
  • IRC channels for informal questions (freenode #python, #python.de)
  • PyCon, SciPy, EuroPython conferences to listen to talks
  • Read PEPs (Python Enhancement Proposals) to follow the development progress
  • PEP8 must be read by any python programmer!
  • getpython3.com as tutorial for experienced programmers and py3k migrations

Here in Graz?

In [114]:
Image(filename='pygraz.png')
Out[114]:

Thanks fellows!

😁 Keep on hacking!