Python-Grundlagen

Python Grundlagen - Ein kurzes Tutorial

Python ist eine universelle, moderne high-level Programmiersprache, die
verschiedene Programmierparadigmen ermöglicht:

Allgemeine Charakteristik / Ziele

  • Ausdruckstark: Unterstützt das Schreiben von kurzem, klaren Programmcode.
  • Komplexe(re) Datenstrukturen möglich bzw. eingebaut, wie Listen oder Maps (dict).

Weitere (technische) Details

  • Dynamisch typisiert (dynamically typed)
  • Automatische Speicherverwaltung (automatic memory management)
  • in der Regel interpretiert, der Standard-Interpreter ist CPython (wird aber auch in Byte-Code kompiliert.)
  • es gibt aber weitere Implementierungen von Python.
  • Performanz-kritische (in Bezug auf die Laufzeit) Teile können in C/C++ entwickelt werden.
  • es gibt aber auch Compiler:
  • umfangreiche Bibliotheken für verschiedenste Anwendungsfälle
    • insbesondere auch im Bereich Data Science, Machine Learning, Artificial Intelligence weit verbreitet.

Python Style Guide

Sauberer Pythoncode folgt einem vorgegeben Stil, der in einem "Python Enhancement Proposals" pep beschrieben ist: https://www.python.org/dev/peps/pep-0008

Tipp: Verwenden Sie von Beginn an einen Type-Checker, um den von Ihnen entwickelten Code zu überprüfen, z.B. Style-Checker für jupyter notebooks.

In [4]:
%load_ext pycodestyle_magic
%pycodestyle_on

Variablen

Variablen sind Namen, die auf einen Wert, eine Funktion oder ein Objekt zeigen:

In [5]:
a_string = "hello, world"
an_integer = 12
a_float = 3.14
a_boolean = True

Beachte die dynamische Typisierung: Variablen haben explizite Typen. Diese werden bei der Zuweisung automatisch erkannt.

In [6]:
type(an_integer)
Out[6]:
int

Typumwandlungen (cast)

In [7]:
# Convert a string to an integer and vice-versa:
i = 12
s = str(i)
type(s)
Out[7]:
str

Print

Einfache Ausgabe via print

In [8]:
print("hello, world")
print(12)
print((5+3)/2)
hello, world
12
4.0
In [14]:
# This is a comment.

# To print several items,
# use commas (items will be separated by spaces):
print("abc", 12, a_float)

# multiline is possible, if there are braces
print('multiline is possible,'
      ' if there are braces')

multiline = 'a long statement may be split using backslash',\
    'this is still the same statement',\
    'the end.'
print(multiline)  # Note: multiline is a tuple of strings (see below)
abc 12 3.14
multiline is possible, if there are braces
('a long statement may be split using backslash', 'this is still the same statement', 'the end.')

Zeichenketten (Strings)

Die Syntax erlaubt es drei verschiedene Möglichkeiten für Zeichenketten:

In [16]:
string_single_quotes = 'abc'
string_double_quotes = "abc"
string_triple_quotes = """this is
    a multiline
    string."""
# Note: a multiline string is a python here-document
In [9]:
# It's useful when we need to include quotes in a string:
string1 = 'hello "world"'
string2 = "don't"

# Otherwise we have to use backslashes:
string2 = 'don\'t'

# Other special characters: http://docs.python.org/ref/strings.html
In [10]:
# Strings are objects which support many operations:

strings = string1 + " : " + string2
print(strings)

# better: use join!
print(" ".join([string1, ':', string2]))
hello "world" : don't
hello "world" : don't

(Objekt-orientiert) Punkt-Notation erlaubt es auf Instanzen (hier von Zeichenketten) Methoden auszurufen:

In [11]:
strings_uppercase = strings.upper()
strings_uppercase
Out[11]:
'HELLO "WORLD" : DON\'T'

Weieter Zeichenketten-Methoden siehe z.B.: http://docs.python.org/lib/string-methods.html

Slicing

Extraktion von Substrings via Slicing:

In [12]:
beginning = strings[0:4]
beginning
Out[12]:
'hell'

String Formating Syntax

von https://realpython.com/python-f-strings/

