Python Intermediate

Themen

  • Datentypen vertieft
    • Strings und String-Verarbeitung
    • Bytes
    • Sequenzen
  • objektorientiertes Programmieren und Klassen
  • fortgeschrittene Kontrollstrukturen
  • Erstellen von Listen und Dictionaries mittels Comprehensions
  • Exceptions
  • Module und Pakete
  • Paketmanagement in Python

Datentypen in Python

Grundlegende Datentypen

  • int
  • float
  • bool
  • NoneType
  • string

Kollektionen

  • list
  • tuple
  • dict

Weitere Datentypen

  • decimal
  • complex
  • bytes, bytearray
  • set, frozenset
  • NamedTuple
  • ...

None

None

None ist ein Singleton:

  • es gibt immer nur ein None-Objekt innerhalb eines laufenden Python-Programms
  • mehrere Variablen können auf dieses Objekt verweisen

Vergleich mit "is"

Das Schlüsselwort is vergleicht in Python, ob sich zwei Referenzen / Namen auf das gleiche Objekt beziehen.

a = [1, 2]
b = a
x = [1, 2]

a == b # True
a is b # True
a == x # True
a is x # False

Vergleich mit "is"

Nachdem None ein Singleton ist, kann darauf mit is None getestet werden.

if a is None:
    ...

bool

bool

True oder False

a = True
if a:
    print('hello')

bool

Intern verhält sich False fast wie 0 und True fast wie 1

False + True # 1

Zahlen

Weitere Operationen mit Zahlen

  • Division mit Rest: 10 // 3
  • Divisionsrest / Modulo: 10 % 3
  • Potenzieren: 2 ** 3

Unterstriche in Literalen

um uns beim Lesen langer Zahlen zu helfen:

earth_circumference = 40075017
earth_circumference = 40_075_017

int

beliebig große Ganzzahlen

int

Andere Zahlensysteme:

a = 42 # decimal
b = 0b101010 # binary
c = 0o52 # octal
d = 0x2a # hexadecimal
e = int('101010', 2)

float

64-bit Gleitkommazahlen

a = 2.3
b = .2
c = 6e23
d = float('nan')
e = float('inf')

float

Rundungsfehler: manche Zahlen können nicht als Gleitkommazahlen dargestellt werden, sie sind immer Annäherungen

Beispiele im Dezimalsystem: 1/3, 1/7, π

Beispiele im Binärsystem (floats): 1/10, 1/5, 1/3, π

Beispiel: π + π ergibt 6.2, wenn wir Dezimalzahlen mit 2 Stellen verwenden (besseres Ergebnis wäre 6.3)

Beispiel: 0.1 + 0.2 ergibt ~ 0.30000000000000004, wenn wir 64-bit floats verwenden

float

0.1 + 0.2 == 0.3
# False
import math

math.isclose(0.1 + 0.2, 0.3)
# True

float

IEEE 754: Standard für Gleitkommazahlen am Computer

wird von Python großteils umgesetzt

Ausnahme: Python löst für manche Operationen Exceptions aus, die unter dem Standard ein Ergebnis liefern würden - z.B. 1.0/0.0

Besondere Zahlen in IEEE 754:

  • inf und -inf (unendliche Werte)
  • nan (not-a-number: undefinierter / unbekannter Wert)

complex

a = 2 + 3j

Erweiterte Zuweisung

Zu binären Operatoren gibt es sogenannte erweiterte Zuweisungen (augmented assignments):

a = a + 1

Kurzform (erweiterte Zuweisung):

a += 1

Weitere Formen: -=, *=, ...

Zeichenkodierung

Unicodezeichen

Unicode: Katalog von über 100000 internationalen Schriftzeichen, jedes mit eindeutigem Namen und Nummer (meist in Hexadezimalform)

Beispiele:

  • K: U+004B (Latin capital letter K)
  • ?: U+003F (Question mark)
  • ä: U+00E4 (Latin small letter a with a diaeresis)
  • €: U+20AC (Euro sign)
  • 🙂: U+1F642 (Slightly smiling face)

Tabellen aller Unicodezeichen

Zeichenkodierung

Zeichenkodierung = Zuordnung von Zeichen zu Bitsequenzen

  • ASCII: Kodiert die ersten 128 Unicodezeichen, u.a. A, !, \$, Leerzeichen, Zeilenumbruch
  • Latin1: Kodiert die ersten 256 Unicodezeichen, u.a. ä, á, ß, §
  • UTF-8, UTF-16, UTF-32: Kodieren alle Unicodezeichen

