Prof. Dr.-Ing. Oliver Radfelder
Informatik / Wirtschaftsinformatik
Hochschule Bremerhaven
Kurz und Knapp: Python

Loggen Sie sich auf hopper ein. Erzeugen Sie ein Arbeitsverzeichnis python-project und schreiben Sie dort Ihr erstes Python-Programm:

$ touch simple.py
$ chmod +x simple.py 
$ vim simple.py
Geben Sie langsam und mit korrekter Formatierung den folgenden ersten Programmcode ein:
#!/usr/bin/python
# ein Kommentar
print("Moin")
mylist = ["moin", "tach", "hi"]
print(mylist)
for el in mylist:
  if el == "moin":
    print("moin moin")
  else:
    print(el)

Dann können Sie - wie immer - direkt aus dem vim heraus mit :!./simple.py das Programm starten.

Es lassen sich auf den ersten Blick einige Besonderheiten in Python erkennen:

Assoziative Arrays - in Python dict bzw. dictionary - sind ähnlich gut integriert wie Listen:

mydict = {"age" : 30}
mydict["name"] = "paul"
print(mydict)

Neben Listen und Dictionaries gibt es noch Tuple und Sets. Besonders Tuple sind interessant. Sie ähneln Listen, sind aber unveränderbar. Etwas eigenartig mutet an, dass ein Tuple mit einem Element dennoch ein abschließendes Komma benötigt. Bisweilen geben Funktionen Tuple zurück - dann lassen sie sich elegant in Variablen aufsplitten.

mytuple = (1,2)
singletuple = (1,)

def myfunction():
  return "string", 7
s, num = myfunction()
print(s+"..."+str(num))

Sowohl Listen als auch Tuple und Strings lassen sich slicen.

lst = [1,2,3,4,5,6]
print(lst)
#[1, 2, 3, 4, 5, 6]
print(lst[2:5])
#[3, 4, 5]

Die Formatierung von Strings ist recht ausgereift (siehe: guru99). Mit der format-Methode von Strings werden Argumente angegeben, die nacheinander in die Platzhalter {} eingesetzt werden. Innerhalb des Platzhalters können Ausrichtung und Formatierungen angegeben werden.

print("|{:<10}|{:^10}|{:>10}|".format("str1","str2","str3"))
#|str1      |   str2   |      str3|

print("|{:10.4f}|".format(3/2))
#|    0.6667|

Noch etwas moderner sind die f-Strings (siehe:realpython):

name='paul'
num=3/2
print(f"{name} {num:.4f}")

Module werden mit import importiert - danach kann über modulname.fct() auf Funktionen des Moduls zugegriffen werden. Funktionen werden mit def function_name(): deklariert und mit function_name() aufgerufen. Mit try/except wird Exception-Handling betrieben.

#!/usr/bin/python
import signal,sys

def signal_handler(sig, frame):
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)

while True:
    try:
        s=input()
        print("["+s+"]")
    except EOFError:
        break

Obiges Listing entspricht dem Standardverhalten eines Unix-kompatiblen Programmes, das mit Strg-C abgebrochen werden kann und dem ein EndOfFile-Signal mit Strg-D gesandt werden kann, ohne dass auf dem Standardfehlerkanal störende Fehlermeldungen ausgegeben werden.

Dateisystem und Co

Dateien lassen sich recht einfach ganz lesen und schreiben.

#!/usr/bin/python
content = """das ist ein String
der über zwei Zeilen geht"""

with open('datei.txt','w') as file:
  file.write(content)

with open('datei.txt','r') as file:
  content = file.read()
  print(content)

Besonders hier lohnt sich das with-Statement, weil damit auf initialisierten Variablen sowohl am Ende close (wenn möglich) aufgerufen wird, als auch das Exception-Handling übernommen wird.

Neben diesem typischen Pythonweg gibt es aber auch das traditionelle Line by Line Lesen - in Python gerne mit einer for-Schleife. Beachte das strip bei jeder Zeile, um das Zeilenendezeichen abzuschneiden ...