In [13]:
name = "Klaus"
age = 74
In [14]:
# simple concatenate - note the explicit cast of the int
"Hello " + name + ". You are " + str(age) + "."
Out[14]:
'Hello Klaus. You are 74.'
In [15]:
# old school %-formating
"Hello, %s. You are %i." % (name, age)
Out[15]:
'Hello, Klaus. You are 74.'
In [16]:
# str.format()
"Hello, {}. You are {}.".format(name, age)
Out[16]:
'Hello, Klaus. You are 74.'
In [17]:
"Hello, {1}. You are {0}.".format(age, name)
Out[17]:
'Hello, Klaus. You are 74.'

Tipp: Verwenden Sie f-Strings:

In [18]:
# Note the f before the first "
f"Hello, {name}. You are {age}."
Out[18]:
'Hello, Klaus. You are 74.'
In [19]:
# you can also use methods inside the f-String
f"{name.lower()} is funny."
Out[19]:
'klaus is funny.'

Stripping

In [20]:
a_string = "  lol abc lol  "
print(a_string)
# Strip spaces at beginning and end of a string:
stripped = a_string.strip()
print(stripped)
  lol abc lol  
lol abc lol

Ersetzungen

Ersetze eine Substring in einem String:

In [21]:
print(a_string)
newstring = a_string.replace('abc', 'def')
print(newstring)
  lol abc lol  
  lol def lol  

Strings sind immutable

Strings sind Konstanten, die nicht geändert werden können. Alle String-Operationen erzeugen neue Strings.

Strings vs. Bytestrings

  • Ein String ist eine Sequenz von Zeichen (characters), d.h. eine Zeichenkette.
  • Ein Bytestring ist eine Sequenz von Bytes. Diese kann so direkt als Bitfolge bzw. Bytefolge gespeichert werden.

Die Konvertierung von Strings (Zeichenketten) zu Bytes und umgekehrt hängt von der Kodierung (dem Encoding) zusammen. In Python 3 ist der Quellcode standardmäßig in UTF-8 kodiert. Eine Tabelle der Unicode-Codeposition und der korreponierenden numerischen UTF-8 Repräsentation finden Sie z.B. in https://www.utf8-zeichentabelle.de/unicode-utf8-table.pl?number=1024. Beachten Sie, dass ein Zeichen abhängig von Zeichen mit 1-4 Bytes in UTF( repräsentiert wird.

Beispiel: Der griechische Buchstabe τ wird in UTF-8 mit zwei Bytes kodiert (numerische Repräsentation).

- binär: 11001111 10000100
- hexadezimal: 0xcf 0x84

als

  • Bytestring b'\xcf\x84' als zwei Hexadezimalzahlen
  • Als UTF-8 String τ (Zeichenkette mit nur einem Zeichen)
In [59]:
'τ'.encode('utf-8')
Out[59]:
b'\xcf\x84'
In [50]:
#0xF0 0x9D 0x9C 0x8F
b'\xcf\x84'.decode('utf-8')
Out[50]:
'τ'

Gewisse Zeichen (ein Byte-Zeichen?) werden im Bytestringdarstellung auch direkt als entsprechendes Zeichen dargestellt:

In [72]:
# "latin small letter o" ist 0x6f in UTF-8
# "6" is 0x36
b'\x6f\x36'
Out[72]:
b'o6'

An folgendem Beispiel wird der Unterschied zwischen verschiedenen (Unicode)-Kodierungen klar. D.h. dieselbe Bytefolge kann abhängig von der Kodierung eine andere Stringfolge ergeben.

In [63]:
print(b'\xcf\x84o\xcf\x81\xce\xbdo\xcf\x82'.decode('utf-16'))
print(b'\xcf\x84o\xcf\x81\xce\xbdo\xcf\x82'.decode('utf-8'))
蓏콯캁澽苏
τoρνoς

Hinweis:

Listen (lists)

Eine Liste ist ein dynamisches Array von beliebigen Objekten/Typen:

Diese werden über eckige Klammern (square brackets) deklariert:

In [22]:
# declare a empty lists by
a_list = []  # or by
a_list = list()
type(a_list)
Out[22]:
list
In [23]:
a_list.append(3)  # append an element
a_list.append("bla")
a_list
Out[23]:
[3, 'bla']
In [24]:
a_list = [1, 2, 3, 'abc', 'def']
In [25]:
# Lists may contain lists:

another_list = [a_list, 'abc', a_list, [1, 2, 3]]
# (in this case, "a_list" is only a pointer)
In [26]:
# Access a specific element by index (index starts at zero):

elem = another_list[1]
print(elem)
elem2 = another_list[3][1]
print(elem2)
abc
2
In [27]:
# It's easy to test if an item is in the list:

if 'abc' in a_list:
    print('bingo!')
bingo!
In [28]:
# Extracting a part of a list is called slicing:

list2 = a_list[2:4]  # Returns a list with items 2 and 3 (not 4)
print(list2)
[3, 'abc']
In [29]:
# Other list operations like appending:

print(a_list)
a_list.append('ghi')
print(a_list)
a_list.remove('abc')
print(a_list)
[1, 2, 3, 'abc', 'def']
[1, 2, 3, 'abc', 'def', 'ghi']
[1, 2, 3, 'def', 'ghi']

List Comprehension

In [31]:
vals = [1, 2, 3, 5, 7, 9, 10]

double_vals = [2 * v for v in vals]
print(double_vals)
[2, 4, 6, 10, 14, 18, 20]

Weitere Listen-Operationen siehe http://docs.python.org/lib/typesseq.html

Tuples

Ein Tuple ist ähnlich einer Liste, aber mit fester Länge (fixed-size) und unveränderlich (immutable). Die Elemente eines Tupels können nicht geändert oder gelöscht werden. Neue Elemente können nicht an- oder eingefügt werden

Die Elemente werden mittels Komma getrennt, wobei meist die Tuples mittels runden Klammern deklariert werden:

In [32]:
a_tuple = (1, 2, 3, 'abc', 'def')
a_tuple[3]  # element indexing starts with 0
Out[32]:
'abc'
In [33]:
# But parentheses are optional:

another_tuple = 1, 2, 3, 'abc', 'def'
another_tuple
Out[33]:
(1, 2, 3, 'abc', 'def')
In [34]:
# Tip: a tuple containing only one item must be declared using a comma,
#      else it is not considered as a tuple:

a_single_item_tuple = 'one value',

type(a_single_item_tuple)
Out[34]:
tuple

Dictionaries

(analog zu Maps in Java)

Dictionaries (dict) sind Schlüssel-Wert Abbildungen (Key-Value Maps), analog einer mathematischen Funktion.

Dictionaries können über geschweifte Klammern deklariert werden:

In [35]:
point = {}  # form an empty dictionary or by:
point = dict()

p = (1.2, -40.0, 29)
point['x'] = p[0]
point['y'] = p[1]
point['z'] = p[2]
point['z']
Out[35]:
29
In [36]:
# declare a dict by curly brackets
point = {'x': p[0], 'y': p[1], 'z': p[2]}

point['z'] = 55.
In [37]:
point = {'x': p[0], 'y': p[1], 'z': p[2]}

del point['x']


# print(point['x']) # raises an error
print(point.get('x'))  # returns None
None
In [38]:
if 'x' not in point:
    print('missing x')

for key in point:
    print(key)
missing x
y
z

Blocks und Einrückungen (Kontrollfluss - control flow)

Code-Blöcke werden durch Einrückungen (indentation) abgegrenzt (und nicht durch Klammern { }). Einrückungen entstehen durch Tabs oder Leerzeichen.

Tipp: Niemals Tabs mit Leerzeichen mischen, da dann die Tiefe der Einrückung oft nicht visuell richtig angezeigt wird. Am besten einen Editor/IDE verwenden, die Tabs in Leerzeichen konvertiert.

if / elif / else - Abzweigungen

In [39]:
a = 3
print(a)
if a == 3:
    print('The value of a is:')
    print('a=3')
3
The value of a is:
a=3
In [40]:
if a == 'test':
    print('The value of a is:')
    print('a = "test"')
    test_mode = True
else:
    print('a != "test"')
    test_mode = False
    # do_something_else()
a != "test"
In [41]:
b = 2
if a == 1 or a == 2:
    pass  # do nothing
elif a == 3 and b > 1:
    pass
elif a == 3 and not b > 1:
    pass
else:
    pass

while Loops