Eine Zeichenkodierung ist notwendig, um Text auf ein Speichermedium zu schreiben oder über das Netzwerk zu übertragen

Zeichenkodierung

Beispiele in ASCII / Latin1 / UTF-8:

  • ! ↔ 00100001
  • A ↔ 01000001
  • a ↔ 01100001

Beispiele in Latin1:

  • Ä ↔ 11000100

Beispiele in UTF-8:

  • Ä ↔ 11000011 10100100
  • 🙂 ↔ 11110000 10011111 10011001 10000010

UTF-8

UTF-8 hat sich insbesondere im Web zum Standardencoding entwickelt

Die ersten 128 Unicode-Zeichen benötigen nur 8 Bit (wie bei ASCII / Latin1)

Alle anderen Zeichen benötigen jeweils 16, 24 oder 32 Bit

UTF-32

UTF-32 kodiert unmittelbar die Unicode-Codepukte, wobei je nach Anwendungsbereich eine andere Bytereihenfolge (big endian oder little endian) auftreten kann.

Beispiel:

🙂 (U+1F642) ↔ 00 01 F6 42 (big endian) oder 42 F6 01 00 (little endian)

Zeilenumbrüche

Zeilenumbrüche können durch die Zeichen LF (line feed, U+000A) bzw CR (carriage return, U+000D) kodiert werden

  • LF: Standard unter Linux, MacOS
  • CRLF: Standard unter Windows, in Netzwerkprotokollen wie HTTP

In String-Literalen wird LF oft durch \n und CR oft durch \r repräsentiert

Strings

Strings

Strings in Python sind Zeichenfolgen, die jedes Unicodezeichen repräsentieren können

String-Literale

Beispiele:

a = "test"
b = 'test'

Mehrzeilige String-Literale

a = """this
is a multi-line
string literal.
"""

Escape Sequenzen

Mit Hilfe des Backslashes können besondere Zeichen eingefügt werden:

a = "He said:\n\"Hi!\""

Escape Sequenzen

  • \' → '
  • \" → "
  • \\ → \
  • \n → Line Feed (Zeilenumbruch unter Unix)
  • \r\n → Carriage Return + Line Feed (Zeilenumbruch unter Windows)
  • \t → Tab
  • \xHH bzw. \uHHHH bzw. \UHHHHHHHH → Unicode-Codepunkt (hexadezimal)

Raw Strings

Wenn in einem String keine Escape Sequenzen benötigt werden:

path = r"C:\documents\foo\news.txt"

(besonders nützlich bei regulären Ausdrücken)

String-Methoden

  • .lower()
  • .upper()

String-Methoden

  • .startswith(...)
  • .endswith(".txt")

String-Methoden

  • .center(10)
  • .ljust(10)
  • .rjust(10)

String-Methoden

  • .strip()
  • .split(' ')
  • .splitlines()
  • .join()

Ãœbung: Formatierung von Goethes Faust

Quellen:

Ãœbung: Faust

Original:

  Ihr naht euch wieder, schwankende Gestalten,
  Die früh sich einst dem trüben Blick gezeigt.

Ziel:

Ihr naht euch wieder, schwankende Gestalten,               1
Die früh sich einst dem trüben Blick gezeigt.              2

Ãœbung: Faust

Aufgaben:

  • Ausrichtung der Zeilennummern basierend auf der Länge der längsten Zeile
  • Zeilennummern nur alle 5 Zeilen
  • Zeilennummern alle 5 Zeilen, wenn die Zeile Text enthält - ansonsten in der nächsten Zeile

String-Formatierung

String-Formatierung

String-Formatierung = Einsetzen von Werten in Strings

Möglichkeiten:

greeting = "Hello, " + name + "!"
greeting = f"Hello, {name}!"

String-Formatierung: Möglichkeiten

city = 'Vienna'
temperature = 23.7

# eher veraltet
'weather in %s: %f°C' % (city, temperature)

'weather in {0}: {1}°C'.format(city, temperature)
'weather in {}: {}°C'.format(city, temperature)
'weather in {c}: {t}°C'.format(c=city, t=temperature)

f'weather in {city}: {temperature}°C'

Formatangaben

  • .4f: vier Dezimalstellen nach dem Dezimalzeichen
  • .4g: vier Dezimalstellen
print(f"Pi is {math.pi:.4f}")
# Pi is 3.1416
print(f"Pi is {math.pi:.4g}")
# Pi is 3.142

Formatangaben

  • >8: rechtsbündig (Gesamtbreite 8)
  • ^8: zentriert
  • <8: linksbündig
print(f"{first_name:>8}")
print(f"{last_name:>8}")
    John
     Doe

