👁️
pierreinside
  • Introduction
  • Workbench
    • VirtualBox
    • Linux
      • Linux-Paketverwaltung
      • Linux Initialisierung
      • Ubuntu 14.10 LTS
      • Ubuntu 16.04 LTS
      • Ubuntu 18.04 LTS
      • Ubuntu 20.04 LTS
      • Ubuntu - Netzwerk
    • Konsole
      • ssh
      • zsh
      • cygwin
      • Babun
      • terminator
      • Terminal Multiplexer
      • Linux Tools
    • awesome
    • Entwicklungsumgebungen
      • Texteditors
      • Visual Studio Code
      • IntelliJ - IDEA
  • Softwareentwicklungsprozess
    • Schätzungen
    • Eventstorming
    • OKR
  • Architektur
    • Uncle Bob
    • Microservices
    • NoSQL
      • ElasticSearch
    • Hystrix
    • Reactive Programming
    • AngularJS
    • Service Mesh
  • Networking
    • Dependency Injection
  • Programming
    • Java Core/EE
      • Java 8
      • Java Annotationen
      • Bean Validation
      • Enterprise Java Beans Specification
      • Dependency Injection
    • JRebel
    • Webservices
      • HTTP
      • REST
      • Spring MVC REST
      • Swagger
      • Postman
    • Spring Ecosystem
      • Spring Core
      • Spring Boot
        • Programming
        • Production Ready
        • Testing
      • Spring Cloud
      • Spring Cloud Config
      • Spring MVC
      • Spring Data
      • Spring Petclinic
    • NodeJS
    • UI-Technologie
      • Thymeleaf
      • ionic
      • Web Fonts
      • Jinja Templates
      • Twitter Bootstrap
    • Python Ecosystem
      • Python Libraries
      • Python Testing
      • Python Best-Practices
      • Python Snippets
      • Python Selenium
      • Kivy UI
      • FastAPI
      • Typer CLI
      • Django
    • Groovy
    • Persistenz
      • Transactions
        • Java TX
        • JPA TX
      • TX Handling
      • JPA
        • Eclipse Link
      • MySQL
        • MySQL Performance
        • Docker MySQL
      • Hazelcast
    • Glassfish
    • YAML
    • Angular
    • Camel
    • Zeichenkodierung
    • Kinder lernen Programmieren
  • Testen
    • Easymock
    • Mockito
  • Performance & Scalability
    • Java Performance
      • Heapdump Analysis
    • Java Concurrency
    • Instana
  • Sicherheit
    • Authentifizierung
      • OpenID Connect
      • Web-Authentication API
    • Authorisierung
      • OAuth
      • SAML
    • Spring Security
    • Zertifikate
    • Kali Linux
    • VPN
    • Zero-Trust-Networks
  • Build und Deployment
    • Maven
    • Bamboo
    • Jenkins
      • Jenkins Pipelines
      • Jenkins Pipelines Tips und Tricks
      • Jenkins-configuration-as-Code
      • Jenkins IDE
    • Travis CI
    • Shellprogrammierung
      • jq - JSON Parsing
    • Konfiguration Management
    • Vagrant
      • Vagrant-Ansible-Integration
      • Vagrant Box bauen
    • Ansible
      • Getting Started
      • Ansible Details
    • Saltstack
    • LinuxKit
    • Container
      • Docker
        • Docker Getting Started
        • Debugging Docker
        • Docker Build
        • Docker Registry
        • Docker run
          • docker run
          • docker network
        • Docker Compose
        • docker machine
        • Docker@Windows
        • Docker Host
        • Docker Scaling
        • Docker Ressources
        • Docker Logging
        • windowsContainer
      • Cloud Deployment Provider
        • AWS
          • Anwendungsdeployment
          • Workload
          • Permissions
          • Netzwerke
          • AWS CLI
            • aws-vault
          • RDS
          • Static Website Hosting
          • EKS - Elastic Kubernetes Service
          • S3
        • Google Cloud Platform
      • Docker Orchestrierung
        • CoreOS
        • Kubernetes
          • microK8s
          • minikube
          • autoscaler
          • Docker
          • k9s
        • Nomad
    • PHP
  • Operations
    • Proxy
      • NGINX
    • DNS
    • Logging
      • Graylog
      • Fluentd
    • Monitoring
      • Grafana
    • Infrastructure-as-Code
      • Terraform
        • AWS-Provider
        • GitHub-Provider
      • Packer
    • Deployment
      • Vault
      • Consul
        • Consul Template
      • Fabio
  • Rechtliches
    • Software-Lizenzen
  • Git Ecosystem
    • Git
      • Git Lifecycle Hooks
    • GitHub
      • GitHub Organizations
    • GitHub Actions
    • GitHub Pages
    • GitHub CLI
    • GitHub Copilot
    • GitHub-AWS OIDC
    • GitBook
    • GitLab
    • Bitbucket/Stash
  • Publishing
    • WordPress
    • Markdown
    • Static Site Generators
      • Hugo
      • Jekyll
    • Tiddly Wiki
    • Leanpub
    • Animationsfilme
  • Storage
    • Synology 2012
    • Synology 2021
  • Collaboration
    • Übersicht
    • Microsoft Teams
  • Konferenzen
    • Velocity Berlin 2019
  • IT mit Kindern
    • Projekt Sportstracker
    • Scratch
    • Pico Spielekonsole
  • Schule
    • Mathematik
  • Misc
    • Foto/Video
      • Foto/Video Sammlung bis 2023
        • Handbrake
        • Onedrive
      • Foto/Video Sammlung ab 2024
      • Gopro
      • Panasonic FZ1000 ii
        • als Webcam
      • AV Receiver
      • Videos erstellen
        • OBS Studio
        • Touch Portal
        • Game-Streaming
      • Kameratasche
      • Kamera 2020
    • Handy
      • 2016
      • 2018
      • 2019
      • 2021
      • 2022
    • Computer
      • Laptop
        • 2018
        • Chromebook
      • Monitor
        • 4k
      • Software
        • Command Line Interface
        • Google API
        • Plant UML
        • Chromium
        • Passwort-Manager
        • GPG
      • Dell CNF 2665 Farbdrucker
      • Dockingstation
      • Gaming PC 2021
      • Mobiles BĂźro
      • Mobiles Internet
      • Mobiler Router
    • Beamer Benq W1000+
    • Spielekonsole
      • 2017
        • Playstation 4
      • Pico Spielekonsole
    • Gadgets
      • iPad Pro 2015 und 2016
      • iPad Air 2024
      • Macbook Pro
      • Smartwatch
      • Slate
      • Mudi
    • Fahrrad
      • Jonas 2018
      • SQLab
    • Auto
      • Auto 2022
      • Camping
        • Camping Touren Ideen
          • Camping Tour - Gardasee 2021
        • Camper
          • Camper klein - keine StehhĂśhe
            • VW Bus Erfahrungen
          • Camper gross - StehhĂśhe
    • Haus
      • Klimaanlage
      • Swimming Pool
      • Quick Mill Orione 3000
      • SpĂźlmaschine 2021
      • Hebe-SchiebetĂźr
      • Gasgrill
      • Minibar / Mini-KĂźhlschrank
      • Glasfaseranschluss (Fiber-to-the-Home)
      • Smart-Home
        • Raspberry Pi
        • Heimnetzwerk
      • Homeoffice
      • Energie
        • Solar
        • Wärmepumpe
    • Freizeit
      • Musik Streaming
      • Sky
      • Online Lernplattformen
      • eScooter - ePowerFun
    • Fußball
      • Meine Arbeit als Fußball-Trainer
      • Fußball Tools
      • DFB TalentfĂśrderung
    • Google Impact Challenge
  • Englisch
Powered by GitBook
On this page
  • Versionen
  • Linux - switch to Python 3 default
  • Getting started
  • Python im Browser
  • Lokale Installation - Command Line
  • Ubuntu via APT
  • Ubuntu tarball
  • iOS Pythonista
  • Online Entwicklungsumgebungen
  • Nutzungsmodi
  • Python Shell Mode
  • In File-Mode
  • Browser-Mode
  • AWS Serverless
  • Visual Studio Code
  • Best Practices
  • Python Virtual Environments
  • Installation per Linux-Package
  • Installation per pip
  • Best-Practice
  • Background Info
  • Sprache
  • Besonderheiten
  • Variablen
  • EinrĂźckung
  • Standarddatentypen
  • Sequences
  • Sequence - String
  • Sequence - List
  • Sequence - Tupel
  • Sets
  • Dictionary
  • Type Hints
  • Alias Types
  • Exceptions
  • Funktionen
  • Built-In-Funktionen
  • Anonyme Funktionen - Lambda Functions
  • Bibliotheken / Pakete / Module
  • Bibliotheken - Main
  • Paketmanager Pip
  • Idiome
  • String contains
  • Loops
  • Files
  • Garbage Collector
  • Objektorientierung
  • Klassen
  • Instanzvariablen vs Klassenvariablen
  • Methoden
  • Dunderscore-Methoden
  • Vererbung
  • Bibliotheken
  • Python Virtual Environments
  • Python Docker Environment
  • FastAPI
  • Bibliotheken / Pakete
  • Python in Kombination mit Shell
  • Python executes Shell-Commands
  • Kinder lernen programmieren
  • Jupyter Notebook
  • Mybinder Deployment
  • Python Web-Development
  • Python Web Server Gateway Interface (WSGI)
  • Fazit
  • Mein Bedarf
  • Lern-Sprache