In [42]:
a = 1
while a < 10:
    print(a)
    a += 1
1
2
3
4
5
6
7
8
9

for Loops

Ein Python for-Schleife ist eigentlich eine foreach-Schleife:

In [43]:
for a in range(4):
    print(a)
0
1
2
3
In [44]:
my_list = [2, 4, 8, 16, 32]
for a in my_list:  # my_list has to be an iterator
    print(a)
2
4
8
16
32

Funktionen

Eine Funktion wird definiert mittels des def Schlüsselworts:

In [45]:
def my_function(arg1, arg2, arg3='default value'):
    print('arg1 =', arg1)
    print('arg2 =', arg2)
    print('arg3 =', arg3)


# Call it (note that arguments are not strongly typed):
my_function(17, 'abc', "def")
arg1 = 17
arg2 = abc
arg3 = def

Es können (zur Dokumentation) Typ-Hinweise gegeben werden:

siehe https://docs.python.org/3/library/typing.html

In [46]:
# type hints
def my_function(arg1: int, arg2: str, arg3: str = 'default value') -> None:
    print('arg1 =', arg1)
    print('arg2 =', arg2)
    print('arg3 =', arg3)
In [47]:
# The 3rd arg may be omitted:
my_function('a string', 12)
arg1 = a string
arg2 = 12
arg3 = default value
In [48]:
# A function may return a value:
def fun2():
    print('fun2')
    return 'any return value'


# call it:
print('fun2() = %s' % fun2())
fun2
fun2() = any return value

Funktionale Aspekte von Python

Funktionen sind Bürger erster Klasse (first-class citizen). Funktionen können in Variablen gespeichert werden und als Funktionsargumente übergeben werden.

In [49]:
def f(x):
    return x ** 2


def operate(h, x):  # higher-order function
    print(h(x))


operate(f, 5)
25

Anonyme Funktionen (lambda functions)

Mittels des lambda-Schlüsselworts können anonyme Funktionen erzeugt werden:

In [50]:
g = lambda x: x**3  # assign to the variable g the unnamed function
g(5)

# Note: this is not pep8 conform.
Out[50]:
125
1:1: E731 do not assign a lambda expression, use a def
In [51]:
operate(lambda x: x**3, 5)
125
In [23]:
def make_incrementor(n):
    return lambda x: x + n

f = make_incrementor(n=3)
f(2)
Out[23]:
5
In [21]:
g = make_incrementor(7)
g(5)
Out[21]:
12
In [27]:
print(make_incrementor(n=7)(x=8))
print(make_incrementor(7)(8))
15
15
In [28]:
foo = [2, 18, 9, 22, 17, 24, 8, 12, 27]

# map and filter return iterators in python3
print(list(filter(lambda x: x % 3 == 0, foo)))

print(list(map(lambda x: x * 2 + 10, foo)))
[18, 9, 24, 12, 27]
[14, 46, 28, 54, 44, 58, 26, 34, 64]
In [29]:
from functools import reduce

print(reduce(lambda x, y: x + y, foo)) 
print(sum(foo))

print(reduce(lambda x, y: x + y, map(lambda x: x * 2 + 10, foo)))
139
139
368
In [31]:
from operator import add

reduce(add, map(lambda x: x * 2 + 10, foo))
Out[31]:
368
In [32]:
import operator

expr = "28+32+++32++39"


print(reduce(add, map(int, filter(bool, expr.split("+") ))))

operator.mul(3, 10)
operator.pow(2, 3)
operator.itemgetter(1)([1, 2, 3])

# more see http://ua.pycon.org/static/talks/kachayev
131
Out[32]:
2
In [44]:
print("expr is of type: ",type(expr))

# split expr on the separator '+'
print(expr.split("+"))
expr.split("+")
# filter with "bool" as operator
print(list(filter(bool, expr.split("+") )))
print(list(map(bool, expr.split("+") )))
# all empty strings are False, not empty strings are True


# cast the string-list elements to int
# with "int" as operator 
list(map(int, filter(bool, expr.split("+"))))

# add up all elemets with add-operator 
print(reduce(add, map(int, filter(bool, expr.split("+") ))))
expr is of type:  <class 'str'>
['28', '32', '', '', '32', '', '39']
['28', '32', '32', '39']
[True, True, False, False, True, False, True]
131