Formatangaben

Kombination:

print(f"{menu_item:<12} {price:>5.2}$")
Burger        11.90$
Salad          8.90$
Fries          3.90$

Formatangaben

weitere Optionen:

Python String Format Cookbook von Marcus Kazmierczak

Listen

Listen

Listen sind veränderliche Sequenzen von Objekten; sie werden üblicherweise verwendet, um gleichartige (homogene) Einträge abzulegen

primes = [2, 3, 5, 7, 11]

users = ["Alice", "Bob", "Charlie"]

Operationen auf Listen

Die folgenden Operationen klappen auch bei anderen Sequenzen - z.B. Tupeln, Strings oder Bytes

  • Elementzugriff (via index): users[2]
  • Zugriff auf mehrere Elemente (Unterliste): users[2:4]
  • Zusammensetzen: users + users
  • Wiederholung: 3 * users
  • Länge: len(users)
  • for-Schleife: for user in users:
  • if-Abfrage: if 'Tim' in users:

Operationen auf Listen - Mutationen

Listen können direkt verändert werden (im Gegensatz zu Tupeln, Strings):

  • Anhängen: users.append("Dan")
  • Einfügen: users.insert(2, "Max")
  • Letztes Element entfernen: users.pop()
  • Ein Element anhand des Index entfernen: users.pop(2)

Sortieren von Listen

Sortierung nach Standardreihenfolge (bei Strings alphabetisch)

l.sort()

Sortierung nach selbstdefinierten Reihenfolgen:

  • nach Länge eines Strings
  • nach Häufigkeit des Buchstabens "a"
l.sort(key=len)

def count_a(s):
    return s.count("a")
l.sort(key=count_a)

Ãœbungen

  • Mischen von Karten
  • Liste von Primzahlen
  • Insertion Sort

Tupel

Tupel

Erstellung: Einträge werden mit Kommas getrennt, üblicherweise mit runden Klammern umschlossen.

empty_tuple = ()
single_value = ('Thomas', )
single_value = 'Thomas',
two_values = ('Thomas', 'Smith')
two_values = 'Thomas', 'Smith'

Unpacking von Tupeln

time = (23, 45, 0)

hour, minute, second = time

Tauschen von Variablennamen:

a, b = b, a

Bytes

Bytes

beim Lesen von Datenträgern oder Empfangen von Daten müssen wir manchmals mit Bytes arbeiten: Abfolgen von Zahlen im Bereich von 0 bis 255 (8 Bit)

Bytes können Bilder, Text, Daten, ... repräsentieren

Hexadezimalnotation

Bytes werden oft in Hexadezimalnotation statt dezimal geschrieben:

  • 1dec = 1hex
  • 9dec = 9hex
  • 10dec = ahex
  • 15dec = fhex
  • 16dec = 10hex
  • 17dec = 11hex
  • 31dec = 1fhex
  • 32dec = 20hex

Hexadezimalnotation

hex-Literale in Python:

  • 1 = 0x1
  • 9 = 0x9
  • 10 = 0xa
  • 15 = 0xf
  • 16 = 0x10
  • 17 = 0x11
  • 31 = 0x1f
  • 32 = 0x20

Erstellen

Erstellen von Bytes aus einer Liste von Zahlen:

a = bytes([0, 64, 112, 160, 255])
b = bytes([0, 0x40, 0x70, 0xa0, 0xff])

Erstellen von Bytes aus einem Byte String-Literal:

c = b"\x00\x40\x70\xa0\xff"

ASCII-Werte können direkt verwendet werden (\x40 = "@", \x70 = "p"):

d = b"\x00@p\xa0\xff"

Bytes

Standard Repräsentation in Python:

print(bytes([0x00, 0x40, 0x70, 0xa0]))
b'\x00@p\xa0\xff'

Wenn möglich werden bytes als ASCII-Zeichen dargestellt; sonst wird ihr Hexadezimalcode angezeigt

Bytes und Strings

Bytes beinhalten of codierten Text

Wenn wir das Encoding kennen, können wir zwischen Bytes und Strings wechseln:

'ä'.encode('utf-8')
# b'\xc3\xa4'
b'\xc3\xa4'.decode('utf-8')
# 'ä'

Sequenzen

Sequenzen

Sequenzen sind Objekte, die aus einer Aufreihung anderer Objekte bestehen, z.B.:

  • Listen
  • Tupel
  • Strings
  • Bytes

Operationen auf Sequenzen

  • Elementzugriff (via index): s[2]
  • Zugriff auf mehrere Elemente: s[2:4]
  • Konkatenation: s + t
  • Wiederholung: 3 * s
  • Länge: len(s)
  • for-Schleife: for el in s:
  • if-Abfrage: if el in s:

Operationen auf Sequenzen

Elementzugriff

users = ['mike', 'tim', 'theresa']

users[0] # 'mike'
users[-1] # 'theresa'

Operationen auf Sequenzen

Änderung von Elementen (falls Sequenz veränderlich ist)

users = ['mike', 'tim', 'theresa']

users[0] = 'molly'

Operationen auf Sequenzen

Zugriff auf mehrere Elemente

users = ['mike', 'tim', 'theresa']

users[0:2] # ['mike', 'tim']

Operationen auf Sequenzen

Konkatenation

users = ['mike', 'tim', 'theresa']

new_users = users + ['tina', 'michelle']

Operationen auf Sequenzen

Wiederholung

users = ['mike', 'tim', 'theresa']

new_users = users * 3

Operationen auf Sequenzen

Länge

users = ['mike', 'tim', 'theresa']

print(len(users))

Operationen auf Sequenzen

for-Schleife

users = ['mike', 'tim', 'theresa']

for user in users:
    print(user.upper())

Dictionaries

Dictionaries

Dictionaries sind Zuordnungen, die bestimmten Einträgen zugehörige Werte zuweisen.

person = {
    "first_name": "John",
    "last_name": "Doe",
    "nationality": "Canada",
    "birth_year": 1980
}

Dictionaries

Elementzugriff bei dictionaries

person["first_name"] # "John"

Dictionaries

Iteration über Dictionaries

for entry in person:
    print(entry)

Dies liefert die Schlüssel: "first_name", "last_name", "nationality", "birth_year"

Seit Python 3.7 bleiben die Schlüssel garantiert in der ursprünglichen Reihenfolge

Dictionaries

Iteration über Schlüssel/Werte - Paare:

for key, value in person.items():
    print(f'{key}, {value}')

Operationen auf Dictionaries

d = {0: 'zero', 1: 'one', 2: 'two'}

d[2]
d[2] = 'TWO'
d[3] # KeyError
d.get(3) # None
d.setdefault(2, 'n')
d.setdefault(3, 'n')

d.keys()
d.items()

d1.update(d2)

Gültige Keys

Jedes unveränderliche Objekt kann als Key verwendet werden - meistens sind es Strings

Ãœbungen

  • Vokabeltrainer
  • Todo-Liste

Objektorientierung und Klassen

Objektorientierung in Python: "Alles ist ein Objekt"

a = 20

a.to_bytes(1, "big")

"hello".upper()

Typen und Instanzen

message = "hello"

type(message)

isinstance(message, str)

Klassen

Klassen können verschiedenste Dinge repräsentieren, z.B.:

  • eine Nachricht in einem E-Mail-Programm
  • einen Benutzer einer Website
  • ein Auto in einem Computer-Rennspiel
  • einen Einkaufskorb in einem Onlineshop
  • ein Bankkonto
  • einen Datensatz, der analysiert werden soll
  • ...

Klassen

Definition einer Klasse umfasst üblicherweise:

  • "Datenstruktur" (Attribute)
  • "Verhalten" (Methoden)

Klassen

Beispiel: Klasse TextIOWrapper kann eine Textdatei repräsentieren (wird beim Aufruf von open() erstellt)

Attribute:

  • closed
  • encoding
  • mode (e.g. r=read, w=write)
  • name (filename)
  • ...

Methoden:

  • close()
  • read()
  • write()
  • ...

Klassen

Beispiel: Klasse BankAccount

  • "Datenstruktur" (Attribute)
  • "Verhalten" (Methoden)

Definition von Klassen

class MyClass():

    # die Methode __init__ initialisiert das Objekt
    def __init__(self):
        # self bezieht sich in jeder Methode
        # auf die aktuelle Instanz
        self.message = "hello"

instance = MyClass()
instance.message # "hello"

Private Attribute und Methoden und Python-Philosophie

Kennzeichnung von Attributen und Methoden, die von außen nicht verwendet werden sollten mit _

We're all consenting adults here: https://mail.python.org/pipermail/tutor/2003-October/025932.html

Achtung: oft Fehlinformation bezüglich __! In der Praxis sollten doppelte Unterstriche kaum verwendet werden.

Beispiel: Umsetzung einer Length-Klasse

a = Length(2.54, "cm")
b = Length(3, "in")

a.unit
a.value

Ãœbung: TodoList- und Todo-Klassen

tdl = TodoList("groceries")

tdl.add("milk")
tdl.add("bread")

print(tdl.todos)
tdl.todos[0].toggle()