Was this helpful?

  1. Programming

Python Ecosystem

Python unterstßtzt verschiedene Paradigmen (prozedurale, funktionale, objektorientierte). Es handelt sich um eine interpretierte Sprache - somit entfällt das lästige Erstellen compilieren (man kann aber auch Just-In-Time-Compiler wie PyPy verwenden) ... einfach Editor auf und coden - was man eben von einer Skriptsprache erwartet. Auf diese Weise kann man es auch sehr praktisch mit Shellprogrammierung mixen oder die Shellskripte vielleicht sogar ablÜsen.


Versionen

Python gibt es in verschiedenen Versionen. Version 2 (hat am 1.1.2020 sein End-of-Life erreicht) und 3 sind zueinander inkompatibel. Dieser Code

stadt = input("In welcher Stadt wohnst Du?")

if "berg" in stadt:
  print("Du wohnst am Berg :-)")
else:
  print("Du wohnst NICHT am Berg :-)")

läuft in Python 3 aber nicht in 2.

Glücklicherweise lassen sich beide Versionen parallel betreiben, da die Binaries dann python, python2 oder python3 heißen (es gibt noch weitere Binaries, die dieses Konzept dann auch verwenden). Am besten funktioniert das mit virtuellen Environments (siehe unten), so daß man unabhängig von der Version immer mit python arbeiten kann.

Linux - switch to Python 3 default

Ich hatte ein realtiv altes Ubuntu-System, das noch Python 2 als default verwendete. Python 3 war schon installiert, doch ich wollte nicht immer python3 eingeben müssen, um den "richtigen" Interpreter zu starten. Leider besteht Python nicht nur aus dem Interpreter, sondern auch aus dem Paketmanager pip und noch weiteren Tools (pipenv, ...), die dann alle zueinander passen müssen. Das kann ganz schön nerven und unter Umständen sogar das System zerschießen, wenn andere Systemtools evtl. dauaruf aufbauen.

Es gibt verschiedene LĂśsungen

  • siehe hier

Aus meiner Sicht sollte man IMMER virtuelle Environements verwenden.


Getting started

Python im Browser

Um mal eben schnell was auszuprobieren (während des Lernen der Sprache) hat sich ReplIt etabliert. Deutlich besser als Python in Shell Mode.

Lokale Installation - Command Line

Ubuntu via APT

Install Python sudo apt-get install python3-pip and create a file hello.py

Ubuntu tarball

  • so gehts

Über einen tarball (z. B. Python-3.9.5.tgz) ist es häufig einfacher eine bestimmte Version oder die neuesten Versionen zu installieren. Evtl. hat die verwendete Linux-Distribution kein Paket für die relevante Version.

Über einen tarball compiliert man Python aus den Sourcen selber innerhalb weniger Sekunden. Man braucht allerdings auch ein paar Development Packages (siehe offizielle Dokumentation). Leider führen fehlende Developer Packages nicht sofort bei configure, make oder altinstall (siehe unten) zu einem Fehler. Stattdessen können die Fehler später (z. B. pip install -r requirements.txt) zu einem Abbruch führen. Das ist leider recht frustrierend :-(

DESHALB: der abschließende Test der Installation per python -m test ist absolut erforderlich, um spätere Probleme zu vermeiden, die man dann nicht so einfach einer unvollständigen/fehlerhaften Installation zuordnen kann. Nichtsdestotrotz kann man den Test natürich jederzeit durchführen.

Danach gehts per

wget https://www.python.org/ftp/python/3.8.10/Python-3.8.10.tgz
tar xvf Python-3.8.10.tar.xz
cd Python-3.8.10
./configure --enable-optimizations --with-ensurepip=install
make -j 8

sudo make altinstall

ACHTUNG: das Scripting scheint das Fail-Fast-Prinzip zu ignorieren ... so brach make bei mir nicht beim ersten Fehler ab. Glßcklicherweise war der Fehler auffällig in rot im Consolen-Output zu sehen ... sonst wäre mir das nie aufgefallen.

Der letzte Befehl ist entscheidend und wird unbedingt empfohlen. Hierdurch wird die neue Version parallel zu den bereits existierenden installiert und ersetzt diese nicht.

In obigem Beispiel wurde eine Version /usr/local/bin/python3.8 installiert, die sich Ăźbrigens nicht neben den per APT installierten Versionen /usr/bin/python (Python 2) und /usr/bin/python3 (Python 3).

Anschließend teste ich die Version über eine virtuelle Umgebung:

cd my-project
virtualenv --python=/usr/local/bin/python3.8 venv
source ./venv/bin/activate

python --version

python -m test

fĂźr newbe's: python -m test startet das Modul test ... das wiederum einen Test der Python-Installation durchfĂźhrt.

Wenn alle Tests (dauernn schon ein paar Minuten) erfolgreich waren, dann kanns losgehen :-)

iOS Pythonista

Auf meinem iPad habe ich die App Pythoista installiert, mit der sich kleinere Programme auch unterwegs implementieren lassen. Die vorhandenen Bibliotheken sind natßrlich eingeschränkt (es ist aber mehr als die Python-Standardinstallation), doch fßr Algorithmen reicht es allemal.

Online Entwicklungsumgebungen

  • Repl.it

  • Python anywhere

  • ...


Nutzungsmodi

Python Shell Mode

Dieser Modus wird per python gestartet ... es ist anschließend kein Editor notwendig, um Dateien zu editieren und zu speichern. Stattdessen wird der Code direkt in die Shell geschrieben und direkt ausgeführt. Das ist sehr praktischen, wenn man mal eben schnell eine Berechnung wie beispielsweise print(2**10) ausführen will. Streng genommen, kann man sich das print sparen und einfach 2**10 eingeben. Die Werte der Expressions werden in diesem Modus automatisch ausgegeben.

>>> 2**10
1024
>>> print(2**10)
1024
>>> "Pierre"
'Pierre'
>>> vorname="Pierre"
>>> nachname="Feldbusch"
>>> vorname + " " + nachname
'Pierre Feldbusch'

In File-Mode

In diesem Modus muß man dem Python-Interpreter eine Datei (= Modul) vorwerfen, die er dann interpretiert/ausführt:

echo "print(42)" > main.py
python main.py

wird dann die Zahl 42 ausgegeben :-)

Browser-Mode

Python läßt sich auch im Browser ausführen - insbes. für Einsteiger ist das eine angenehme Option:

  • Replit

    • Auto-Formatting

    • Syntax-Highlightning

    • Funktionsdoku

    • Code Intelligence

Evtl. lassen sich nicht alle Bibliotheken und Features (Multi-Threading) nutzen. FĂźr den Anfang bzw. kleinere Skripte kĂśnnte das aber ausreichen.

AWS Serverless

  • AWS Lambda

Python Code läßt sich als Lambda Function in AWS ausführen.


Visual Studio Code

  • Tutorial

  • prepare System - see above

Starte Visual Studio Code and installiere Visual-Studio-Code Python Extension von Microsoft. Create a file hello.py:

msg = "Hello World, Pierre"
print(msg)

Führe anschließend den Code per "Run Selection/Line in Python Terminal" aus :-)

In der unteren blauen Statusleiste ist die verwendete Python-Version zu erkennen. Hier kann man die Version auch umschalten ... beispielsweise auf ein Virtuelles Environment.

Nichtsdestotrotz hatte ich mit den Default-Einstellungen der Python-Extension einige Probleme:

  • Navigieren in importierte Module (auch meine eigenen, die sich direkt im gleichen Git-Repository befinden) funktioniert nicht. Hierzu finde ich diesen Beitrag:

    • Stackoverflow

  • der Wechsel des Python-Interpreters funktioniert nicht zuverlässig. Ich muß dann immer mal die Datei wechseln, um den neuen Interpreter auch tatsächlich in der Statusleiste angezeigt zu bekommen

  • nach Installation eines Moduls per pip install module (Ăźber meine Console oder auch das VSCode Terminal) steht das Modul scheinbar noch nicht zur VerfĂźgung. Ein import module sorgt fĂźr einen Fehler, der erst durch einen VSCode-Neustart verschwindet

Nach Umstellen vom Microsoft Language-Server auf Pylance hat alles schon viel besser funktioniert:

Enthält ein Projekt eine Virtuelle Umgebung z. B. in PROJEKT/venv-myproject so erkennt das VSCode und verwendet den darin befindlichen Interpreter und deren Libraries. In der unteren blauen Statusleiste ist das zu erkennen - hierßber kann man au

Best Practices

Ich verwende normalerweise einen VSCode Workspace, der 30 oder mehr Projekte enthält (das funktioniert, weil VSCode wirklich super schnell ist). Fßr die Nutzung in einem Python-Projekt ist das allerdings nicht die beste LÜsung, da relative Module (in lokalen Packages) nicht aufgelÜst werden kÜnnen. Das sorgt fßr angezeigte Fehler und reduziert VSCode von einer Python-IDE zu einem reinen Editor.

Deshalb Üffne ich mein Python Projekt in einer separaten VSCode-Instanz, das nur diese eine Projekt im Root enthält.


Python Virtual Environments

  • Getting Started

  • Tutorial

  • Install packages in a virtual environment using pip and venv