Skripts

Shebang:

  • !#/usr/bin/env python meist aber python2 Interpreter!
  • !#/usr/bin/env python3

Kommandozeilen-Argumente

Um Argumente auf der Kommandozeile einem Skript zu übergeben, ist es notwendig Module von der Standard Library zu importieren:

import sys

print(f"Name of the script      : {sys.argv[0]=}")
print(f"Arguments of the script : {sys.argv[1:]=}")

Tipp: Verwenden Sie lieber das argparse-Modul, um Kommandozeilenargumente zu behandeln. Arbeiten Sie hierzu das argparse-Tutorial durch.

mehr zu Kommandozeilenargumenten siehe https://realpython.com/python-command-line-arguments/

Dateiverarbeitung

Öffnen einer Datei zum Lesen:

f = open('my_file.txt')
# open() returns a file object with several methods.

# To read a specific number of characters:
s = f.read(10)

# To read the whole file in a string
s = f.read()

#To close an open file:
f.close()

To iterate over file lines:

f = open('my_file.txt')
for line in f:
    print (line)
f.close()

Besser mit with-Statement (Context Manager) - auch zur Fehlerbehandlung (siehe unten):

In [8]:
# open for read 'r'
with open('../bash/work/countries.txt', 'r') as f:
    for line in f:
        print(line, end='') #  there is already a newline at the end
France
Canada
Burkina Faso
Democratic Republic of the Congo
Russia
New Zealand

Öffnen von Dateien zum Schreiben (write w) auch am besten mit with-Statement:

with open('out_file.txt', 'w') as fw:
  fw.write('a line of text\n') # note '\n' is necessary here
  fw.write(a_string)

Für weitere Datei-Operationen siehe z.B. http://docs.python.org/lib/bltin-file-objects.html

OS Modul

Um mit Dateien umzugehen, z.B. um eine Datei zu löschen werden wieder Module aus der Standard Bibliothek benötigt:

import os, os.path
if os.path.exists('my_file.txt'):
    os.remove('my_file.txt')
In [2]:
import os

Laufendes Arbeitsverzeichnis

In [11]:
cwd = os.getcwd()
type(cwd) 
Out[11]:
str

Wechseln des Arbeitsverzeichnis

os_chdir(path)

Verzeichnisinhalt auflisten

Anzeigen der Dateien für eine Verzeichnis: os.listdir(".") (hier übergeordnetes Verzeichnis des Arbeitverzeichnis)

In [13]:
files = os.listdir("..")
type(files), type(files[0]) # python list of str
Out[13]:
(list, str)

Erzeugen eines Verzeichnisses

os.mkdir(name_of_dir)

Umgebungsvariablen

z.B. HOME Umgebungsvariable erhalten:

In [20]:
os.environ['HOME']
Out[20]:
'/home/chris'

Unix/Linux User/Group Funktionalitäten

z.B: User-Id erhalten:

In [23]:
os.getuid()
Out[23]:
1001

Information zum Betriebssystem

In [25]:
os.uname()
Out[25]:
posix.uname_result(sysname='Linux', nodename='platon', release='5.4.0-72-generic', version='#80~18.04.1-Ubuntu SMP Mon Apr 12 23:26:25 UTC 2021', machine='x86_64')

Ausführen eines Programms in einer Subshell

os.system(some_command)

Dies ist nicht zu empfehlen. Verwenden Sie stattdessen das Modul subprocess (siehe unten).

für mehr zum OS-Modules siehe help(os) oder die Referenz https://docs.python.org/3.9/library/os.html

Module subprocess

Einfaches Ausführen eines Kommandos in einem Unterprozess subprocess.run(command)

In [12]:
import subprocess
proc = subprocess.run(["ls", "-l"], 
                      universal_newlines=True,
                      stdout = subprocess.PIPE)