tdl.stats() # {open: 1, completed: 1}

Vererbung und Komposition

Vererbung und Komposition

öfters verwenden wir eine Klasse als Basis für eine andere Klasse

z.B.:

  • User-Klasse als Basis der AdminUser-Klasse
  • TicTacToeGame als Basis von TicTacToeGameGUI

Vererbung und Komposition

Vererbung: ein AdminUser ist ein User

Komposition: TicTacToeGameGUI könnte TicTacToeGame im Hintergrund verwenden

häufiges Mantra: Composition over Inheritance: verwende Vererbung nicht zu sehr

Vererbung

Vererbung:

class User():
    ...

class AdminUser(User):
    ...

die AdminUser-Klasse erbt automatisch alle bestehenden Methoden der User-Klasse

Komposition

Komposition:

class TicTacToeGame():
    ...

class TicTacToeGameGUI():
    def __init__(self):
        self.game = TicTacToeGame()

Vererbung

Beispiel zur Vererbung - Datenbankmodell in Django:

from django.db import models

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

Coding-Stil

PEP8

Standard-Stil für Python-Code

offizielles Dokument: https://www.python.org/dev/peps/pep-0008/

cheatsheet: https://gist.github.com/RichardBronosky/454964087739a449da04

Code-Formatierungs-Tools

  • black
  • autopep8
  • yapf

In VS Code-Einstellungen: "python.formatting.provider": "black"

Code-Formatierungs-Tools

input:

a='Hello'; b="Have you read \"1984\"?"
c=a[0+1:3]

output via black:

a = "Hello"
b = 'Have you read "1984"?'
c = a[0 + 1 : 3]

Python-Philosophie, Zen of Python

Auszüge aus dem Zen of Python (anzeigbar via import this):

  • Explicit is better than implicit.
  • Readability counts.
  • Special cases aren't special enough to break the rules.
  • There should be one-- and preferably only one --obvious way to do it.

Docstrings

Docstrings

Dokumentationsstrings, die z.B. Funktionen genauer beschreiben

Kommentare in einer Funktion: helfen Programmierern, die an dieser Funktion arbeiten

Docstring einer Funktion: hilft Programmierern, die diese Funktion verwenden

Docstrings

Beispiel:

def fib(n):
    """Compute the n-th fibonacci number.

    n must be a nonnegative integer
    """
    ...

Docstrings ausgeben

help(fib)
help(round)

Debuggen

Debuggen

Breakpoints (Haltepunkte) können gesetzt werden, um die Ausführung des Codes an diesem Punkt zu pausieren.

Möglichkeiten, um Breakpoints zu setzen:

  • in VS Code links neben die Zeilennummer klicken
  • direkt in Python mittels breakpoint() (seit Python 3.7)

Ausführung in VS Code:

  • Symbol "Run and Debug" in der linken Toolbar
  • wähle "create a launch.json file" - "Python" - "Python File"

Debuggen

Manuell weiterspringen:

  • bis zum nächsten Breakpoint weiter ausführen:
    • Continue in VS Code
    • c für continue im Python Debugger
  • debugging beenden:
    • Stop in VS Code
    • q für quit im Python Debugger

Debuggen

Manuell weiterspringen:

  • in die nächste Zeile springen:
    • Step Over in VS Code
    • n für next im Python Debugger
  • in die nächste Zeile springen - und evtuell einem Funktionsaufruf folgen:
    • Step Into in VS Code
    • s für step im Python Debugger
  • die aktuelle Funktion verlassen:
    • Step Out in VS Code
    • r für return im Python Debugger

Debuggen

Werte in VS Code begutachten:

  • direkt unter variables
  • eigene Ausdrücke angeben unter watch

Ausgabe von Werten im Python Debugger mittels p für print:

p mylist
p mylist[0]

Kontrollstrukturen

Kontrollstrukturen

  • if
  • Schleifen
    • while
    • for ... in ...
    • for ... in range(...)
  • try ... except ...

if

if

wir erinnern uns:

if age_seconds < 1000000000:
    print("You are less than 1 billion seconds old")
else:
    print("You are older than 1 billion seconds")

Kriterien bei if

Bei Kriterien für if und while verwenden wir üblicherweise Ausdrücke, die bei der Auswertung boolesche Werte ergeben.

Wir könnten jedoch auch andere Typen verwenden:

a = 0
if a: ...

name = input("enter your name")
if name: ...

products = []
if products: ...

Diese Typen werden in boolesche Werte konvertiert bevor sie als Kriterium herangezogen werden.

Kriterien bei if