ACHTUNG: virtuelle Environments erfordern die Bereitstellung von Python-Versionen auf dem System ... das virtuelle Environment bringt diese nicht mit. In dem virtuellen Environment wird dann eine der auf dem System installierten Python Versionen verwendet.

Bei diesem Ansatz werden Bibliotheken und Interpreter in einem applikationsspezifischen Verzeichnis installiert, so daß verschiedene Ausführungsumgebungen voneinander getrennt werden können. Auf diese Weise werden Konflikte vermieden und man kann sich nicht auf Bibliotheken stützen, die im Zusammenhang mit einem anderen Projekt installiert wurden ... das verbessert die Qualität in den projektspezifischen Dependencies (requirements.txt Datei).

Installation per Linux-Package

  • Ubuntu-Installation

Das ist die präferierte Variante, da python -m venv nicht alle Features mitbringt. Das Paket zum Management dieser virtuellen Umgebungen wird per sudo apt install virtualenv installiert.

Per (z. B.) virtualenv my-venv wird ein virtuelles Environment im Ordner ./my-venv angelegt.

Installation per pip

venv ist auch ein Python Modul und kann per pip install venv installiert werden. Die Erzeugung einer virtuellen Umgebung erfolg dann per python -m venv my-venv

Best-Practice

Ich präferiere, das virtuelle Environment in einen Subfolder (z. B. ~/src/jenkins-stats/venv) des Git-Repo's zu packen, das den Source-Code enthält. Zumindest, wenn ich den Source-Code selbst unter Kontrolle habe, denn dann füge ich venv zur .gitignore des Repos hinzu. Aus meiner Sicht erhöht das die Übersichtlichkeit ... das virtuelle Environment liegt nicht mehr irgendwo, sondern direkt im Python-Projekt.

So sieht der ganze Prozess dann aus:

git clone https://github.com/HewlettPackard/Jenkins-stats.git jenkins-stats
cd jenkins-stats
mkdir venv
echo venv >> .gitignore
virtualenv venv
source venv/bin/activate

Anschließend befinden sich in diesem Verzeichnis einige Shell-Skripte. Um diese Umgebung zu aktivieren wird source ~/src/jenkins-stats/venv/bin/activate ausgeführt. In meiner zsh-Shell ändert sich dadurch der Command-Prompt ... der Name der aktuell aktiven virtuellen Umgebung ist erkennbar:

╭─pfh@workbench ~/src/com.github  
╰─➤  source ~/ideWorkspaces/venv/jenkins-stats/bin/activate
(jenkins-stats) ╭─pfh@workbench ~/src/com.github

Background Info

Die typischen Python Kommandos (python, pip, ...) sind in einem virtuellen Environment auf die Skripte in der virtuellen Umgebung (~/src/jenkins-stats/venv/bin/python) umgebogen. Die Installation von Libraries per pip install Jinja2 oder pip install -r requirements.txt fĂźhren zur Installation der Pakete im virtuellen Environment (~/src/jenkins-stats/venv/lib) ... nicht im System-Installationsverzeichnis fĂźr Python-Module.

virtualenv verwendet dabei die auf dem System verfĂźgbare Default-Python-Version (die per python verfĂźgbar ist)... kopiert dabei aber die Executables in die virtuelle Umgebung. Hat man noch eine andere Python-Version (z. B. python3) installiert (aber Python 2 war der Default bei python --version), so kann man diese per

virtualenv --python=/usr/bin/python3 venv

Ist dies die erste Python Version in dem virtuellen Environment, dann wird sie per python --version verfügbar sein ... es muß kein python3 --version verwendet werden.

ich habe lange überlegt, ob ich meinen virtuellen Environments einen kontextabhängigen Namen geben soll, damit ich in meiner ZSH-Shell immer weiß welche virtuelle Umgebung (wird dort angezeigt) aktiviert ist. Letztlich habe ich mich dagegen entschieden und verwende IMMER venv. Erstens scheint das der Standardname zu sein und zweitens kann ich so einfach venv in jedes meiner .gitignore packen. Vor der Python-Nutzung initialisiere ich dann immer die virtuelle Umgebung ... hierzu verwende ich einen alias venv=./venv/bin/activate. Für die Erzeugung habe ich einen anderen venv-create='virtualenv --python=/usr/bin/python3 venv. Das macht das Handling komfortabler ... ich könnte auch ein Shellscript als Decorator um python bauen, um eine automatische Aktivierung durchzuführen (aber natürlich müßte ich dann auf der Konsole immer im Python-Projekt stehen, um die passende Umgebung zu finden).

Das Umschalten auf eine neue virtuelle Umgebung ist in VSCode ein bisschen umständlich, wenn ich einen Workspace verwende, der ganz viele Projekte (auch mit unterschiedlichen virtuellen Umgebungen) enthält. Starte ich hingegen einen VSCode-Worspace mit nur einem Projekt/Verzeichnis, unter dem sich dann auch gleich der Ordner venv befindet, dann ist das super komfortabel, da meine venv-Umgebung sofort zur Auswahl steht.


Sprache

Man sollte sich an die typischen Idiome/Patterns halten und auch den Styleguide berĂźcksichtigen. Bestenfalls unterstĂźtzt die IDE hierbei und gibt Warnungen aus, wenn sie nicht eingehalten werden (Stickwort Linting).

Ganz wichtig:

  • eine Python-Datei (foo.py) repräsentiert ein Modul, das wiederum ... wild gemischt enthalten kann

    • ausfĂźhrbaren Code (print("pierre"))

    • Funktionsdefinitionen (def foo():)

    • Klassendefinitionen (class Foo:)

    • Meta-Daten (import random)