In [13]:
proc_out = proc.stdout.splitlines()
print(proc_out)
['total 184', '-rw-rw-r-- 1 chris chris 69094 Dez 16 16:09 extract_ard_politik_talkshows.ipynb', '-rw-rw-r-- 1 chris chris     0 Mai  6  2021 geckodriver.log', 'drwxr-xr-x 2 chris chris  4096 Mai 24  2020 intro_exercises', '-rw-r--r-- 1 chris chris   116 Mär 10  2021 literatur.txt', '-rwxrw-r-- 1 chris chris  3796 Dez 21 15:12 minesweeper.py', '-rwxrw-r-- 1 chris chris  3882 Dez 16 18:49 minesweeper.py~', '-rw-r--r-- 1 chris chris   659 Mai  7  2021 mymodule.py', 'drwxr-xr-x 3 chris chris  4096 Feb 23  2021 programs', 'drwxr-xr-x 2 chris chris  4096 Mai  7  2021 __pycache__', '-rw-r--r-- 1 chris chris 70790 Jan  4 14:08 Python-Grundlagen.ipynb', '-rw-rw-r-- 1 chris chris  2260 Mai  7  2021 Python-os-Modul.ipynb', '-rw-rw-r-- 1 chris chris  2173 Mai  7  2021 selenium.ipynb', '-rw-rw-r-- 1 chris chris     0 Nov  7 13:42 stderr.txt', '-rw-r--r-- 1 chris chris   201 Jan 31  2021 test.py', '-rw-r--r-- 1 chris chris   200 Jan 31  2021 test.py~']

Auch Pipes etc. möglich.

Mehr siehe https://docs.python.org/3/library/subprocess.html

Klassen

Python erlaubt Klassen-basierte objektoriente Programmierung.

Eine Klasse gibt die Struktur für Instanzen der Klassen (Objekte) vor. Operationen auf den Instanzen können mittels Methoden ("Funktionen" für Klassen) definiert werden.

In Python enthält eine Klasse also

  • Attribute (attributes), d.h. Variablen und
  • Methoden (methods).

Eine Klasse wird mit dem class-Schlüsselwort definiert.

  • Jeder Methode der Klasse sollte als erstes Argument self haben (Referenz auf die laufende Instanz der Klasse).
  • Einige Methoden haben spezielle Bedeutung, z.B.:

In [66]:
class Point:
    """
    Simple class for representing a point in a Cartesian coordinate system.
    """

    def __init__(self, x, y):
        """
        Create a new Point at x, y.
        """
        self.x = x  # instance variable self.x
        self.y = y

    def translate(self, dx, dy):
        """
        Translate the point by dx and dy in the x and y direction.
        """
        self.x += dx
        self.y += dy

    def __str__(self):
        return("Point at [%f, %f]" % (self.x, self.y))
In [67]:
# To create a new instance of a class:

p1 = Point(0, 0)  # this will invoke the __init__ method in the Point class

print(p1)  # this will invoke the __str__ method
Point at [0.000000, 0.000000]
In [68]:
# To invoke a class method in the class instance `p`:

p2 = Point(1, 1)

# this modifies the internal store coordinates
p1.translate(0.25, 1.5)

print(p1)
print(p2)
Point at [0.250000, 1.500000]
Point at [1.000000, 1.000000]

Methoden können dynamisch zu Klassen hinzugefügt werden:

In [71]:
def point_add(self, other):
    """
    Defines the +-Operator
    """
    assert isinstance(other, Point)  # check the type
    newPoint = Point(self.x, self.y)
    newPoint.x += other.x
    newPoint.y += other.y
    return newPoint
In [72]:
# __add__ is the special methods for implementing the +-Operator
# we can dynamically extend the class Point by this method
Point.__add__ = point_add
In [73]:
print(p1 + p2)
Point at [1.250000, 2.500000]

Exceptions

Wenn zur Laufzeit Bedingungen, die erfüllt sein müssten, nicht gegeben sind, können Exceptions (Ausnahmen) geworfen werden. Wenn beispielsweise die mathematische Operation Teile $a$ durch $b$ ausgeführt werden soll, macht dies nur Sinn mit der Bedingung, dass $b\neq 0$ ist. Hier kann in der Implementierung der Teil-Opration dies überprüft werden und falls dies nicht der Fall ist eine Exception geworfen werden.

In Python werden Exceptions mittels raise geworfen:

In [74]:
raise Exception("description of the error")
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-74-c32f93e4dfa0> in <module>
----> 1 raise Exception("description of the error")