Grundsätzlich können beliebige Werte als Kriterium verwendet werden. Die meisten sind dabei "truthy".

Folgende Werte gelten als "falsy" - ein Aufruf von bool(...) liefert False zurück:

  • False
  • 0, 0.0
  • None
  • leere Sammlungen ("", [], (), {})
  • Vor Python 3.5: Mitternacht (datetime.time(0, 0, 0))

Kriterien bei if

nicht "pythonic":

name = input("Enter your name:")
if name != "":
    ...

"pythonic":

name = input("Enter your name:")
if name:
    ...

Verketten von Vergleichen

Überprüfen, ob age im Bereich 13-19 liegt:

13 <= age and age <= 19

kürzere Version:

13 <= age <= 19

Überprüfen, ob a und b beide 0 sind (kurze Version):

a == b == 0

if Expression

Ein Ausdruck, der einen von zwei möglichen Werten ergibt - basierend auf einem booleschen Kriterium

size = 'small' if length < 100 else 'big'

In anderen Sprachen:

// JavaScript
size = length < 100 ? 'small' : 'big';

For-Schleifen

For-Schleifen mit Entpacken von Tupeln

Wiederholung: Entpacken von Tupeln

time = (23, 45, 0)

hour, minute, second = time

For-Schleifen mit Entpacken von Tupeln

Aufzählen von Listenelementen:

l = ['Alice', 'Bob', 'Charlie']

for i, name in enumerate(l):
    print(f'{i}: {name}')

Enumerate gibt eine Datenstruktur zurück, die sich wie die folgende Liste verhält:

[(0, 'Alice'), (1, 'Bob'), (2, 'Charlie')]

For-Schleifen mit Entpacken von Tupeln

Auflisten von Ordnerinhalten (inklusive Unterodner) mittels os.walk:

import os

for directory, dirs, files in os.walk("C:\\"):
    print(f"{directory} {files}")
C:\ []
C:\PerfLogs []
C:\Program Files []
C:\ProgramData []
...

Continue

Continue

Schlüsselwort continue: kann verwendet werden, um den Rest der aktuellen Iteration einer Schleife zu überspringen

Beispiel:

for name in os.listdir("."):
    if not name.endswith(".txt"):
        # skip .txt files
        continue
    # process other files here

Comprehension

List Comprehension

Wichtige Möglichkeit, um Listen basierend auf anderen Listen zu erstellen

In anderen Programmiersprachen oft umgesetzt mittels map und filter / grep

List Comprehension

Umwandeln der Einträge:

names = ["Alice", "Bob", "Charlie"]

uppercase_names = [name.upper() for name in names]

Resultat:

["ALICE", "BOB", "CHARLIE"]

List Comprehension

Filtern:

amounts = [10, -7, 8, 19, -2]

positive_amounts = [amount for amount in amounts if amount > 0]

result:

[10, 8, 19]

List Comprehension

Allgemeine Syntax:

new_list = [new_entry for entry in old_list]

new_list = [new_entry for entry in old_list if condition]

Dictionary Comprehension

colors = {
  'red': '#ff0000',
  'green': '#008000',
  'blue': '#0000ff',
}
m_colors = { color: colors[color][1:] for color in colors}

or

m_colors = {
    name: value[1:] for name, value in colors.items()
}

Ãœbung

  • Beispiel Todo-Liste: Entfernen erledigter Todos
  • BankAccount: separate Listen aller Ein- bzw Auszahlungen

Exceptions (Ausnahmen)

Arten von Exceptions

  • AttributeError, IndexError, KeyError
  • NameError
  • TypeError
  • ValueError
  • IOError
  • ZeroDivisionError
  • ...

Übung: versuche, jede der obigen Exceptions auszulösen

Exceptions abfangen

age_str = input("Enter your age")
try:
    age = int(age_str)
except ValueError:
    print("Could not parse input as number")

Exceptions abfangen

age_str = input("Enter your age")
try:
    age = int(age_str)
except ValueError as e:
    print("Could not parse input as number")
    print(e)
    print(e.args)

Exceptions abfangen

Mehrere Arten von Exceptions abfangen:

try:
    file = open("log.txt", encoding="utf-8")
except FileNotFoundError:
    print("could not find log file")
except PermissionError:
    print("reading of file is not permitted")
except Exception:
    print("error when reading file")

Exceptions abfangen

Einsatz von finally:

try:
    file = open("log.txt", "w", encoding="utf-8")
    file.write("abc")
    file.write("def")
except IOError:
    print("Error when writing to file")
finally:
    file.close()

Exceptions abfangen

Einsatz von else:

try:
    file = open("log.txt", "w", encoding="utf-8")
except IOError:
    print("could not open file")
else:
    # no errors expected here
    file.write("abc")
    file.write("def")
file.close()

Python-Philosophie: EAFP

LBYL: Look before you leap

EAFP: It's easier to ask for forgiveness than permission

(Beispiel: Parsen von Zahlen)

Exceptions auslösen

Exceptions auslösen

raise ValueError('test')

Abgefangene Exceptions erneut auslösen

try:
    ...
except ClientError as e
    if "DryRunOperation" not in str(e):
        raise

Eigene Exceptions

Eigene Exceptions können wir als Unterklassen von Exception definieren

class MoneyParseException(Exception):
    pass

raise MoneyParseException()

Eigene Module: fortgeschritten

Eigene Module: fortgeschritten

Modul als Verzeichnis:

- foo/
  - __init__.py
# __init__.py
a = 1
b = 2

Eigene Module

Modul als Ordner mit separaten Definitionen:

- foo/
  - __init__.py
  - _a_mod.py
  - _b_mod.py
# __init__.py
from foo._a_mod import a
from foo._b_mod import b

Auflösen von Imports

Alle Pfade für Imports sehen wir via:

import sys
print(sys.path)

Kompilieren von Modulen

Importierte Module werden in kompilierter Form abgelegt, um später schneller eingelesen werden zu können.

Wir finden die kompilierten Versionen im Ordner __pycache__

Modulname und Einstiegspunkt

in einem importierten Module bezieht sich die Variable __name__ auf dessen Namen

wurde eine Python-Datei direkt ausgeführt und nicht importiert, ist deren __name__ gleich "__main__"

if __name__ == "__main__":
    print("this file was run directly (and not imported)")

Package versions and virtual environments

Package versions

installing a package via PIP:

pip install cowsay

installing a specific version:

pip install cowsay==6.1

installing a compatible version (this could also install versions 6.2, 6.3, etc. - if they are available):

pip install cowsay~=6.1

Virtual environments

virtual environments: allow for installing different dependencies and dependency versions for different projects

Virtual environments

creating a virtual environment (typically named ".venv"):

python -m venv .venv

will create a new folder ".venv/" which contains the virtual environment

Virtual environments

activating an environment on Windows:

./.venv/Scripts/activate

deactivating a venv:

deactivate

if necessary: enable execution of local scripts on Windows - from an admin terminal:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned

Dependency lists

"old" configuration file: requirements.txt

"new" configuration file: pyproject.toml

Dependency lists

example requirements.txt:

cowsay~=6.1
requests~=2.30

installation from requirements.txt:

pip install -r requirements.txt

Dependency lists

example pyproject.toml:

[project]
name = "my-python-project"
version = "1.0"
dependencies = [
  "cowsay~=6.1",
  "requests~=2.30",
]

installation:

pip install .

Funktionen

Beliebige Anzahl an Parametern (args / kwargs)

def foo(*args, **kwargs):
    print(args)
    print(kwargs)

foo("one", "two", x="hello")
# args: ("one", "two")
# kwargs: {"x": "hello"}

args ist ein Tupel, kwargs ein Dictionary.

Beliebige Anzahl an Parametern (args / kwargs)

Aufgabe: "Nachbau" von range() mit Hilfe einer while-Schleife

Entpacken von Parameterlisten

numbers = ["one", "two", "three"]

# equivalent:
print(numbers[0], numbers[1], numbers[2])

print(*numbers)

Globaler und lokaler Scope

global / nonlocal

Spielt beim Zuweisen von Variablen eine Rolle

Globaler und lokaler Scope

Beispiel: Schere, Stein, Papier

import random
wins = 0
losses = 0
def play_rock_paper_scissors():
    player = input("rock, paper or scissors?")
    opponent = random.choice(["rock", "paper", "scissors"])
    if player == opponent:
        pass
    elif (
        (player == "rock" and opponent == "scissors")
        or (player == "paper" and opponent == "rock")
        or (player == "scissors" and opponent == "paper")
    ):
        global wins
        wins += 1
    else:
        global losses
        losses += 1
while input("play? (y/n)") != "n":
    play_rock_paper_scissors()
print(f"won: {wins}, lost: {losses}")

Globaler und lokaler Scope

Eine bessere Alternative zum Keyword global ist oft das Verwenden einer Klasse:

import random
class RockPaperScissors():
    def __init__(self):
        self.wins = 0
        self.losses = 0
    def play(self):
        ...
    def run(self):
        while input("play? (y/n)") != "n":
            self.play()
        print(f"won: {wins}, lost: {losses}")