file = open('datei.txt', 'r')
print("Using for loop")
for line in file:
    print(line.rstrip())
file1.close()

Das geht auch mit stdin:

import sys
for line in sys.stdin:
    print(line.strip())

Networking

Networking ist in Python ausgesprochen C-ähnlich. Ein ServerSocket wird erstellt, an ein Interface und einen Port gebunden und mit listen aktiviert. Mit accept wird blockierend gewartet, bis eine Verbindung eröffnet wird und aus dem rohen Socket mit makefile ein File-artiger Zugang erzeugt werden kann. Danach ist es wichtig, auf die richtige Reihenfolge von readline, rstrip, write, flush und close zu achten - in Python wird bei readline der Zeilenumbruch ungewöhnlicherweise mitgeliefert.

Hier der Server:

#!/usr/bin/python
import socket
addrinfo=socket.getaddrinfo("0.0.0.0",7000)
seso=socket.socket()
seso.bind(addrinfo[0][-1])

seso.listen()
while True:
  conn, address = seso.accept()
  print("connect:"+str(address))
  socketfile = conn.makefile("rw")
  while True:
    line = socketfile.readline()
    if not line:
      break
    line=line.rstrip("\n")
    print(line)
    socketfile.write(f"[{line}]\n")

    #darf in micropython nicht aufgerufen werden
    socketfile.flush()

  socketfile.close()
  conn.close()

Und hier der passende Client:

#!/usr/bin/python
import socket

addrinfo=socket.getaddrinfo("localhost",7000)
sock=socket.socket()
sock.connect(addrinfo[0][-1])

socketfile = sock.makefile("rw")
print("connected")
for i in range(3):
  socketfile.write(f"message {i}\n")

  #darf in micropython nicht aufgerufen werden
  socketfile.flush()

  line = socketfile.readline().rstrip("\n")
  if not line:
    break
  print("got:"+line)


Sonstiges

Das Modul Json

Das json-Modul bietet Möglichkeiten, aus Python-Strukturen mit dumps ein json-Objekt zu erzeugen oder mit loads umgekehrt aus einem String eine Python-Struktur zu erzeugen. Mit load und dump lässt sich direkt mit Dateien arbeiten.

#!/usr/bin/python
obj={
    "name" : "raspberry",
    "type" : "fruit"
    }
print(obj)
jsonstr = json.dumps(obj)
print(jsonstr)
print(json.loads(jsonstr)["name"])

with open('config.json') as f:
  config = json.load(f)
with open('config.json', 'w') as f:
  json.dump(config, f)


Das Modul requests

Mit requests lassen sich recht einfach http-Requests durchführen.

#!/usr/bin/python
import requests

r = requests.get('https://informatik.hs-bremerhaven.de/oradfelder/demo.txt')
print(r.status_code)
print(r.text)

Das Modul multiprocessing

Mit multiprocessing können mehrere Prozesse von Python aus koordiniert werden:

#!/usr/bin/python
import multiprocessing
import os
import time

def task(sleeptime):
  for i in range(1,10):
    time.sleep(sleeptime)
    print(f"this is in ... {os.getpid()} ")

process = multiprocessing.Process(target=task,args=(0.0002,))
process.start()

for i in range(1,10):
  time.sleep(0.002)
  print(f"this is in ... {os.getpid()} ")

process.join()

Das Modul os

#!/usr/bin/python
import os
print(os.getpid())
print(os.getcwd())
print(os.listdir())
os.chdir("/tmp")
os.mkdir("mytmp")
os.rename('mytmp','mytemp')
os.rmdir("mytemp")

Das Modul csv

Mit csv ist der Zugriff auf csv-Dateien möglich:

#!/usr/bin/python
import csv

with open('names.txt') as csv_file:
    csv_reader = csv.reader(csv_file, delimiter=',')
    for row in csv_reader:
         print(f'{row[0]} - {row[1]}.')