Exception: description of the error
In [77]:
# A typical use of exceptions is to abort functions
# when some error condition occurs, for example:
def point_add2(self, other):
    """
    Defines the +-Operator
    """
    if not isinstance(other, Point):  # check the type
        raise TypeError("Second argument of +-Operator is not a Point!")
    newPoint = Point(self.x, self.y)
    newPoint.x += other.x
    newPoint.y += other.y
    return newPoint


Point.__add__ = point_add2
In [78]:
p3 = Point(2., 3.)
print(p3 + 4.) # this causes an exception
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-78-658d1a1e3e7b> in <module>
      1 p3 = Point(2., 3.)
----> 2 print(p3 + 4.)

<ipython-input-77-da6623114068> in point_add2(self, other)
      6     """
      7     if not isinstance(other, Point):  # check the type
----> 8         raise TypeError("Second argument of +-Operator is not a Point!")
      9     newPoint = Point(self.x, self.y)
     10     newPoint.x += other.x

TypeError: Second argument of +-Operator is not a Point!

Fangen von Exceptions

Programmcode sollte mit Exceptions (Ausnahmen) umgehen können. Dazu sollte in der Beschreibung der Schnittstellen zu Modulen (siehe unten) angegeben sein, welche Ausnahmen auftreten können. Das Behandeln von Ausnahmen wird als Exception Handling bezeichnet.

Dabei werden in Python mittels try und except die Ausnahmen/Fehler "gefangen", um damit programmatisch umzugehen:

In [83]:
import sys
try:
    print("test if we can add an int:")
    p4 = p3 + 4
except Exception as e:
    print("Caught an exception: ", str(e),  file=sys.stderr)  # print to stderr
# 
test if we can add an int:
Caught an exception:  Second argument of +-Operator is not a Point!

Beachte: Einfaches Ausgeben der Exception-Ursache und Weiterausführen des Codes (wie im obigen Beispiel) ist ein Antipattern. Dies kann zu schwer zu findenden Bugs führen, da in der Regel, die Exception nicht ohne Grund geworfen wurde. Richtiges Exception-Handling ist bei der Softwareentwicklung sehr wichtig.

Mehr zum Thema Fehler und Exceptions, siehe z.B. https://docs.python.org/3/tutorial/errors.html

Modules

TODO deutsch

One of the most important concepts in good programming is to reuse code and avoid repetitions.

The idea is to write functions and classes with a well-defined purpose and scope, and reuse these instead of repeating similar code in different part of a program (modular programming). The result is usually that readability and maintainability of a program is greatly improved. What this means in practice is that our programs have fewer bugs, are easier to extend and debug/troubleshoot.

Python supports modular programming at different levels. Functions and classes are examples of tools for low-level modular programming. Python modules are a higher-level modular programming construct, where we can collect related variables, functions and classes in a module. A python module is defined in a python file (with file-ending .py), and it can be made accessible to other Python modules and programs using the import statement.

Consider the following example: the file mymodule.py contains simple example implementations of a variable, function and a class:

In [85]:
%%file mymodule.py
# ipython magic function writes the content of the cell to a text file "mymodule.py"

"""
Example of a python module. Contains a variable called my_variable,
a function called my_function, and a class called MyClass.
"""

my_variable = "Hello World"

def my_function():
    """
    Example function
    """
    return my_variable
    
class MyClass:
    """
    Example class.
    """

    def __init__(self):
        self.variable = my_variable
        
    def set_variable(self, new_value):
        """
        Set self.variable to a new value
        """
        self.variable = new_value
        
    def get_variable(self):
        return self.variable
Overwriting mymodule.py
In [87]:
# We can import the module `mymodule` into our Python program using `import`:
import mymodule
In [88]:
# Use `help(module)` to get a summary of what the module provides:
help(mymodule)
In [89]:
mymodule.my_variable
Out[89]:
'Hello World'
In [91]:
mymodule.my_function()
Out[91]:
'Hello World'
In [93]:
my_class = mymodule.MyClass()
my_class.set_variable(10)
my_class.get_variable()
Out[93]:
10

Übung Recherche

Recherchieren Sie weshalb Python-Dateien oft die Konstruktion if __name_ == "__main__" enthalten. Wie funktioniert die Konstruktion? Was ist der Sinn der Konstruktion, bzw. wann wird diese eingesetzt?

Futher readings