Objektreferenzen und Mutationen

Objektreferenzen und Mutationen

Was ist der Wert von a am Ende dieses Programms?

a = [1, 2, 3]
b = a
b.append(4)

Objektreferenzen und Mutationen

Eine Zuweisung (z.B. b = a) versieht ein existierendes Objekt mit einem neuen (zusätzlichen) Namen.

Im Hintergrund steht nach wie vor nur ein einzelnes Objekt.

Objektreferenzen und Mutationen

Durch das Ausführen von b = a entsteht einen zusätzliche Referenz, die sich auf das selbe Objekt bezieht.

Operationen, die zum Setzen zusätzlicher Referenzen führen:

  • Zuweisungen (b = a)
  • Funktionsaufrufe (myfunc(a) - es entsteht eine neue funktionsinterne Referenz)
  • Eintragungen in Kollektionen (z.B. mylist.append(a))
  • ...

Objektreferenzen bei Funktionen

Der Aufruf einer Funktion mit einem Objekt als Parameter versieht dieses Objekt mit einer weiteren Referenz (call by sharing).

def foo(a_inner):
    print(id(a_inner))

a_outer = []
foo(a_outer)
print(id(a_outer))

Side Effects und reine Funktionen

Side Effects und reine Funktionen

reine Funktionen: Funktionen, die mit ihrer Umgebung nur dadurch interagieren, dass sie Parameter entgegennehmen und Werte zurückgeben

Side Effects (Nebeneffekte, Seiteneffekte): Aktionen einer Funktion, die die Umgebung verändern

Side Effects und reine Funktionen

häufige Side Effects:

  • ändern der Einträge von self in einer Objektmethode
  • Input / Output (Interaktion mit Speichermedien / Netzwerk / ...)

Side Effects, die üblicherweise vermieden werden:

  • Abändern von Argumenten, die an die Funktion übergeben wurden
  • Setzen / Ändern globaler Variablen

Reine Funktionen

Vorteile reiner Funktionen:

  • einfacher zu beschreiben
  • einfacher wiederzuverwenden
  • einfacher zu testen

Side Effects

Beispiel einer nicht-idealen Funktion, die ein Argument (formats) verändert:

def list_files_by_formats(path, formats):
    if "jpg" in formats:
        formats.append("jpeg")
    files = []
    for file in os.listdir(path):
        for format in formats:
            if file.endswith("." + format):
                files.append(file)
                break
    return files

Side Effects

formats = ["jpg", "png"]

print(list_files_by_formats(formats))

print(formats)

# will print: ["jpg", "png", "jpeg"]

Side Effects

"korrektere" Implementierung:

def list_files_by_formats(path, formats):
    if "jpg" in formats:
        formats = formats.copy()
        formats.append("jpeg")
    # ...

Side Effects

das folgende wäre ein Anti-Pattern (eine Funktion, die Argumente abändert):

mylist = [2, 1, 3]

sort(mylist)
print(mylist)
# [1, 2, 3]

Side Effects

tatsächliche Möglichkeiten zum Sortieren in Python:

reine Funktion:

print(sorted(mylist))

Methode, die ein Objekt abändert:

mylist.sort()
print(mylist)

Abändern von Standardparametern

Unerwartetes Verhalten in Python, wenn Standardparameter abgeändert werden:

def list_files_by_formats(path, formats=["gif", "jpg", "png"]):
    if "jpg" in formats:
        formats.append("jpeg")
    # ...
list_files_by_formats(".")
# formats: ["gif", "jpg", "png", "jpeg"]

list_files_by_formats(".")
# formats: ["gif", "jpg", "png", "jpeg", "jpeg"]

(Web-Suche: mutable default arguments)

Python Versionen

Python Versionen

Python 2 vs Python 3

Strings und Bytes

Tiefgreifende Änderung in Python 3:

Strikte Trennung von Text (strings) und Binärdaten (bytes)

in Python 2: Datentypen bytes, str und unicode

Print

Python 2:

print "a",

Python 3:

print("a", end="")

Division

Python 2:

10 / 3    # 3

range

in Python 2: range() liefert Liste zurück, xrange() liefert speicherschonendes Objekt

in Python 3: range() liefert speicherschonendes Objekt

input

in Python 2: input() wertet die Eingabe aus, raw_input() gibt String zurück

in Python 3: input() gibt String zurück

__future__ imports

Verhalten von Python 3 in Python 2 übernehmen:

from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division

Python-Future

Kompatibilitätsschicht zwischen Python 2 und Python 3

Unterstützung von Python 2 und Python 3 aus der gleichen Codebase