with open('newnames.csv', mode='w') as csv_file:
    writer = csv.writer(csv_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
    writer.writerow(['Peter', 'November'])
    writer.writerow(['Erica', 'March'])

Generatoren, yield, Iteratoren

In Python findet sich das Schlüsselword yield recht häufig. Generatoren sind Funktionen, die als Iteratoren dienen. Bei jedem yield wird die Ausführung angehalten bis zum nächsten Aufruf der Funktion next(gen). Wenn der Generator in einer for-Schleife genutzt wird, wird next implizit aufgerufen.

def square_gen(n):
  num = 0
  while num < n:
    num +=1
    yield(num*num)

for i in square_gen(100):
  print(i)

Häufig sieht man so etwas mit Funktionen, die ein Iterable erwarten - so wie z.B. sum():

def firstn(n):
    num = 0
    while num < n:
        yield num
        num += 1

thesum = sum(firstn(1000000))
print(thesum)

map und Comprehensions

Die Funktion map erwartet eine Funktion und ein Iterable und wendet die Funktion auf jedes Element an und liefert einen Map-Iterator zurück.

prices = [1.09, 23.56, 57.84, 4.56, 6.78]
def get_price_with_tax(price):
  return price * (1.21)
final_prices = map(get_price_with_tax, prices)

Als List Comprehension formuliert sieht es wie folgt aus:

squares = [price * 1.21 for price in prices]

Es gibt auch Set und Map Comprehensions:

elements=[2,4,5,7,2]
{e for e in elements}

{n:n*n for n in range(10)}

Decorators

Python hat einiges aus der funktionalen Programmierung übernommen. In diesem Sinne können Funktionen an Funktionen als Argumente übergeben werden:

def mydecorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

def my_code():
    print("my code!")

my_code = mydecorator(my_code)

@mydecorator
def sugar():
    print("syntactic sugar")

todo

args kwargs

Virtuelle Umgebungen

Mit virtuellen Umgebungen lassen sich weitere Module lokal in einzelnen Projekten installieren. Dafür muss mit dem Modul venv ein Projekt angelegt werden und das Helfer-Script bin/activate jedes Mal aufgerufen werden, wenn man in dem Projekt arbeiten will. Zum Beenden ruft man deactive auf.

python -m venv myproject
cd myproject
source bin/activate
pip install scipy
...
deactivate

Das Modul mariadb

Das Modul mariadb muss beispielsweise zunächst installiert werden, was am Besten innerhalb einer virtuellen Umgebung geschieht:

pip install mariadb

Dann lässt sich die Datenbankverbindung wie in anderen Programmiersprachen öffnen und mit einem Cursor über eine Ergebnismenge iterieren:

#!/usr/bin/python
import mariadb
import sys

try:
    conn = mariadb.connect(
        user="demo",
        password="daspasswortinmycnf",
        host="mysql-server",
        port=3306,
        database="demo_db",
        autocommit=True
    )

except mariadb.Error as e:
    print(f"Error connecting to MariaDB: {e}")
    sys.exit(1)

cur = conn.cursor()
cur.execute("INSERT INTO demo (name) VALUES (?)", ("karin",))


cur.execute(
    "SELECT * FROM demo WHERE id > ? or name=?",
    (1,"ulrike"))

for (id,name) in cur:
    print(f"id: {id}, name: {name}")

conn.close()

Anleitung auf mariadb.com. Für die Anbindung an PostgreSQL ist der übliche Datenbanktreiber psycopg2.

fcntl

Mit fcntl lassen sich ähnlich wie in C (flock) und in der Bash (flock) kompatible Locks erzeugen:

import os
import fcntl
import time

def acquire(lock_file):
    open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC
    fd = os.open(lock_file, open_mode)
    fcntl.flock(fd, fcntl.LOCK_EX)
    return fd

def release(lock_file_fd):
    fcntl.flock(lock_file_fd, fcntl.LOCK_UN)
    os.close(lock_file_fd)
    return None

def main():
    fd = acquire('myfile.lock')

    if fd is None:
        print(f'ERROR: {pid} lock NOT acquired')
        return -1
    print(f'acquired')
    time.sleep(1.0)
    release(fd)
    print(f"lock released")


if __name__ == '__main__':
    main()

NumPi, Pandas, DataTable, Dash, ...

Pandas sind die Grundlage für viele Operationen auf csv-artigen Daten ...

Das Modul redis

Das Modul redis muss ebenfalls installiert werden:

pip install redis

Dann lässt sich mit redis wie üblich arbeiten:

#!/usr/bin/python
import redis

rconn = redis.Redis(host='localhost', port=6379, decode_responses=True)
rconn.auth("daspasswortinmycnf")

rconn.set("foo","pythonbar")
print(rconn.get("foo"))
rconn.close()

Anleitung auf redis.io.

Das Modul PyInstaller

Mit PyInstaller lassen sich auf der jeweiligen Plattform ausführbare Binaries erstellen:

pip install -U pyinstaller
pyinstaller myprogram.py

Anleitung auf https://pyinstaller.org/.

Websocket Client

Mit dem Modul websocket lässt sich zunächst ein recht einfacher Websocket-Client erzeugen:

import sys
import websocket

#websocket.enableTrace(True)
ws = websocket.WebSocket()
ws.connect("wss://informatik.hs-bremerhaven.de/docker-oradfelder-ws/")
for i in range(10000):
  ws.send("moin"+str(i))
  print(ws.recv())
  sys.stdout.flush()
ws.close()

Hingegen ist ein vernünftiger Websocket-Server nur sinnvoll mit asyncio zu realisieren:

import asyncio
import websockets

async def echo(websocket):
      async for message in websocket:
        # print(message)
        await websocket.send(message)

async def main():
    async with websockets.serve(echo, "0.0.0.0", 7000):
        # run forever
        await asyncio.Future()

asyncio.run(main())

Das Modul playwright

Mit playwright lassen sich Web-basierte automatische Tests erstellen:

python -m venv pyplay
cd pyplay
source bin/activate
pip install playwright
playwright install
echo '
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("http://playwright.dev")
    print(page.title())
    browser.close()
' > demo.py
python demo.py

Anleitung auf https://playwright.dev/python/docs/library.

Ansteuern der LED auf dem Firebeetle ESP32


from machine import Pin

led = Pin(2,Pin.OUT)
led.value(1)


WiFi Verbindung aufbauen in micropython


import network
ssid="..."
password="..."

station=network.WLAN(network.STA_IF)
station.active(True)
station.connect(ssid,password)

while station.isconnected() == False:
  pass

Flask und gunicorn

Flask ist ein kleines Web-Framework. Zunächst ist Flask eine WSGI-Anwendung:

from flask import Flask

app = Flask(__name__)

@app.route("/docker-oradfelder-http/")
def hello_world():
    return "

Hello, World!

"

Flask beinhaltet einen kleinen Entwicklungsserver:

flask --app hello run --host=0.0.0.0 --port=4000

gunicorn ist ein produktiver WSGI-Server:

from flask import Flask, session

app = Flask(__name__)
app.secret_key = "abc"

@app.route('/docker-oradfelder-http/')
def index():
    session['response']='session#1'
    return '

Application Deployed!

\n' @app.route('/docker-oradfelder-http/get') def getVariable(): if 'response' in session: s = session['response']; return f"{s}" else: return f"get\n" if __name__ == '__main__': app.run(debug=False)
gunicorn -D -p ~/var/gunicorn.pid -b 0.0.0.0:4000 -w 8 application:app

WSGI-Server sind synchron arbeitende Http-Server. Etwas moderner - da asynchron mit asyncio umgesetzt - sind ASGI-Server wie uvicorn, FastAPI, Starlette oder Quart.

Links