Datei = Abfolge von Bytes auf einem Speichermedium
Oft ist eine Datei eine Folge von Textzeichen - z.B. die Formate .txt, .html, .csv oder .py.
Der Inhalt von Textdateien kann als Strings geschrieben und gelesen werden, andere Dateien können als Bytefolge repräsentiert werden.
file = open("message.txt", "w", encoding="utf-8")
file.write("hello world\n")
file.write("end\n")
file.close()
Die Datei wird zum Schreiben geöffnet (w = write).
Als Zeichencodierung wird UTF-8 verwendet.
file = open("message.txt", encoding="utf-8")
content = file.read()
file.close()
print(content)
Standardmodus: Lesen (r = read)
# mode: text, append
open("todos.txt", mode="ta")
t
: Textmodus (standard)b
: Binärr
: Lesen (standard)w
: (Ãœber)schreibena
: Anhängenwasm_content = bytes([
0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 127,
3, 2, 1, 0, 7, 10, 1, 6, 97, 110, 115, 119, 101, 114,
0, 0, 10, 6, 1, 4, 0, 65, 42, 11
])
file = open("answer.wasm", mode="wb")
file.write(wasm_content)
file.close()
with open("todos.txt", encoding="utf-8") as file_obj:
content = file_obj.read()
Datei wird automatisch geschlossen, wenn der eingerückte Codeblock verlassen wird.
Textdateien können unterschiedlich codiert sein:
Praxistipp: Wenn möglich UTF-8 verwenden (beste Unterstützung für Sonderzeichen)
Die Standard-Zeichencodierung für Textdateien hängt vom Betriebssystem ab:
import locale
locale.getpreferredencoding()
Objekte, die z.B. .read()
oder .write()
unterstützen:
open()
)sys.stdout
, sys.stdin
sys.stdin.readline()
urllib.request.urlopen('https://google.com')
Zeile für Zeile einlesen (geringer Speicherbedarf):
with open("myfile.txt", encoding="utf-8") as file:
for line in file:
print(line)
Methoden / Attribute:
.close()
.mode
.read()
(lies die ganze Datei ein).read(10)
(lies die nächsten 10 Bytes).readline()
(lies die nächste Zeile)Programm, das Einträge einer Einkaufsliste vom Benutzer abfragt und in einer Textdatei abspeichert
Möglichkeiten zum Speichern / Lesen:
JSON: weit verbreitetes und standardisiertes Dateiformat
kann die grundlegenden Python-Datentypen repräsentieren (none, bool, int, float, list, dict)
import json
data = ["one", "two", "three"]
jsonstring = json.dumps(data)
with open("numbers.json", mode="w", encoding="utf-8") as jsonfile:
jsonfile.write(jsonstring)
import json
with open("numbers.json", encoding="utf-8") as jsonfile:
jsonstring = jsonfile.read()
data = json.loads(jsonstring)
CSV ist ein Textdateiformat, das tabellarische Daten beinhalten kann
Beispiel:
ISO,Country,Capital,Languages
AD,Andorra,Andorra la Vella,"ES,FR"
AE,United Arab Emirates,Abu Dhabi,"AE,fa,en,hi,ur"
AF,Afghanistan,Kabul,"AF,tk"
Python Libraries:
import pandas as pd
data = pd.DataFrame(
[
["CN", 9.6, 1386],
["RU", 17.0, 144],
["US", 9.8, 327],
],
columns=["code", "area", "population"],
)
data.to_csv("countries.csv")
import pandas as pd
data = pd.read_csv("countries.csv")
print(data)
print(data.values.tolist())
import csv
data = [
['code', 'area', 'population'],
['CN', 9.6, 1386],
['RU', 17, 144],
['US', 9.8, 327]
]
with open('countr.csv', 'w', encoding='utf-8', newline='') as f:
writer = csv.writer(f)
writer.writerows(data)
with open('countr.csv', encoding='utf-8', newline='') as f:
reader = csv.reader(f)
for row in reader:
print(row)
zwei Pakete in der Python-Standardlibrary:
xml.etree.ElementTree
xml.dom.minidom
externe Library (Erweiterung von ElementTree):
lxml
import xml.etree.ElementTree as et
person = et.Element('person')
name = et.SubElement(person, 'name')
name.text = 'Adam'
age = et.SubElement(person, 'age')
age.text = '40'
age.set("unit", "years")
Beim Speichern von XML immer ein Encoding angeben!
xmlbytestring: bytes = et.tostring(person, encoding='utf-8')
with open("myfile.xml", mode="wb") as file:
file.write(xmlbytestring)
# or
xmlstring: str = et.tostring(person, encoding='unicode')
with open("myfile.xml", encoding="utf-8", mode="w") as file:
file.write(xmlstring)
# or
tree = et.ElementTree(person)
tree.write("myfile.xml", encoding="utf-8")
import xml.etree.ElementTree as et
person = et.fromstring(xmlstring)
for childnode in person:
print(childnode.tag)
print(childnode.text)
print(childnode.attrib)
Eigenes Dateiformat, in dem verschiedene Python-Dateitypen gespeichert werden können
Achtung:
import pickle
import datetime
now = datetime.datetime.now()
serialized = pickle.dumps(now)
with open("datetime.pickle", mode="wb") as picklefile:
picklefile.write(serialized)
import pickle
with open("datetime.pickle", mode="rb") as picklefile:
serialized = picklefile.read()
earlier = pickle.loads(serialized)
Python-Datenstruktur für das Tic-Tac-Toe-Feld:
field = [
['X', 'O', None],
['X', 'X', 'O'],
['O', 'O', 'X']
]
wichtige Pakete:
os.getcwd()
(aktueller Pfad)os.chdir()
os.listdir()
os.walk()
os.mkdir("foo")
os.mkdir("foo/bar/baz")
os.remove("foo.txt")
(Datei löschen)os.rmdir("foo/bar/baz")
(leeren Ordner löschen)shutil.rmtree()
(Ordner löschen)os.rename("foo.txt", "bar.txt")
shutil.move()
(Datei oder Ordner verschieben)shutil.copy("foo.txt", "bar")
(Datei kopieren)shutil.copytree()
(Ordner kopieren)Direkte Ausgabe mittels os.system
:
os.system("ls .")
os.system("mkdir foo")
Ergebnisse in Python einlesen mittels os.popen
:
a = os.popen("ls .").read()
print(a)
Programm, das alle Textdateien in einem Ordner nach einem Begriff durchsucht und die Anzahlen angibt
Mini-Sprache, um ein Suchmuster für Text zu definieren
Beispiele einfacher Suchmuster:
https?://.+?\.com
<h1>.+?</h1>
\d?\d:\d\d
Online: https://regexr.com/
In VS Code: Ctrl+F und Klick auf den Button .*
Die folgenden Zeichen haben besondere Bedeutungen:
\
^
$
.
|
?
*
+
()
[]
{}
Die besondere Bedeutung umgehen wir durch Voranstellen eines Backslashes:
13\$
9\.99€
1\+1=2
.
: jedes Zeichen außer ein Zeilenumbruch\s
: Whitespace\d
: Ziffer\w
: Ziffer, Buchstabe oder UnterstrichÃœbung: finde alle Ziffern in einem Dokument
a*
: Buchstabe a 0 Mal oder öfter wiederholt (findet den längsten String)a*?
: Buchstabe a 0 Mal oder öfter wiederholt (finden den kürzesten String)a+
: Buchstabe a 1 Mal oder öfter wiederholt (findet den längsten String)a+?
: Buchstabe a 1 Mal oder öfter wiederholt (findet den kürzesten String)Übungen:
.jpeg
oder .jpg
endenAusdrücke können via (...)
gruppiert werden
Beispiele:
(ab)+
findet Wiederholungen der Zeichenfolge ab<(-=)+->
findet Muster der folgenden Art: <-=-=-=-=->
...|...|...
: Matcht eine der aufgelisteten Alternativen
Beispiel zum finden eines Bildes: \.(jpe?g|png|gif)
Ãœbungen:
http://
oder https://
beginnen und mit .com
oder .org
endenGruppen können verwendet werden, um Informationen auszulesen
Beispiel: (\d?\d):(\d\d)
liest zwei Werte aus
\A
: Anfang eines Strings\Z
: Ende eines Strings^
: Anfang einer Zeile$
: Ende einer Zeile[a-z]
: beliebiger kleiner ASCII-Buchstabe[a-zA-Z]
: beliebiger ASCII-Buchstabe[,;.]
: gleichwertig zu (,|;|.)
Reguläre Ausdrücke werden verwendet, um einen Teil eines Strings, der einem bestimmten Muster entspricht, zu finden
In Python können reguläre Ausdrücke mittels des Pakets re
behandelt werden, insbesondere:
re.search
re.finditer
Beispiel:
import re
match = re.search(r"[a-z][A-Z]", "abCdE")
if match:
print("match")
print(match[0]) # bC
else:
print("no match")
Beispiel:
import re
match_iter = re.finditer(r"https?://.+?\.com", website_content)
for match in match_iter:
print(match[0])
Aufgabe: finde alle URLs in einem HTML-Dokument auf der Festplatte
(Beispieldokument: z.B. Seite https://news.ycombinator.com auf Festplatte speichern)
times = re.finditer(
r'(\d?\d):(\d\d)',
'The course times are 9:30 - 16:30'
)
for time in times:
print(f"hour: {time[1]}")
print(f"minute: {time[2]}")
Performanceoptimierung, wenn reguläre Ausdrücke wiederverwendet werden:
my_re = "..."
re.search(my_re, ...)
re.finditer(my_re, ...)
wird zu
my_re = "..."
my_re_obj = re.compile(my_re)
my_re_obj.search(...)
my_re_obj.finditer(...)
Python-Pakete:
datetime.date
datetime.time
datetime.datetime
a = datetime.time(hour, minute, second, microsecond)
b = datetime.datetime(2018, 1, 13)
c = datetime.datetime(2018, 3, 26, 12, 30)
c - b
import time
for i in range(10):
print(i)
time.sleep(1)
aktuelle Unix-Zeit (Sekunden seit 1970-01-01 00:00:00 UTC)
import time
time.time()
Funktionen zur Python-Umgebung
Beispiele:
argv
stdout.write
getrefcount
path
version
version_info
Kommandozeilenparameter sind auslesbar über sys.argv
# hello.py
import sys
print(sys.argv)
python hello.py --run --file=foo.txt
['hello.py', '--run', '--file=foo.txt']
import sys
old_stdout = sys.stdout
class LoudStdout:
def write(self, text):
old_stdout.write(text.upper())
loudstdout = LoudStdout()
sys.stdout = loudstdout
import socket
client = socket.socket()
client.connect(("google.com", 80))
client.send(b"GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
response = client.recv(4096)
print(response)
Ãœbung: Skript, das die eine Datei request.httpreq
einliest und verarbeitet und eine Datei response.httpres
generiert
low-level Interface
from http.client import HTTPSConnection
connection = HTTPSConnection("www.google.com")
connection.request("GET", "/")
answer = connection.getresponse()
content = answer.read()
connection.close()
print(content)
in Standardlibrary
from urllib import request
content: bytes = request.urlopen(
"https://www.google.com").read()
Externes Paket, installierbar via pip:
pip install requests
import requests
content: str = requests.get("https://www.google.com").text
Installation:
pip install selenium
geckodriver / chromedriver / ...
geckodriver Download von:
https://github.com/mozilla/geckodriver/releases/tag/v0.23.0
Herunterladen und in einem Pfad in Pythons sys.path
ablegen - oder im Projektverzeichnis
Siehe Präsentation zu HTTP
python -m http.server
https://docs.python.org/2/howto/webservers.html
(etwas veraltet)
= Standard-Interfaces, um ein Programm auf einem Server auf einen HTTP-Request antworten zu lassen
CGI (Common Gateway Interface): Interface zwischen Serversoftware (z.B. Apache) und Webanwendung (geschrieben in beliebiger Programmiersprache)
Interface beruht auf stdin, stdout und Umgebungsvariablen
Nachteil: für jede Anfrage muss ein neuer Prozess gestartet werden (langsam)
WSGI = Web Server Gateway Interface
Standard, um ein Python-Programm am Server auf HTTP-Anfragen antworten zu lassen
Inspiriert von CGI; wichtiger Vorteil: ein laufender Prozess kann der Reihe nach mehrere Anfragen beantworten
Einstiegspunkt ist eine Python-Funktion
Die Funktion bekommt zwei Parameter übergeben: environ
und start_response
Die Anfrageparameter sind über environ
abzufragen (z.B. URL, HTTP Header, Formulardaten, ...)
Der zweite Parameter ist eine Funktion, üblicherweise start_response
genannt.
Zum starten der Antwort rufen wir start_response(status, response_headers)
auf, z.B.:
start_response(
"200 OK",
[("Content-Type", "text/plain; charset=utf-8")]
)
Der Antwortkörper wird als ein Iterable von Bytestrings zurückgegeben, z.B. als Liste von Bytestrings.
server-software:
from wsgiref.simple_server import make_server
from app import application
server = make_server("localhost", 8000, application)
server.serve_forever()
Dieser Teil wird üblicherweise von Libraries wie gunicorn übernommen
# app.py
def application(environ, start_response):
response_status = "200 OK"
response_body_text = "hello"
response_body = response_body_text.encode("utf-8")
response_headers = [
("Content-Type", "text/plain; charset=utf-8"),
("Content-Length", str(len(response_body))),
]
start_response(response_status, response_headers)
return [response_body]
Wir können es im debugger begutachten, z.B.:
{
"CONTENT_LENGTH": "12"
"HTTP_USER_AGENT": 'Mozilla/5.0 (Win...',
"HTTP_COOKIE":
"PATH_INFO": "/todos/new",
"REQUEST_METHOD": "GET",
"wsgi.input": ...
}
PATH_INFO im environ-dictionary: angefragter Pfad am Server
Aufgabe: Anzeige verschiedener Seiten unter verschiedenen Adressen
response_headers = [
("Location", "/login"),
("Content-Length", "0")
]
start_response("307 Temporary Redirect", response_headers)
return [b'']
Aufgabe: Weiterleitung einiger Seiten, z.B. /now
leitet weiter auf /time
Cookies = kleine Datenmengen, die von einer Website im Browser abgelegt werden können
Cookies dienen insbesondere dazu, einen früheren Besucher wiederzuerkennen, z.B. für:
response_headers.append((
"Set-Cookie", "mycookie123=abcd; Max-Age=30"
))
try:
current_cookies_str = environ["HTTP_COOKIE"]
except KeyError:
...
Resultat z.B.: "cookie1=one; cookie2=two"
from http import cookies
...
mycookies = cookies.SimpleCookie()
mycookies.load(current_cookies_str)
mycookies["cookie1"].value
indem ein neues Cookie gesetzt wird, dessen "Ablaufsdatum" in der Vergangenheit liegt:
response_headers.append((
"Set-Cookie",
"mycookie123=; expires=Thu, 01 Jan 1970 00:00:00 GMT"
))
Aufgabe: Website, die einen Benutzer nur 5x eine Seite aufrufen lässt und ihn dann auffordert, sich zu registrieren, um die Seite weiter zu verwenden
So lesen wir Parameter aus Formularen aus:
from urllib.parse import parse_qs
request_body_size = int(environ.get('CONTENT_LENGTH', '0'))
# environ["wsgi.input"] is a file-like object
request_body = (environ["wsgi.input"]
.read(request_body_size)
.decode("utf-8"))
parameters = parse_qs(request_body)
first_name = parameters.get("first-name")[0]
WSGI Konfigurationsdatei
# /var/www/username_pythonanywhere_com_wsgi.py
import sys
path = "/home/username/"
if path not in sys.path:
sys.path.append(path)
from app import application
Für Grundlagen siehe Präsentation zu HTTP
Libraries für die API Entwicklung in Python:
Aufgabe: Entwickle ein einfaches API mit FastAPI, das folgende Resourcen bietet:
/time
/date
/randnr
(Zufallszahl)Aufgabe:
Entwickle ein API mit FastAPI, das eine Liste von Todos verwalten kann
setze folgende Endpunkte um:
GET /todos
(JSON array aller Todos)GET /todos/$id
(einzelnes Todo anhand id)POST /todos
(Hinzufügen eines Todos)DELETE /todos/$id
(Löschen eines einzelnen Todos)PATCH /todos/$id
(Ändern eines Todos)POST /delete_completed_todos
Die Todos können in einer einfachen JSON-Datei gespeichert werden - oder auch in einer tatsächlichen Datenbank
FastAPI: Library für API-Entwicklung, die auf Typenannotationen beruht
grundlegendes Beispiel:
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/double")
async def double(number: int):
return number * 2
ausführen des Beispiels via:
uvicorn main:app --reload
Anzeigen der Dokumentation und Testumgebung (Swagger) auf: http://localhost:8000/docs
Zu Grundlagen siehe Präsentation Datenbanken und Datenspeicherung
Database API Specification: Standard, der von verschiedenen Python-Datenbankanbindungen umgesetzt wird; standardisiert in PEP 249
PEP 249 definiert:
Libraries, die PEP 249 implementieren:
SQLite: Datenbank, die nur eine einzelne Datenbankdatei verwendet
Ist in Python integriert (Modul sqlite3
)
import sqlite3
# database stored in a file
connection = sqlite3.connect('contacts.db')
# in-memory database
connection = sqlite3.connect(':memory:')
PIP-Paket psycopg
import psycopg
connection = psycopg.connect(host="localhost",
user="...",
password="...",
dbname="...")
oder
connection = psycopg.connect(
"postgresql://user:password@localhost/dbname")
PIP-Paket PyMySQL
import pymysql.cursors
connection = pymysql.connect(host='localhost',
user='user',
password='passwd',
db='db',
charset='utf8mb4')
Python-Paket cx_Oracle
import cx_Oracle
connection = cx_Oracle.connect(user="user",
password="password",
dsn="localhost/oraclepdb")
ODBC = open database connectivity: Standard zur Anbindung an Datenbanken (unabhängig von Python)
pyodbc = Implementierung für Python, damit können beliebige ODBC-Datenbanken angebunden werden
import pyodbc
connection = pyodbc.connect(
"Driver=SQLite3 ODBC Driver;Database=contacts.db")
connection = ...
cursor = connection.cursor()
cursor.execute("SELECT ...")
print(cursor.fetchall())
...
cursor.execute("INSERT INTO ...")
cursor.execute("INSERT INTO ...")
connection.commit()
...
connection.close()
gefährlich:
search_name = input()
res = cursor.execute(
f"""SELECT tel FROM person WHERE name = '{search_name}'"""
)
sichere Methode (mit SQL-Escaping):
search_name = input()
res = cursor.execute(
"""SELECT tel FROM person WHERE name = ?;""",
(search_name, )
)
https://www.python.org/dev/peps/pep-0249/#paramstyle
Die Attribute sqlite3.paramstyle
, pymysql.paramstyle
etc. geben das Format für Abfragen mit Parametern an
cursor.execute(command, parameters)
cursor.rowcount
: Anzahl der letzten Ergebnissecursor.fetchone()
: Eine Zeile des Resultats auslesen (üblicherweise als Tupel)cursor.fetchmany(10)
cursor.fetchall()
siehe courses-tutorials/python-todolist-wsgi-sqlite
SQLite Datentypen und zugehörige Python Datentypen:
NULL
- None
INT
/ INTEGER
- int
REAL
- float
TEXT
- str
BLOB
- bytes
Zwei Typen, die üblicherweise nicht von SQLite unterstütz werden:
TIMESTAMP
- datetime
DATE
- date
Speicherung dieser Typen als SQL DECIMAL mit automatischer Umwandlung in / von Python Typen:
connection = sqlite3.connect(
'contacts.db'
detect_types=sqlite3.PARSE_DECLTYPES)
Ãœbung: Erstelle eine Kontaktdatenbank die automatisch SQL Dezimalzahlen, die das Geburtsdatum beschreiben, als date
-Objekte ausliest.
Wir können weitere Typen speichern, indem wir sogenannte Adapter- und Converer- Funktionen schreiben. Diese zusätzlichen Typen werden üblicherweise als Bytesequenzen in der Datanbank abgelegt.
Ein Adapter ist eine Funktion, die ein Python Objekt in einen SQL Wert umwandelt.
Ein Converter ist eine Funktion, dien einen SQL Wert in ein Python Objekt umwandelt.
Siehe:
Ãœbungen:
ipadress.IPv4Address
zu speichern bzw zu lesen - in SQL würden diese als kodierter Bytestring gespeichert werdenSQLAlchemy = Object Relational Mapper
Objektorientierter Zugriff auf beliebige SQL-Datenbanken
Alternative: Django ORM
# db_interface.py
from sqlalchemy import create_engine
engine = create_engine("sqlite:///music.db", echo=True)
engine.connect()
# schema.py
from sqlalchemy.orm import mapped_column, Mapped, DeclarativeBase
class Base(DeclarativeBase):
pass
class Artist(Base):
__tablename__ = "artist"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column()
country: Mapped[str] = mapped_column()
# db_interface.py
Base.metadata.create_all(engine)
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()
...
session.commit()
INSERT INTO artist VALUES ('The Beatles', 'United Kingdom');
wird zu:
beatles = Artist(name="The Beatles", country="United Kingdom")
session.add(beatles)
SELECT name, country FROM artist;
wird zu:
for artist in session.query(Artist):
print(f"{artist.name} ({artist.country})")
oder
for name, country in session.query(Artist.name, Artist.country):
print(f"{name} ({country})")
SELECT name, country FROM artist ORDER BY name;
wird zu:
for name, country in session.query(
Artist.name, Artist.country).order_by(Artist.name):
...
SELECT name, country FROM artist WHERE artist.name='The Beatles'
wird zu:
session.query(Artist).filter(Artist.name=="The Beatles").one()
aktuell:
<db_schema.Song object at 0x00000175902A5FD0>
besser:
Help! - The Beatles
umsetzbar mittels __repr__
/ __str__
UPDATE song
SET title = 'Help'
WHERE title = 'Help!';
wird zu
entry = session.query(Song).filter(Song.title=="Help!").one()
entry.title = "Help"
DELETE
FROM song
WHERE title = 'Help!';
wird zu
session.query(Song).filter(Song.title=="Help!").delete()
class Song(Base):
__tablename__ = "song"
id = Column(Integer, primary_key=True)
title = Column(String(30))
artist_id = mapped_column(ForeignKey("artist.id"))
from sqlalchemy.orm import relationship
class Artist(Base):
...
songs: Mapped[Song] = relationship("Song", back_populates="artist")
class Song(Base):
...
artist_id = mapped_column(ForeignKey("artist.id"))
artist: Mapped[Artist] = relationship("Artist", back_populates="song")
yesterday = Song(title="Yesterday", artist=beatles)
help = Song(title="Help!", artist_id=beatles.id)
session.add(...)
print(yesterday.artist)
print(beatles.songs)
PIP-Paket openpyxl
Erstellen, Speichern, Laden:
import openpyxl
wb = openpyxl.Workbook()
wb.save("wb.xlsx")
wb2 = openpyxl.load_workbook("wb.xlsx")
Erstellen und Abfragen von Worksheets:
ws1 = wb.worksheets[0]
print(wb.sheetnames) # ["Sheet"]
ws1 = wb["Sheet"]
# new sheet 0
wb.create_sheet("Sheet2", 0)
ws2 = wb["Sheet2"]
Arbeiten mit Worksheets
ws = wb.worksheets[0]
ws.title = "times table"
Arbeiten mit Zellen
a1 = ws.cell(1, 1)
a1 = ws["A1"]
a1.value # None
a1.value = 3
a1.row # 1
a1.column # 1
a1.coordinate # "A1"
Beispiel: Erstellen der folgenden Tabelle:
product | price | stock |
---|---|---|
apple | 1.00 | 10 |
banana | 0.70 | 20 |
pear | 0.80 | 20 |
import tkinter as tk
window = tk.Tk()
window.mainloop()
Ein Fenster wird als Tk
-Objekt erstellt
Mit mainloop()
starten wir das Programm (und warten auf Benutzerinteraktion)
import tkinter as tk
window = tk.Tk()
hello_label = tk.Label(master=window, text="Hello!")
hello_label.pack()
window.mainloop()
hello_label = tkinter.Label(master=window, text="Hello!")
hello_label.config(text="Hi!")
...
message_label = tk.Label(master=window, text="")
message_label.pack()
def display_message():
message_label.config(text="Hello!")
hello_button = tk.Button(master=window,
text="Say Hello!",
command=display_message)
hello_button.pack()
...
Der Anwendungszustand wird am besten in einer Klasse gespeichert und verwaltet.
import tkinter as tk
class CounterApp:
def __init__(self):
self.count = 0
self.window = tk.Tk()
self.count_btn = tk.Button(
master=self.window,
text=str(self.count),
command=self.increment_count
)
self.count_btn.pack()
def increment_count(self):
self.count += 1
self.count_btn.config(text=str(self.count))
def run(self):
self.window.mainloop()
counter = CounterApp()
counter.run()
Aufgabe: Einen Reset-Button zum demonstrierten Counter hinzufügen.
Mit pack
: Einfache Zeilen- oder Spaltenlayouts
label.pack()
Mit grid
: Komplexere Ausrichtungen an einem Raster sind möglich
label_a.grid(column=0, row=0)
label_b.grid(column=0, row=1)
Möglichkeiten:
height
(in Pixeln oder relativ zur Schriftgröße)width
borderwidth
background
(Hintergrundfarbe)foreground
(Textfarbe)justify
(Textausrichtung, z.B.: tk.CENTER
, tk.LEFT
, tk.RIGHT
)padx
, pady
(Abstand zwischen Rahmen und Inhalt)font
(z.B.: ("Arial", 16)
)Label
Button
Frame
Entry
Ermöglicht das Erstellen von .exe-Dateien aus Python-Projekten
Insbesondere für GUI-Anwendungen sinnvoll
Installation:
pip install pyinstaller
Erstellen einer ausführbaren Anwendung:
pyinstaller app.pyw --onefile --windowed
Resultat: dist/app.exe
https://automatetheboringstuff.com/chapter16/
(für Fehler und Korrekturen siehe nächste Folie)
zur verdeckten Passworteingabe: Modul "getpass"
Fehler in der resouce:
Die query besteht aus zwei Einträgen:
UIDs = imapObj.search(['SINCE 05-Jul-2014'])
UIDs = imapObj.search(['SINCE', '05-Jul-2014'])
Neuere Version von pyzmail verwenden:
→ pyzmail
pyzmail36
bytes statt string verwenden:
→ 'BODY[]'
b'BODY[]