Besonderheiten

  • Variables don’t have types in Python; values do. That means that it is acceptable in Python to have a variable name refer to an integer and later have the same variable name refer to a string.

  • Python unterstĂźtzt viele Programmier-Paradigmen ... prozedural, objektorientiert, funktional. FĂźr mich als Java-Entwickler war die Mischung am Anfang etwas befremdlich, d. h. man schreibt sein Hauptprogramm main.py und darin sind dann prozedurale Element (Initialisierung), aber auch schon gleich erste Klassen. Eine Mixtur aus Klassen und Initialisierung globaler Variablen. Geht sicher auch anders oder ist das ein gängiges Idiom? Vielleicht habe ich nur keinen Çlean-Code gesehen ...

  • Python hat Built-In-Funktionen wie type, len, input, range ... diese kĂśnnen ohne import genutzt werden

    • vollständige Liste

  • Datentypen wie int, string, Listen, Tupel haben eingebaute Funktionen, die man nutzen kann ... (print("Pierre".count("r"))

    • "Pierre".split("e")

  • "_".join(["P", "i", "e", "r", "r". "e"])

Variablen

Global-Namespace vs. Local-Namespace:

def anyFunction(x):
  m = 8
  z = m + 6
  w = y + 1
  y = x * x + k
  return y

y = 5
k = 3
m = 1

In diesem Fall ist y im globalen Namespace definiert - innerhalb der Funktion anyFunction wird y allerdings als lokale Variable behandelt, weil sie eine Zuweisung (y = x * x + k) erhält. Im lokalen Kontext hat die Variable aber keinen Wert und es wird zu einem Fehler kommen.

Im Gegensatz dazu hat die globale Variable k in anyFuntion keine Zuweisung, sodaß innerhalb anyFuntion der globale Wert k = 3 verwendet wird.

m hingegen ist sowohl im globalen Namensraum als auch im lokalen Namensraum von anyFuntion. Der lokale Namensraum hat hÜhere Priorität.

Absurd ist in Python, daß man global m definieren könnte und dann würde innerhalb anyFuntion der Wert aus dem globalen Namensraum verwendet. Wer ist das denn für ein Quatsch???

EinrĂźckung

EinrĂźckungen sind bei Python wichtig. Bei Kontrollstrukturen ist das gleich offensichtlich. Bei folgendem Code nicht sofort ... dieser Code ist syntaktisch falsch wegen der EinrĂźckung:

    fruit = "Banane"
    print(fruit)

So muß es aussehen:

fruit = "Banane"
print(fruit)

Standarddatentypen

  • int

  • float

  • str

  • bool (True, False)

Man kann den Type eines Wertes per "Casting" verändern int5 = int("5").

Sequences

Sequences sind die Datentypen

  • String (immutable)

  • List (mutable) ... mit eckigen Klammern definiert

    • ändern names[1] = "Pierre"

    • einfĂźgen names[1:1] = ["Jonas", "Robin"]

    • anhängen names += ["Jonas", "Robin"]

    • lĂśschen

      • names[1:3] = []

      • del names[1:3]

    • Liste clonen by Slice-Operator: namesClone = names[:]

    • der Operator += hat ein spezielles Handling bei (mutable) List: "obj = obj + object_two is different than obj += object_two ... The first version makes a new object entirely and reassigns to obj. The second version changes the original object so that the contents of object_two are added to the end of the first."

      • Empfehlung: den += Operatior nicht bei Listen - oder besser - gar nicht verwenden

  • Tuple (immutable) ... mit runden Klammern definiert

    • die runden Klammern kĂśnnen weggelassen werden (weil dieser Sequenz-Typ der typische ist - Stichwort funktionale Programmierung) ... die beiden folgenden Tupel sind semantisch gleich

      tupelA = ( "Banane", "Apfel", "Pfirsich" )
      tupelB = "Banane", "Apfel", "Pfirsich"

Besonderheiten von Sequenzen

  • bei immutable Datenstrukturen nimmt Python eine Speicheroptimierung vor und speichert den gleichen Wert nur ein einziges mal (Aliasing) - bei mutable Datentypen ist das nicht der Fall!!!

  • list = ["hello", 2.0, 5, [10, 20]] ist in Sprache wie Java nicht erlaubt, weil die Werte unterschiedlichen Typs sind ... in Python ist das erlaubt wegen der Regel "Variablen haben keinen Typ - Werte haben einen Typ"

    • mit Listen funktionieren auch typische Operatoren wie + und *

      • blist = alist * 2

  • tupel = ("hello", 2.0, 5, [10,20]) ist ein Tupel ... sieht einer List list = ["hello", 2.0, 5, [10,20]] sehr ähnlich ist aber immutable

  • Sequenzen unterstĂźtzen Slicing ... List-Slices sind Listen, Tupel-Slices sind Tupel, String-Slices sind Strings:

    • name = Pierre; inBetween = name[1:len(name)-2]

  • Tupel: ('n', 'no', 'nop', 'nope')

    • Elemente mĂźssen nicht vom gleichen Datentyp sein: ((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))

    • im Gegensatz zu list ist ein Tuppel immutable und hat i. a. unterschiedliche Datentypen - Tupel werden in anderen Use-Cases verwendet

Sequence - String

Speicheroptimierung:

fruitA = "Banane"
fruitB = "Banane"
print("compare identity - expected True", fruitA is fruitB)
print("compare content - expected True:", fruitA == fruitB)
  • mit String kann man "rechnen"

    def multiply(s, mult_int):
        return s * mult_int
    print(multiply("Hello", 3))      # HelloHelloHello
  • immutable Functions:

    • nameUpper = "Pierre".upper()

    • nameLower = "Pierre".lower()

    • stripped = " Das ist ein Test ".strip()

      • strip entfernt auch ZeilenumbrĂźche

    • replaced = "Das ist ein Test".replace("a", "b")

    • print("Hallo {}, ich bin {} Jahre alt und ich habe {} Euro in der Brieftasche".format("Pierre", 13))

      • String-Formatierungsvarianten

      • hier kann man den Wert noch formatieren (z B. print("Hallo {}, ich bin {} Jahre alt und ich habe {:.2f} Euro in der Brieftasche".format("Pierre", 13, 100)))

      • noch schĂśner ist, wenn man den Platzhaltern Namen geben kann

        • das vereinfacht Refactorings am String, weil die Reihenfolge der Variablenwerte keine Rolle spielt:

          • print("Hallo {name}, ich bin {alter} Jahre alt und ich habe {betrag:.2f} Euro in der Brieftasche".format(name="Pierre", alter=13, betrag=100))

        • wiederholte Strings mĂźssen nicht als Werte wiederholt angegeben werden:

          • print("Hallo {name}, ich bin {alter} Jahre alt und ich habe {betrag:.2f} Euro in der Brieftasche. Bis bald, {name}".format(name="Pierre", alter=13, betrag=100))

      • mittlerweile hat sich der f-String durchgesetzt, der noch leichter lesbar ist. Es erlaubt Formatierungen und Rechenoperationen innerhalb des Strings

        • print(f"Hallo {name}, ich bin {alter} Jahre alt und ich habe {betrag:.2f} Euro in der Brieftasche")

        • print(f"a + b = {a+b}")

Interessante Built-In Functions:

  • l = list("Pierre") liefert l = ["P", "i", "e", "r", "r", "e"]

  • s = set("Pierre") liefert s = {"P", "i", "e", "r"} (oder eine andere Reihenfolge)

Sequence - List

mit mutating Object-Functions (funktioniert natĂźrlich nicht auf immutable Tuples) - es werden keine neuen Objekte erstellt, d. h. list = list + ["banana"] erstellt ein neues list Objekt ... was bei list.append("banana") nicht der Fall ist:

  • list.append("banana")

  • list.count("banana")

  • list.insert(1, "banana")

  • list.index("banana")

  • list.remove("banana")

  • list.reverse()

  • list.sort()

  • list.sort(key=None, reverse=False)

  • list.pop()

    • auf diese Weise lassen sich mit append und pop Stacks implementieren

  • list.popleft()

    • auf diese Weise lassen sich Queues mit list.append()/list.popleft() implementieren

      • nicht performant - besser queue aus dem collections-Paket verwenden

Alternativ zu den Object-Functions (z. B. list.sort()) kann man die Built-In-Funktion sorted(list) verwenden, die immutable (= funktional) ist und somit auch auf immutable Sequences (z. B. Tupels).

Sequence - Tupel

Speicheroptimierung:

fruitsA = [ "Banane", "Apfel", "Pfirsich" ]
fruitsB = [ "Banane", "Apfel", "Pfirsich" ]
print("expected False (compare identical object)", fruitsA is fruitsB)
print("expected True (compare content!!!)", fruitsA == fruitsB)
print("expected False", id(fruitsA) == id(fruitsB))

Mit Tupel-Assignment läßt sich das Tauschen von Variablenwerten sehr elegant beschreiben:

a = 3
b = 5
print(a, b)
a, b = b, a   # siehe Anmerkung
print(a, b)

In a, b = b, a zeigt sich die Eleganz von Python. Im Prinzip handelt es sich um diese Zuweisung: (a, b) = (b, a). Dadurch, daß die Klammern bei Tupeln optional sind, wirkt a, b = b, a wie ein neues Sprachkonstrukt.

Auf Tupeln kann man per sorted sortieren (Breaking Ties Eigenschaft):

list = [(3, 5), (1,4), (1, 3)]
print(sorted(list))    # [(1, 3), (1, 4), (3, 5)]

Diese Eigenschaft kann man gut verwenden, um komlexe Sortierkriterien zu definieren:

list = ["Anton", "Zorro", "Nathan", "12345", "Robin"]
print(sorted(list, key=lambda name: (len(name), name))    # ["12345", "Anton", "Robin", "Zorro", "Nathan"]

Sets

  • geschweifte Klammern (wie in der Mathematik)

  • set: myset = {"Pierre", "Silke", "Jonas"}

    • mit comprehensions: a = {x for x in 'abracadabra' if x not in 'abc'}

    • Operationen: myset - { "Pierre" } liefert {"Silke", "Jonas"}

  • Functions:

    • myset.add("Pierre")

    • myset.pop()

    • myset.discard("Pierre")

    • myset.remove("Pierre")

    • myset.clear()

Auch für Dictionaries werden geschweifte Klammern verwendet. Das führt dazu, daß leere Sets und leere Dictionaries die gleiche Definition x = {} verwenden würden. Python macht daraus ein Dictionary. Ein leeres set könnte man über x = set() definieren.

Dictionary

  • Dictionary (Key-Value-Maps) ... mit geschweiften Klammern:

    • tel = {'jack': 4098, 'sape': 4139}

    • tel['pierre']=3006 => {'jack': 4098, 'sape': 4139, 'pierre': 3006}

  • ungeordnet

  • der Zugriff ist aber - wie bei den Squenzen - Ăźber eckige Klammern

  • wichtige Object-Functions

    • dict.keys() - liefert ein Iterable (for k in tel.keys():)

    • dict.values() - liefert ein Iterable (for k in tel.values():

    • dict.items() - liefert ein Tupel

      • for k in tel.items(): print(k[0], k[1])

      • mit Tupel-Assignment kann das aber eleganter geschrieben werden

        • for (key, value) in tel.items(): print(key, value)

# Creation at initialization time
dictAlt = { "one":"eins", "two":"zwei", "three":"drei" }
print(dictAlt)

# Creation after creation
dict = {}
dict["one"] = "eins"
dict["two"] = "zwei"
dict["three"] = "drei"
dict["wasauchimmer"] = ["pierre", "feldbusch", 1972]
print(dict)

del dict["wasauchimmer"]

# dict.keys() erzeugt nur ein Iterable, aber keine list ... aber wird kĂśnnen es per cast transformieren
for key in dict.keys():
  print(key, ":", dict[key])

for value in dict.values():
  print(value)

# hier wird eine List erstellt und ist damit ein Iterator
for item in dict.items():
  print("key: ", item[0], "value", item[1])
# ... aber VIIIIEL eleganter <==== IDIOM
for k, v in dict.items():
  print("key: ", k, "value", v)

# dict implementiert einen Iterator ... wie list/tupel
for key in dict:
  print(key, ":", dict[key])

if "two" in dict:
  print("two ist drin")

# wenn man auf einen Key per Indexing zugreift, der nicht existiert, gibt es einen Fehler
# Wenn man also nicht genau weiß, ob der Key drin ist, dann sollte man es vorher
# prĂźfen (um den Runtime-Error zu vermeiden) oder die get-Methode verwenden
value = dict.get("Pierre")          # liefert None
if value is None:
  print("nicht gefunden")
else:
  print(value)
print(dict.get(value, "default"))   # liefert default
if value in dict:
  print(dict[two])

feldbusch = { "Pierre" : 48, "Pierre": 76 }
print(feldbusch["Pierre"])        # liefert 76

# dictionary sortieren nach values
dict = { "Pierre" : 48, "a": 76, "b": 14, "c": 100 }
for k in sorted(dict.keys(), key=lambda k: dict[k]):
  print(k, dict[k])
# ... oder noch kĂźrzer ... das sieht doch schon fast wie eine DSL aus :-)
for k in sorted(dict, key=lambda k: dict[k]):
  print(k, dict[k])

Type Hints

  • Guide

Python ist eine untypisierte Sprache ... Variablen haben keine Datentypen ... Werte haben Datentypen. Deshalb ist folgendes problemlos mĂśglich:

var = "Pierre"
print(var)
var = 42
print(var)

Die Variable ändert seinen Typ zur Laufzeit. Bei kleineren Skripten und wenigen Datentypen ist das ganz ok, doch hinsichtlich der Lesbarkeit des Code ist schwierig. Hier weiß man nicht, ob das Ergebnis ein String ist oder eine Liste oder gar ein Set:

def getServices(category):
  my_name = "Pierre"
  if category == "backend":
    return [ "a", "b", "c" ]
  else:
    return [ "d", "e", "f" ]

Erst in der Implementierung erkennt man den zurĂźckgegebenen Datentyp Liste. Bei komplexeren Methoden mit evtl. mehreren return Statements kann das problematisch werden - die Folge sind Fehler ... zudem will niemand unleserlichen Code refactorn (der Anfang vom Ende).

Aus diesem Grund hat Python aus Type Checking eingführt, so daß man dem Code mit

from typing import List

def get_services(category:str) -> List[str]:
  my_name: str = "Pierre"                   # sieht gewĂśhnungsbedĂźrftig aus
  if category == "backend":
    return [ "a", "b", "c" ]
  else:
    return [ "d", "e", "f" ]

services = get_services("backend")

mehr Semantik verleihen kann. Das verhindert erstens Fehler und ermöglicht zudem eine bessere Code-Navigation in der IDE mit Auto-Completion-Support (bei Eingabe von service. in der IDE werden nur die Funktionen/Methoden einer List zur Auswahl gestellt). So klappt das Tippen doch gleich viel schneller und mit weniger Fehlern. Wer schon mal versucht hat, eine unbekannte API (ich hatte das Vergnügen mit api4jenkins) ohne Type Hints zu verwenden, weiß wovon ich spreche. Das endet in frustrierendem Trial-and-Error ... wer Glück hat findet zumindest eine gute Dokumentation. Da greife ich doch lieber gleich zur REST-API und der requests-API ... da bin ich wahrscheinlich schneller - obwohl ich davon überzeugt bin, daß api4jenkins die bessere Abstraktionsebene bietet.

Type checking zur Compile-Zeit bzw. vor der AusfĂźhrung hat man damit aber noch nicht ... zumindest nicht mit python von der Console. Eine gute Python-IDE kann hier helfen.

Alias Types

Mehrfach geschachtelte Typen wie z. B. List[Tuple[str, str]] sind i. a. recht schwer zu lesen. Hier kann es helfen einen Alias zu definieren, der einen semantischen Namen verwendet, so daß man dann

Card = Tuple[str, str]
Deck = List[Card]

verwendet. Aus meiner Sicht deutlich besser zu lesen.

An dieser Stelle kann man allerdings auch eine Klasse Card definieren. Das hätte den Vorteil, daß man dann auch noch Methoden reinpacken kann. Aus meiner Sicht sind Alias ein guter Mittelweg zwischen nichtssagenden Datentypen und Klassen mit Daten und Methoden.

Exceptions

Exceptions dienen der Behandlung von Ausnahme-Situationen. Sie sollen verhindern, daß das Programm komplett abbricht und man stattdessen eine weitere Chance erhält (Stichwort Resilience).

Der Exception-Typ (im Beispiel ZeroDivisionError) ist optional ... läßt man ihn weg, wird jegliche Exception vom except-Block "gefangen".

vornamen = [ "pierre", "hans", "patrick" ]
try:
    x = 5/0
    vorname = vornamen[3]
    myvar = doesNotExist
except ZeroDivisionError as e:
    print("got an ZeroDivisionError")
    print(e)
except IndexError as e:
    print("got an IndexError")
    print(e)
except:
    print("got any other error")
    print(e)

Man sollte Exceptions aber nicht mißbrauchen, um den Programmfluß für typische Use-Cases zu steuern:

vornamen = [ "pierre", "hans", "patrick" ]
i = 0
while True:
  try:
      print(vornamen[i])
      i += 1
  except IndexError:
      break

Exceptions sind Klassen und haben eine Vererbungshierarchie. Ein except ArithmeticError: fängt alle von ArithmeticError abgeleiteten Exceptions, also ZeroDivisionError, FloatingPointError und OverflowError. Auf diese Weise kann man mit EINEM except-Statements gleich eine ganze Familie von Fehlern fangen.

Mit einem KeyboardInterrupt-Exception-Handler lassen sich beispielsweise Server-Prozesse sauber beenden:

def createServer():
  serversocket = socket(AF_INET, DOCK_STREAM)
  try:
      serversocket.bind(("localhost", 5000))
      serversocket.listen(5)
      while True:
        (clientsocket, address) = serversocket.accept()
        receivedData = clientsocket.recv(5000).decode()

        # interpret received data
        # ...

        # prepare response
        data = "HTTP/1.1 200OK\r\n"
        data += "Content-Type: text/html; charset=utf-8\r\n"
        data += "\r\n"
        data += "<html><body>hello world</body></html>\r\n\r\n"

        # send response
        clientsocket.sendall(data.encode())
        clientsocker.shutdown(SHUT_WR)
  except KeyboardInterrupt:
    print("Server shutdown initiated")
  except Exception as e:
    print("error")
    print(e)
  
  serversocket.close()

createServer()

Exceptions kann man Ăźber

raise Myexception()

auslĂśsen.

Funktionen

  • in Python verwendet man fĂźr Funktionsnamen (und Variablennamen) Snake-Case (get_value()) anstatt Camel-Case (getValue())

  • Funktionen liefern IMMER genau EINEN Returnwert ... wenn nicht explizit mit return bla, dann ist der Returnwert immer None und kann dann beispielsweise per if myFunc() == None: abgefragt werden

    • ein Idiom in Python ist die Verwendung von Tupels als RĂźckgabewert - damit lassen sich sehr schĂśn mehrere RĂźckgabewerte definieren und die Formulierung sieht dabei sehr elegant aus (ganz ähnlich wie in Golang):

      def getSchwerpunkt(whatever):
        # Berechnung
        return x, y
      xAxis, yAxis= getSchwerpunkt(quadrat)
  • Parameter kĂśnnen auch Ăźber Tupel Ăźbergeben werden ... allerdings mit einer sehr speziellen Star-Notation:

    def setMittelpunkt(x, y):
        print("(x, y) = (", x, ",", y, ")")
    
    x = 3
    y = 5
    
    setMittelpunkt(x, y)
    
    mittelpunkt = x, y
    setMittelpunkt(mittelpunkt[0], mittelpunkt[1])
    
    setMittelpunkt(*mittelpunkt)
  • Parameter kĂśnnen Default-Werte haben und sind dann optional ... aber ACHTUNG bei mutable Parameters (z. B. Lists), denn der Default-Wert bleibt erhalten!!!

    • mandatory Parameter mĂźssen zuerst aufgefĂźhrt werden

      def doit(value, list=[]):
        list.append(value)
        return list
      print(doit(1))              # [1]
      print(doit(2))              # [1, 2]
      print(doit(3))              # [1, 2, 3]
      print(doit(4), ["Pierre"])  # [Pierre, 4]
    • der Default-Wert wird zum Zeitpunkt der Funktionsdefinition festgelegt - nicht zum Zeitpunkt der AsufĂźhrung:

      initial = 3
      def doit(value=initial):
        return value
      initial = 7
      print(doit())              # 3

      man kann hier eine Menge Schindluder betreiben ... das sollte man vermeiden - CleanCode!!! man schreibt den Code fßr den Leser (Code wird 10x häufiger gelesen als geschrieben) ... irgendwelche Spitzfindigkeiten sollte man vermeiden

  • Keyword-Parameter machen den Code sehr lesbar und sind in Kombination mit optionalen Parametern häufig absolut notwendig

    def doit(x, y=2, z=3):
      return x * y + z
    print(doit(1))
    print(doit(1, z=5))         # ausgelassener Parameter "y" 
    print(doit(1, z=5, y=7))    # Reihenfolge y, z geändert
    print(doit(z=5, y=7, x=1))  # Reihenfolge x, y, z geändert
  • Dokumentation einer Funktion sollte man mit einen sog. "docstring" machen, denn es gibt Tools, die daraus eine Dokumentation erzeugen

    def hello():
      """Gibt "Hallo" aus"""
      print("Hallo")
    hello()
    • Ăźbrigens: die Dokumentation ist zur Laufzeit per print(hello.__doc__) lesbar

      • beachte: Funktionen sind ganz normale Objekte print(type(hello)) liefert <class 'function'>

  • Funktionsparameter kĂśnnen Defaultwerte haben und sind dann optional: def ask_ok(prompt, retries=4)

  • mit *args gibt es eine spezielle Variante von Übergabeparameter: Argumentliste

def store(*args):
    ...

store("Pierre", "Silke")
  • mit *kvargs gibt es eine spezielle Variante von Übergabeparameter: Dictionary

def store(**kvargs):
    ...

store(name="Pierre", age=27)

Als eingefleischter Nutzer typisierter Sprachen finde ich es relativ schwierig eine Funktion aus dem Kontext heraus zu verstehen, weil ich bei der Funktion

def best_key(x):
  # irgendein komplexer code

gar nicht weiß, welchen Datentyp x repräsentiert. Der Code innerhalb der Funktion funktioniert aber nur basierend auf einem nicht sichtbaren Kontrakt. Ich muß also den Code der Funktion erstmal halbwegs verstehen, um dann daraus den erwarteten Input-Typ abzulesen.

Das finde ich sehr gewöhnungsbedürftig ... aber konsistent, wenn man berücksichtigt, daß Variablen keinen Typ haben, nur die Werte. Was ich aus dem Code häufig noch schwieriger rauslesen kann ist, ob es sich um eine Liste (mutable) oder ein Tupel (immutable) handelt. Das ist auch für den Entwickler schwierig, der die Methode best_key refactoren will und eigentlich nicht weiß, ob dort immer Listen oder manchmal auch Tupel reinwandern ... das bestimmt nämlich der Aufrufer???

In Python 2 hat man das in die Dokumentation geschrieben. In Python 3 verwendet man hierfĂźr Annotationen ... das wird allerdings von Python nicht ausgewertet, sondern komplett ignoriert:

def floatToInt(x: float) -> int:
 return int(x)

Durch die fehlende Typisierung kann eine Funktion sogar ganz unterschiedliche Datentypen zuĂźrckliefern:

def hello(name):
  if "Pierre" == name:
    return "Hallo Pierre"
  else:
    return 42
name = input("wie ist dein name")
print(type(hello(name)))

Sicherlich kein Best-Practice, aber prinzipiell möglich. Typisierter Sprachen würden das verhindern ... um so wichtiger die losen Best-Practices zu kennen und einzuhalten. ABER: es zeigt sich, daß man damit extremen Spaghetticode schreiben kann :-(

Built-In-Funktionen

... kĂśnnen ohne import genutzt werden

  • print

  • type

  • len(list)

  • max(a, b)

  • input

    • age=int(input("How old are you? "))

  • range

    • l=range(3,6) # l=(3,4,5)

  • sorted

    • listB = sorted(listA)

    • listB = sorted(listA, reverse = True) ... reverse ist ein optionaler Parameter

    • listB = sorted(listA, key = absolute) mit folgender absolute-Funktion (ACHTUNG: absolute ist ein Functionpointer!!!):

      def absolute(x):
        if x >= 0:
          return x
        else:
          return -x
    • listB = sorted(listA, key = lambda x: absolute(x)) mit einer Lambda-Function

  • enumerate erhält eine Sequence als Parameter und liefert ein Iterable von (index, value) - IDIOM

    • statt

      fruits = ['apple', 'pear', 'apricot', 'cherry', 'peach']
      for n in range(len(fruits)):
          print(n, fruits[n])
    • verwendet man

      fruits = ['apple', 'pear', 'apricot', 'cherry', 'peach']
      for index, fruit in enumerate(fruits):
          print(index, fruit)

Anonyme Funktionen - Lambda Functions

In meiner Zeit als C-Entwickler nannte man das Functionpointer. Viele Jahre später wurde daraus der Begriff Lambda-Function. Auf diese Art und Weise läßt sich ein Algorithmus als Parameter übergeben, um so das Strategy-Pattern zu implementieren und den Code sehr schön lesbar zu halten. Die Funktionsbeschreibung wird schlanker und erinnert an eine mathematische Funktionsdefinition im Stil von f(x) = x * x häufig auch per x -> x * x ausgedrückt.

Eine Funktion

def func(args):
  return value

wird ganz schematisch (und deshalb können IDEs auch eine automatische Transformation anbieten) folgendermaßen in eine Lambda-Funktion transformiert:

lambda args: value

Ein Beispiel:

def square(n):
  return n * n
print(square(2))

wird zu

sq = lambda n: n*n
print(sq(2))

In diesem Beispiel habe ich eigentlich nicht viel gewonnen, weil ich ja doch eine benannte (Lambda-) Funktion sq erstellt habe. Doch in folgendem Beispiel wird die Mächtigkeit deutlich ... ein Sortierkriterium wird ßber eine Lambda-Funktion definiert:

pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
pairs.sort(key=lambda pair: pair[1])

Bibliotheken / Pakete / Module

  • sehr schĂśn erklärt

  • Dokumentation

  • siehe separates Kapitel fĂźr mehr Details

"It’s important to keep in mind that all packages are modules, but not all modules are packages. Or put another way, packages are just a special kind of module." (siehe Dokumentation)

Die Stärke einer Sprache liegt häufig in den Bibliotheken, die man verwenden kann. In Python nennt man dies Bibliotheken Packages (sie enthalten ein oder mehrere Module) - sie mßssen lokal vorhanden sein. Entweder sind sie bereits in der verwendeten Python-Distribution oder mßssen mit dem Python Package Manager (pip) installiert werden. Der Python-Interpreter sucht in folgender Reihenfolge nach Modulen (kann man auch zur Laufzeit ßber sys.path rausbekommen):

  • aktuelles Verzeichnis

  • PATHONPATH Umgebungsvariable

  • Python Installation (hierĂźber werden die Python-Basis-Bibliotheken eingebunden)

Ein Paket (auf dem Filesystem ein Verzeichnis) ist eine Sammlung von Modulen (auf dem Filesystem eine einzelne foo.py Datei). Jedes Modul kann ausführbaren Code enthalten, der beim Import eines Moduls ausgeführt wird. Die Definition einer Funktion/Klasse (def) wird auch "ausgeführt", resultiert aber nicht Ausführung des Bodies, sondern nur in die Aufnahme der Funktionen in die Symboltabelle, so daß sie gefunden werden können.

FĂźr den Import gibt es zwei Varianten:

  • import random

    • hiermit wird das Modul random gesucht (random.py), initialisiert (AusfĂźhrung random/__init__.py sofern vorhanden) und random wird in die lokale Symboltabelle aufgenommen (damit ist das Paket erst nutzbar), doch die Funktionen/Klassen des Moduls mĂźssen mit vorangestelltem Paketnamen (diceValue = random.randrange(1, 7)) referenziert werden.

  • import jinja2.environment

    • hier wird das Package jinja2 verwendet

    • durch den import eines Moduls aus diesem Package wird das Package (beim ersten mal) Ăźber jinja2/__init__py initialisiert - anschließend wird das Modul jinja2/environment eingelesen

    • nun stehen Funktionionen/Klassen Ăźber jinja2.environment.Template() zur Nutzung zur VerfĂźgung

  • from random import randrange

    • hiermit wird das Modul random.py gesucht und - wenn noch nicht geschehen - initialisiert. Zudem wird die Funktion randrange aus dem Modul random in die lokale Symboltabelle des aktuellen Moduls aufgenommen. Beim Aufruf kann man auf den Modulnamen verzichten und die Funktion verwenden als wäre sie im aktuellen Python-File implementiert. Das sieht auf den ersten Blick einfacher aus (diceValue = randrange(1, 7) liest sich angenehmer), doch es geht die Information verloren aus welchem Paket die Komponente stammt.

  • from jinja2.environment import Template

    • hier wird das Package jinja2 verwendet

    • durch den import eines Moduls aus diesem Package wird das Package (beim ersten mal) Ăźber jinja2/__init__py initialisiert - anschließend wird das Modul jinja2/environment eingelesen und die Klasse Template wird in der lokalen Symboltabelle hinterlegt => ist also per Template() (ohne Paket/Modul-Prefix) nutzbar

Packages wie jinja2 vereinfachen den Import ... es reicht hier aus from jinja2 import Template anzugeben. Hier fehlt die Information Ăźber das Modul environment.

Die Erklärung warum das funktioniert findet sich im jinja2/__init__.py Modul (zu finden unter venv/lib/python3.6/site-packages/jinja2/__init__.py), das u. a. folgendes macht:

from .environment import Template as Template

Die Klasse wird also bei der Initialisierung des jinja2-Packages als Template zur VerfĂźgung gestellt. Und genau diesen "Alias" verwendet man dann bei from jinja2 import Template.

Es ist tatsächlich Best-Practice das Public-Interface eines Packages (das der Anbieter unbedingt stabil halten wird (Backward-Compatible)) auf diesem Weg zu definieren!!!

Es wird nicht verhindert auch Funktionen/Klassen aus den Modulen zu nutzen, die nicht public sind (z. B. from jinja2.environment import get_spontaneous_environment), doch kĂśnnen die jederzeit refactored werden (u. a. gelĂśscht werden). Auf diesen Funktionen/Klassen sollte man den eigenen Code nicht aufbauen!!!

Funktionen, die in einem anderen Module (= Python-File) innerhalb eines Verzeichnisses liegen, mĂźssen genauso importiert werden wie Funktionen aus Libraries, die man per pip installiert hat.

Unter VSCode hatte ich das Problem, daß meine importierten Custom-Python-Files nicht gefunden werden konnten. Es fehlte die Information über den Root, von dem aus die in Unterordnern befindlichen foo.py Module gefunden werden konnten. Wenn mein VSCode Workspace allerdings den Root-Folder enthielt (code ./my-project), dann war der Root vorhanden und der Import der Klasse Settings per from package.helper import Settings wurde korrekt über das Verzeichnis ./my-project/package/helper.py aufgelöst.

Wenn man ein Paket importiert, so wird immer die sich darin befindliche Datei __init__py ausgefĂźhrt (ich spreche hier von "Regular Packages" ... nicht von "Namespace Packages")

Man muß Pakete aber nicht statisch importieren. Das kann auch dynamisch per __import()__-BuiltIn-Funktion geschehen. Python kann damit sehr generischen Code bereitstellen. Das Paket importlib bietet noch viel mehr Optionen.

Bibliotheken - Main

Importiert man ein Modul, so wird dieses Modul (greetings.py) ausgefßhrt, d. h. der gesamte ausfßhrbare Code kommt tatsächlich zur Ausfßhrung:

def greet(name="World"):
  print(f"Hello {name}")

In diesem Fall besteht die "AusfĂźhrung" in der Definition der Funktion greet ... dadurch erfolgt keine Ausgabe.

Als Entwickler mĂśchte man seinen Code allerdings auch selbst nutzen, beispielsweise um ihn zu testen, d. h. in greetings.py wĂźrde man gerne auch sowas machen:

def greet(name="World"):
  print(f"Hello {name}")

greet()
greet(name="Pierre")
greet("Python")

so dass ein python greetings.py zur Ausgabe

Hello World
Hello Pierre
Hello Python

fĂźhrt.

Klar, man kann den Testcode in ein anderes Modul verlagern ... hält den Code aber nicht so schlank wie ein einziges File, d. h. ein Modul, das seine Tests enthält (finde ich ganz charmant).

Das hat aber den Nachteil, daß diese Ausgabe auch erscheinen würde, wenn man ein import greetings ausführt ... und das will man i. d. R. nicht.

In Python lĂśst man das Problem i. a. mit

def greet(name="World"):
  print(f"Hello {name}")

if __name__ == "__main__"
  greet()
  greet(name="Pierre")
  greet("Python")

dann erfolgt die Ausgabe nur, wenn die AusfĂźhrung als Main-Modul gestartet wurde. Bei einem import greetings ist der __name__ zur Laufzeit greetings => die Ausgabe erfolgt nicht.

Paketmanager Pip

siehe eigenes Kapitel fĂźr mehr Details

  • pip install jinja2

  • pip install -r requirements.txt

  • pip list

    • welche Packages sind installiert

  • pip check

    • Dependency check der installierten Packages

Idiome

  • Werte ignorieren mit _ als Variablenname

    track_medal_counts = {
      'long jump': 3,
      '100 meters': 2,
      '400 meters': 1
    }
    track_events = []
    for event, _ in track_medal_counts.items():
        track_events.append(event)
  • List Comprehensions: squares = [x**2 for x in range(10)]

String contains

Da ein String eine Sequence/List ist macht man das nicht per Functions-Call, sondern

stadt = input("In welcher Stadt wohnst Du?")
if "berg" in stadt:
  print("Du wohnst am Berg :-)")
else:
  print("Du wohnst NICHT am Berg :-)")

Loops

for i in range(5):
    print(i)

ACHTUNG: in erwartet ein Iterable ... eine Sequenz ist ein Iterable. range(5) erzeugt eine List (einfach mal ausprobieren: print(type(range(5)))).

Files

# open in readonly mode
fileref = open("../data/mydata.csv", "r")   # relativ zum AusfĂźhrungskontext (absolute Pfade funktionieren auch)
                                            # Absolute Pfade sind natĂźrlich nicht portierbar

content = fileref.read()                    # fileref.read(n) liest n characters
print(contents[:100])
print(len(content))

lines = fileref.readlines()                 # fileref.readlines(2) liest x Zeilen ... oder eine: fileref.readline()
                                            #
                                            # ACHTUNG: diese Variante verwendet man aus SpeichergrĂźnden besser nicht,
                                            #          denn hierbei wird die gesamte Datei in den Speicher geladen.
                                            #          Stattdessen verwendet man "fileref" als Iterator und liest
                                            #          zeilenweise.
print(len(lines))

print(lines[:4])
for line in lines:
  print(line)
for line in fileref:                        # das ist die typische Nutzung (IDIOM)
  print(line.strip())

fileref.close()

open() ist eine Built-In-Function

Achtung: es handelt sich bei dem File-Handle (z. B. fileref) um einen Iterator, d. h. nach einem content = fileref.readlines() sorgt ein erneutes content2 = fileref.readlines() fĂźr eine leere Liste.

Da man beim Filehandling gerne mal das Schließen des Handles vergißt, besitzt Python folgendes Idiom:

with open("../data/mydata.csv", "r") as fileref:
  for line in fileref:
    print(line.strip())

Das ist Kontextmanager um das Filehandling, der sich auch gleich um das Schließen kümmert :-)

Garbage Collector

In Python muss man keinen Speicher freigeben ... das geschieht automatisch, indem Python die Anzahl der Referenzen auf eine Speicherstelle zählt und den Speicher bei erreichen von 0 freigibt.

Dieser Ansatz basiert allerdings auf einem Global-Interpreter-Lock (GIL), der Multithreading schwierig macht.

"The GIL has long been seen as an obstacle to better multithreaded performance in CPython (and thus Python generally). Many efforts have been made to remove it over the years, but at the cost of hurting single-threaded performance—in other words, by making the vast majority of existing Python applications slower." (infoworld.com)


Objektorientierung

Klassen

class Point():
  pass

point1 = Point()
point2 = Point()

point1.x = 5

Instanzvariablen vs Klassenvariablen

Klassenvariablen sind instanzĂźbergreifend und existieren nur ein einziges mal (entspricht static Properties in Java). Deshalb werden sie auch innerhalb der Klasse definiert.

Das paßt konzeptionell zu dem Ansatz die Instanz an alle Methoden explizit zu übergeben. Für mich als Java-Entwickler zunächst mal eine Umstellung.

Instanzvariablen haben hÜhere Priorität ... bei print(point1.x) wird also zunächst mal nach einer Instanzvariablen x geschaut und wenn es keine gibt, dann wird nach einer Klassenvariablen geschaut.

Nichtsdestotrotz greift man auf eine Klassenvariable auch Ăźber die Instanz zu (self.classVariable). Man kĂśnnte auf die Klassenvariable aber auch per Point.classVariable = 3 zugreifen.

class Point():
  classVariable = 0
  def methodA(self):
    if self.classVariable == 0:
      return "null"

point1 = Point()
point1.instanceVariable = 5

Eine Sache, die mir hier nicht gefällt ist, daß ich als Leser der Klasse Point nicht weiß, daß es ein Property instanceVariable hat ... das taucht irgendwann im Code auf - vielleicht sogar in einer anderen Datei. Zudem werden die Instanzvariablen erst zur Laufzeit angelegt ... nur, daß sie im Code erscheinen bedeutet nicht, daß sie auch existieren:

class Point():
  def initialization(self):
    self.x = 0
    self.y = 0
  def __str__(self):
    return "({}, {})".format(x, y)

print(Point())

fĂźhrt zu einem Laufzeitfehler NameError: name 'x' is not defined, da die Instanzvariable x (und auch y) zum Zeitpunkt der AusfĂźhrung der __str__-Methode noch gar nicht existiert. Erst durch expliziten Aufruf der Methode initialization() erfolgt die Anlage und danach funktioniert auch __str__ fehlerfrei. Typischerweisse sollte man diese Initialisierung im Konstruktor __init__(self) machen.

Um Spaghetti-Code zu vermeiden, sollte man im Konstruktore ALLE Instanzvariablen anlegen!!!

Jetzt wird es sehr technisch - aber es lohnt sich, darĂźber nachzudenken:

  • Methoden werden in Python als Klassenvariablen behandelt

Das bedeutet, daß ein Aufuf von point1.methodA() folgendermaßen abgearbeitet wird:

  • gibt es in der Instanz point1 eine Instanzvariable methodA => NEIN

  • gibt es in der Klasse Point eine Klassenvariable methodA => JA

  • da nach methodA eine () folgt wird die Funktion methodA() mit point1 als Parameter aufgerufen

Das Wissen hierßber ist fßr die tägliche Arbeit nicht entscheidend, gibt aber Einblicke wie das objektorierte Konzept in Python implementiert ist.

Methoden

Die Funktionen in Klassen werden Methoden genannt. Alle Methoden haben als ersten Parameter IMMER das Objekt self, das die Instanz repräsentiert. Python verwendet also einen Delegationsansatz, um Klassen mit Instanzen zu verknßpfen.

Da ich aus einer anderen Welt (Java, C++, Eiffel) komme, mutet das zunächst mal komisch an.

Dunderscore-Methoden

  • guter Überblick

Die Lifecycle-Methoden (Konstruktor, String-Repräsentation, ...) sind in speziellen __init__(self) (den Parameternamen self zu verwenden ist ein Idiom - man kann jeden anderen verwenden ... macht aber niemand) Methoden verpackt ... kÜnnen ßberschrieben werden, mßssen aber nicht.

  • __init__(self, x, y)

    • Konstruktor

  • __str__(self)

  • __add__(self, other)

    • point1 + point2

  • __sub__(self, other)

Vererbung

Die __init__(self) Methode kann, muß aber nicht in der erbenden Klasse überschrieben werden. Wenn keine definiert ist, wird die aus der Superklasse automatisch verwendet.

Beim erweitern des geerbten Konstruktors muß dieser explizit per super() aufgerufen werden:

class SubClass(SuperClass):
  def __init__(self, name, size):
    super().__init__(name)
    self.size = size

Bibliotheken

Module, die Python nicht mitliefert, mĂźssen per pip install MODULE_NAME installiert werden. FĂźr die Automatisierung dieses Prozesses ist es hilfreich, alle notwendigen Module in einer Datei (z. B. requirements.txt) zu sammeln (die i. a. unter Versionskontrolle steht) und dann per pip3 install -r requirements.txt auf einen Schlag zu installieren. Das vereinfacht die Nutzung einer (unbekannten) Anwendung ungemein.

Dies hat allerdings den Nachteil, daß dieses Paket in der zentralen gemeinsam genutzten Python-Installation installiert wird. Dabei werden verschiedene Versionen einer Bibliothek nicht unterstützt. Das führt dazu, daß evtl. andere Python Projekte nicht mehr ausführbar sind.

HierfĂźr gibt es verschiedene LĂśsungen

  • Python Virtual Environments

  • Python Docker Environment

TLDR ... Während ich als Entwickler die Verwendung von virtuellen Environments auf meiner lokalen Entwickler-Maschine präferiere, halte ich das Deployment fßr Enduser in Form eines Docker Images fßr die bessere Variante. Beide Ansätze haben also ihre Berechtigung.

Python Virtual Environments

  • Getting Started

Diese Variante wird man bei der lokalen Entwicklung verwenden, weil der Ansatz sehr schlank ist und fĂźr den Entwickler transparent ist.

Die Nutzung ist abhängig von der Python-Version ... ich beschreibe hier Python 3.8 ...

Manche Python 3 kommen bereits mit dem Modul venv vorinstalliert - bei anderen muß man es erst installieren. Zur Installation gibt es folgende Varianten:

  • als Linux Binary apt install virtualenv ... eine virtuelle Umgebung wird per virtualenv --python=/usr/bin/python3 venv unter dem Verzeichnis venv angelegt

  • als Python-Package (in der Python-System-Installation) per pip install virtualenv. Anschließend kann per python -m virtualenv venv eine virtuelle Umgebung unter dem Verzeichnis venv angelegt werden.

Das wichtigste Verzeichnis einer virtuellen Umgebung ist bin (unter Windows heißt das Verzeichnis Scripts), das u. a. folgende Skripte enthält:

  • activate

  • deactvate

  • python

  • pip

Anschließend muß dieses Environment per source venv/bin/activate aktiviert werden.

ACHTUNG: unter Cygwin hat das nicht besonders gut funktioniert. Erstens mußte ich die Linebreaks von activate von Windows (CRLF) auf Linux (LF) umstellen. Zweitens wurden teilweise Batch Dateien statt Shell-Skripte erstellt ... es fehlte deactivate (stattdessen existierte deactivate.bat).

Nun ist nicht mehr die zentrale Python Installation des Systems konfiguriert, sondern die des virtuellen Environments. Das ist an folgendermaßen erkennbar:

  • Prompt enthält den Namen des virtuellen Environments (z. B. (myenv) ubuntu2004beta%)

  • which python liefert venv/Scripts/python statt einer zentralen Python Installation (z. B. /usr/bin/python3) - erreicht durch eine Anpassung der PATH Umgebungsvariable

    • allerdings ist das nur ein symbolischer Link auf die zentrale Python-Installation

    • erreicht wird damit aber eine environment-spezifische Bibliothek-Konfiguration, da

      "When Python is starting up, it looks at the path of its binary. In a virtual environment, it is actually just a copy of, or symlink to, your system’s Python binary. It then sets the location of sys.prefix and sys.exec_prefix based on this location, omitting the bin portion of the path. The path located in sys.prefix is then used for locating the site-packages directory by searching the relative path lib/pythonX.X/site-packages/, where X.X is the version of Python you’re using." (realpython)

Aus der Location des Executables wird auch die Location fĂźr Bibliotheken (site-packages) abgeleitet

Es sollten nicht gleichzeitig mehrere virtuelle Environments aktiviert sein (Seiteneffekte, seltsame Fehlermeldungen). Deshalb startet man besser eine neue Konsole!!!

Python Docker Environment

Python Services/Code in einen Docker Container zu packen vereinfacht die AusfĂźhrung von Python-Code, weil die Laufzeitumgebung (Python 2 vs 3, Bibliotheken) gleich mitgeliefert wird. Der AusfĂźhrende braucht nur eine Docker-Installation und kann den Code starten.

Bei diesem Ansatz wĂźrde man

  • Python im Dockerfile installieren

  • eine requirements.txt definieren und im Dockerfile installieren

FastAPI

  • siehe separate Seite

Bibliotheken / Pakete

siehe eigenes Kapitel


Python in Kombination mit Shell

Python executes Shell-Commands

Mit Fabric steht eine sehr brauchbare Python Library zur VerfĂźgung, um aus Python-Code (remote) Shell-Kommandos abzusetzen und die Ergebnisse in Python weiterzuverarbeiten.

Die Installation ist mit pip schnell erledigt

pip install fabric2

Danach kann man Programme wie hello-fabric.py

from fabric2 import Connection
c = Connection('localhost')
result = c.run('uname -s')
print("result: " + result.stdout.strip())

per python hello-fabric.py ausfĂźhren.

In der Dokumentation findet man erste Programmier-Beispiele.

Kinder lernen programmieren

siehe hier


Jupyter Notebook

  • EinfĂźhrungsvideo

  • Jupyter Notebook Galeries - wie andere das nutzen - Visio

    • so kĂśnnte man das beispielsweise fĂźr eine Schulpräsentation nutzen

Interessante Variante, um interaktive Dokumentationen mit Markdown, HTML-iFrames, Python Code, JavaScript, Bash, Ruby, Shell Kommandos zu erstellen.

Im Hintergrund wird ein Webserver gestartet - die Dokumentation ist eigentlich Code, der in einer JSON-Datei gespeichert werden. Diese kann man mit anderen teilen und unter Versionskontrolle stellen. Visual-Studio-Code unterstĂźtzt das auch ... da ist die Integration noch weniger sichtbar.

Das Dateiformat ipynb wir beispielsweise auch direkt von Github unterstützt, so daß die Datei unter Versionskontrolle gestellt, geteilt und komfortabel im Browser angezeigt werden kann.

Mybinder Deployment

Über mybinder können Jupyter-Notebooks aus Github-Repositories deployed werden, um sie interaktiv zu nutzen.

Ein Beispiel:

Im Github-Repository python-course befindet sich das Jupyter-Notebook


Python Web-Development

Python Web Server Gateway Interface (WSGI)

  • Spezifikation - PEP 3333

  • konkretere Details

Dieses Interface ermöglicht die standardisierte Integration von Python-Backends in Webserver wie Apache-Http, NGINX, ... und trägt damit bei, daß sich das Python-Ecosystem (z. B. weitere Python-Web-Application-Frameworks) weiterentwickeln kann.

Die Spezifikation allein ist aber nur die Grundlage, auf der sich dann Implementierungen entwickelt haben:

  • gunicorn

  • uWSGI

  • mod_wsgi - Apache Modul

  • cherrypy - Python Webserver acting as WSGI


Fazit

TL;DR ... ich mag Python und halte es auch fĂźr eine sehr gute Sprache zum Lernen von Softwareentwicklung.

Mein Bedarf

FĂźr meinen Bedarf ist es eine tolle Sprache, weil

  • Scripting mĂśglich ... ich muß nicht erst einen Compiler anwerfen, um einen Einzeiler zu programmieren

    • hierzu verwendet man i. a. funktionale oder prozedurale Programmierung

  • komplexere Software läßt sich gut mit objektorientierten Konzepten umsetzen

Manche Dinge (z. B. der vorgesehene Variablentyp) wßnsche ich mir in der Schnittstellenspezifikation. Aus meiner Sicht ist es keine gute Idee, den intendierten Parametertyp aus dem Funktionscode ableiten zu mßssen. Insbesondere bei der Verwendung von Bibliotheken ist das unter Umständen nicht mal mÜglich. Auch hier helfen Best-Practices (Beschreibung des Parameter-Typs in der Doku).

Lern-Sprache

Das schöne an Python ist die nahtlose Integration unterschiedlicher Programmier-Paradigmen (prozedural, funktional, objektorientiert). Dadurch entsteht eine schöne Varianz, so daß man auch leicht die verschiedenen Paradigmen vergleichen kann und ihre Vor- und Nachteile kennenlernt. Konzepte wie Lambda und Comprehensions helfen, sehr eleganten und leserlichen Code zu produzieren.

Insbesondere die gute Raspberry Pi UnterstĂźtzung macht Python zu DER Lernsprache.

Ein Nachteil ist aus meiner sicht, daß durch den Interpreter-Ansatz Best-Practices schwieriger einzuhalten sind. Eine Variable kann an Zeile 23 einen String repräsentieren und an Zeile 24 einen Integer ... das wird einfach nicht verhindert. Während erfahrene Softwareentwickler das automatisch vermeiden, sind Anfänger vielleicht geneigt, solche "Abkürzungen" zu nehmen und Spaghetti-Code zu schreiben, der kaum lesebar und fehlerträchtig ist. Allerdings - wenn man es positiv sieht - lassen sich zumindest solche Ansätze direkt nebeneinanderstellen und die Vorteile schön rausarbeiten ... zum Lernen vielleicht sogar NOCH besser.

PreviousTwitter BootstrapNextPython Libraries

Last updated 8 months ago

Was this helpful?

Pylance Language Server