Herzlichen Glückwunsch!

Sie haben den ersten Programmierkurs des Computerlinguistikstudiums erfolgreich abgeschlossen! Damit verfügen Sie jetzt über eine gute Grundlage für die weiterführenden Kurse in den nächsten Semestern.

Organisatorisches

Falls Sie im Laufe des Semesters genügend Aufgaben eingereicht haben (19 oder mehr), können Sie sich heute in der Pause/nach der Stunde Ihren BN abholen. Sollte Ihr BN nicht dabei sein, obwohl Sie genug Aufgaben eingereicht haben, sprechen Sie uns bitte an.

Falls Sie nicht die Grenze für den BN erreicht haben, können Sie noch bis einschließlich 10. Februar Aufgaben nachreichen. Achtung: In der Nachreichphase ab 31.01. akzeptieren wir nur noch Aufgaben aus Woche 9 oder danach.

Heute werden wir einige Themen besprechen, die wir hier noch nicht behandeln konnten, die Ihnen aber vielleicht später über den Weg laufen werden...

Virtualenv (virtuelle Python-Umgebungen)

In den letzten Wochen haben wir gelegentlich neue Python-Pakete installiert, z.B. Mastodon oder NLTK. Die Module für diese Pakete werden im Python-Installationsordner angelegt, typischerweise in einem Pfad wie C:\Users\Student\AppData\Local\Programs\Python\Python37-32\Lib\site-packages.

Um die Python-Umgebung auf einem Computer oder Server zu ändern, braucht man bestimmte Berechtigungen. Das ist ein Problem, wenn man auf einem Gerät arbeitet, auf dem man keinen Admin-Zugang hat: Man müsste jedesmal die Admins kontaktieren, um ein Paket nachzuinstallieren...

Ein zweites Problem ist, dass verschiedene Projekte möglicherweise unterschiedliche Versionen von Pythonpaketen brauchen. Von Version zu Version kann so ein Paket sich deutlich verändern. Dadurch kann es Konflikte geben, wenn Ihre verschiedenen Projekte unterschiedliche Versionen der Pakete brauchen. Noch schlimmer ist es, wenn einige Projekte in Python 2 und andere in Python 3 geschrieben sind.

Aus diesen und weiteren Gründen gibt es die Möglichkeit, virtuelle Python-Umgebungen einzurichten. Dabei wird eine minimale Python-Installation in Ihrem Projektordner angelegt, und Sie installieren genau die Pakete, die Sie brauchen, in genau der Version, die für Ihr Projekt geeignet ist. Das Einrichten von virtualenvs benötigt weniger umfangreiche Berechtigungen als das Verändern der systemweiten Python-Installation!

Der Bot, den wir zu Weihnachten gemeinsam programmiert haben, läuft auf einem Server, der alle 3 Stunden unser Pythonprogramm ausführt. Dabei wird eine minimale, virtuelle Python-Installation verwendet, die nur die benötigten Module enthält.

In der Kommandozeile des Servers können wir unseren Bot zunächst nicht ausführen, weil die systemweite Installation das Paket Mastodon nicht kennt:

Wir müssen zunächst den virtuellen Python-Interpreter unseres Bots aktivieren.

Wenn wir jetzt das Pythonprogramm ausführen, wird der virtuelle Python-Interpreter aufgerufen, der Mastodon kennt.

Weitere Infos zu virtuellen Environments finden Sie z.B. auf StackOverflow oder in der Dokumentation des virtualenv-Pakets.

Objektorientierung

Wir verwenden bisher Variablen, um je einen Wert unter selbstgewählten Namen zu speichern. Dabei können wir die Namen so wählen, dass klar wird, welche Funktion die Variablen jeweils haben.

Mit Objektorientierung haben wir die Möglichkeit, mehrere Variablen und dazugehörige Funktion zu bündeln, und zwar in Form von Klassendefinitionen. Eine Klasse ist ein "Rezept" für neue Objekte und kann folgendes enthalten:

  • Attribute. Das sind Variablen, die einem Objekt der Klasse zugeordnet werden.
  • Methoden. Das sind Funktionen, die auf Objekte der Klasse angewendet werden können.

Eine Einführung in Objektorientierung mit Python finden Sie z.B. auf Youtube. Hier ein kleines Beispiel, das die Klasse "Studierende" definiert und dann beliebig viele Objekte davon instanziieren kann.

Beim Erzeugen eines neuen Objekts geben wir den Namen, die Matrikelnummer und die Emailadresse an. Die Note setzen wir auf den Startwert -1, damit erkennbar ist, dass es zwar eine Note geben soll, wir darüber aber bisher noch keine Informationen haben.

Wenn wir einzelne Objekte ausgeben wollen, wird nur eine kryptische Meta-Information angezeigt, nämlich dass es sich um ein Objekt der Klasse Student handelt, das an einer bestimmten Speicheradresse abgelegt wurde.

Um inhaltliche Informationen über die Objekte ausgeben zu lassen, definieren wir die Methode who_is_this(), die auf jedes Objekt der Klasse angewendet werden kann. Rückgabe dieser Methode ist eine kurze Beschreibung des aktuellen Objekts.

Die Note können wir mit der Methode set_final_grade() einspeichern und mit der Methode get_final_grade() auslesen.

Schließlich haben wir auch noch eine Methode, mit der wir das aktuelle Objekt mit einem anderen Objekt der gleichen Klasse vergleichen können und ermitteln, wer von beiden die bessere Note hat.

In [ ]:
# Die "Kopfzeile" einer Klassendefinition erinnert an
# Funktionsdefinitionen oder Schleifen.
# Auch Klassendefinitionen werden eingerückt.
class Student:
    def __init__(self, inputname, inputmatrikel, inputemail):
        # Diese Funktion heißt Konstruktor.
        # Die Argumente inputname, inputmatrikel, inputemail
        # werden beim Erzeugen eines neuen Objekts angegeben
        # und dann in den Attributen self.name, self.matrikel
        # und self.email gespeichert.
        self.name = inputname
        self.matrikel = inputmatrikel
        self.email = inputemail
        
        # Für das Attribut self.finalgrade legen wir
        # den Startwert -1 fest.
        self.finalgrade = -1
        
        print("New Student:\n\tName: {}\n\tMatrikel: {}\n\tEmail: {}".format(self.name, self.matrikel, self.email))
        
    def set_final_grade(self, inputgrade):
        # Diese Methode kann auf ein Objekt
        # der Klasse angewendet werden, um einen
        # Wert für das Attribut self.finalgrade
        # festzulegen.
        self.finalgrade = inputgrade
        print("Set final grade for this student to {}.".format(self.finalgrade))
        
    def get_final_grade(self):
        # Diese Methode gibt den Wert des Attributs
        # self.finalgrade für das aktuelle Objekt
        # zurück.
        return self.finalgrade
    
    def who_is_this(self):
        # Diese Methode ergänzen wir, damit wir
        # Objekte der Klasse informativ printen
        # können.
        return "Existing Student:\n\tName: {}\n\tMatrikel: {}\n\tEmail: {}".format(self.name, self.matrikel, self.email)
    
    def who_is_better(self, student2):
        # Diese Methode ermittelt, wer von zwei Studierenden
        # die bessere Note hat.
        if self.finalgrade != -1 and student2.finalgrade != -1:
            if self.finalgrade < student2.finalgrade:
                return "Winner: {}, Loser: {}".format(self.name, student2.name)
            else:
                return "Winner: {}, Loser: {}".format(student2.name, self.name)
        return "One or both students have no final grade."
        
# print("Neue Objekte erstellen:")        
# # Hier werden neue Objekte der Klasse Student erzeugt.
# # In den Klammern stehen die Eingabeargumente für den
# # Konstruktor.
# lisa = Student("Lisa Meier", "12345", "lisa@meier.de")
# hans = Student("Hans Meier", "54321", "hans@meier.de")

# print("\nBestehende Objekte printen:")
# # Wir können die Variablenwerte printen, sehen aber nur,
# # dass es sich um Objekte der Klasse Student handelt.
# print(lisa)
# print(hans)

# print("\nBestehende Objekte informativ printen:")
# print(lisa.who_is_this())
# print(hans.who_is_this())

# print("\nAttributwerte von Objekten verändern:")
# lisa.set_final_grade(1.0)
# hans.set_final_grade(2.3)

# print("\nZwei Objekte miteinander vergleichen:")
# print(lisa.who_is_better(hans))
# print(hans.who_is_better(lisa))

Objektorientierung ist in Python ein möglicher Stil, dem Sie folgen können, aber nicht müssen. Sie werden später im Studium auch noch Java lernen. Das ist eine Programmiersprache, in der alles objektorientiert umgesetzt wird.

Aufgabe

Pythonpakete können objektorientiert geschrieben sein, müssen es aber nicht. Ein Beispiel für ein objektorientiertes Paket ist random. Öffnen Sie das Paket (die Datei liegt vermutlich im Pfad C:\Users\Student\AppData\Local\Programs\Python\Python37-32\Lib\random.py) und lesen Sie nach, wie die Methode shuffle() implementiert ist.

  • Was ist der Rückgabewert der Funktion?
  • An welcher Stelle/welchen Stellen wird auf Attribute oder Methoden des instanziierten Random-Objekts zugegriffen?
  • Welche Strategie verfolgt die Methode, um die übergebene Liste zu mischen?
  • Können Sie eine eigene Funktion (nicht Methode) schreiben, die das Verhalten von shuffle() kopiert? Unten ist bereits eine Variante von _randbelow() gegeben, die Sie verwenden können. Ignorieren Sie in Ihrer Funktion das Argument "random=None" und bauen Sie nur den Teil der Methode nach, der ausgeführt wird, falls das Argument random beim Aufruf fehlt.
In [ ]:
import random

input_list = [1, 2, 3, 4, 5, 6, 7, 8]

def my_randbelow(i):
    return random.randint(0, i)

def my_shuffle(x):
    pass
    # ergänzen Sie hier Ihren Code.
    # hier nicht das random-Modul
    # verwenden!

print(my_randbelow(5))
my_shuffle(input_list)
print(input_list)

Python in der Kommandozeile

Die Kommandozeile/Terminal/Shell haben wir zu Beginn des Semesters kennengelernt. In der Kommandozeile können wir bestimmte Befehle ausführen, z.B. cd (change directory) zum Wechseln des aktuellen Verzeichnisses:

Auch Pythonprogramme können von der Kommandozeile aus aufgerufen werden. VSCode macht das für uns automatisch, wenn wir das aktuelle Programm ausführen.

Wir können noch eine Reihe weiterer Argumente ergänzen, wenn wir das möchten. Wenn wir den Befehl python -h (help) ausführen, erhalten wir eine Übersicht über mögliche Eingaben:

 C:\Users\eseyffarth> python -h
usage: C:\ProgramData\Anaconda3\python.exe [option] ... [-c cmd | -m mod | file | -] [arg] ...
Options and arguments (and corresponding environment variables):
-b     : issue warnings about str(bytes_instance), str(bytearray_instance)
         and comparing bytes/bytearray with str. (-bb: issue errors)
-B     : don't write .py[co] files on import; also PYTHONDONTWRITEBYTECODE=x
-c cmd : program passed in as string (terminates option list)
-d     : debug output from parser; also PYTHONDEBUG=x
-E     : ignore PYTHON* environment variables (such as PYTHONPATH)
-h     : print this help message and exit (also --help)
-i     : inspect interactively after running script; forces a prompt even
         if stdin does not appear to be a terminal; also PYTHONINSPECT=x
-I     : isolate Python from the user's environment (implies -E and -s)
-m mod : run library module as a script (terminates option list)
-O     : optimize generated bytecode slightly; also PYTHONOPTIMIZE=x
-OO    : remove doc-strings in addition to the -O optimizations
-q     : don't print version and copyright messages on interactive startup
-s     : don't add user site directory to sys.path; also PYTHONNOUSERSITE
-S     : don't imply 'import site' on initialization
-u     : unbuffered binary stdout and stderr, stdin always buffered;
         also PYTHONUNBUFFERED=x
         see man page for details on internal buffering relating to '-u'
-v     : verbose (trace import statements); also PYTHONVERBOSE=x
         can be supplied multiple times to increase verbosity
-V     : print the Python version number and exit (also --version)
         when given twice, print more information about the build
-W arg : warning control; arg is action:message:category:module:lineno
         also PYTHONWARNINGS=arg
-x     : skip first line of source, allowing use of non-Unix forms of #!cmd
-X opt : set implementation-specific option
file   : program read from script file
-      : program read from stdin (default; interactive mode if a tty)
arg ...: arguments passed to program in sys.argv[1:]

Other environment variables:
PYTHONSTARTUP: file executed on interactive startup (no default)
PYTHONPATH   : ';'-separated list of directories prefixed to the
               default module search path.  The result is sys.path.
PYTHONHOME   : alternate <prefix> directory (or <prefix>;<exec_prefix>).
               The default module search path uses <prefix>\lib.
PYTHONCASEOK : ignore case in 'import' statements (Windows).
PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.
PYTHONFAULTHANDLER: dump the Python traceback on fatal errors.
PYTHONHASHSEED: if this variable is set to 'random', a random value is used
   to seed the hashes of str, bytes and datetime objects.  It can also be
   set to an integer in the range [0,4294967295] to get hash values with a
   predictable seed.
PYTHONMALLOC: set the Python memory allocators and/or install debug hooks
   on Python memory allocators. Use PYTHONMALLOC=debug to install debug
   hooks.
PS C:\Users\eseyffarth>

Interessant für uns sind vor allem die Argumente file und arg. file verwenden wir immer, um den Pythoncode aus unseren Dateien auszuführen. arg können wir benutzen, um einem Pythonprogramm zusätzliche Eingabeinformationen zu geben.

Das folgende Programm ist fast so simpel wie unser erstes Pythonprogramm, in dem wir "Hallo Welt" ausgegeben haben. Hier soll aber der Name des Users ausgegeben werden. Der User gibt den Namen beim Aufruf des Programms als zusätzliches Argument an. Der Code sieht folgendermaßen aus:

In [ ]:
import sys     # Wir importieren das Paket sys,
               # das uns Zugriff auf die Argumente gibt.
    
print(sys.argv)  # In der Liste sys.argv sind alle Eingabeargumente gespeichert.

name = sys.argv[-1]  # Das letzte Argument entspricht dem letzten Argument,
                     # das in der Kommandozeile eingegeben wurde.
    
print("Hallo {}!".format(name))

Bitte nicht wundern - dieser Code macht in Jupyter nicht das, was wir wollen. Das liegt daran, dass Jupyter keine Kommandozeilen-Umgebung ist. Speichern Sie den Code aus dem Kästchen auf Ihrem Computer als Datei und rufen Sie diese Datei dann von der Kommandozeile aus auf:

[1]   C:\Users\eseyffarth> cd D:\philcloud\ws18-python\
[2]   D:\philcloud\ws18-python> python inputtest.py Esther
[3]   ['inputtest.py', 'Esther']
[4]   Hallo Esther!
[5]   D:\philcloud\ws18-python>

In Zeile [1] wechseln wir in das Verzeichnis, in dem die Pythondatei liegt.

Zeile [2] enthält den Aufruf unserer Datei mit einem zusätzlichen Argument, z.B. Esther.

In Zeile [3] werden alle Argumente des Pythonaufrufs als Liste ausgegeben. Der Dateiname ist dabei selbst auch ein Argument.

In Zeile [4] wird der print-Aufruf aus dem Programm ausgeführt, und zwar mit dem angegebenen Namen anstelle des Platzhalters im Formatstring.

Zeile [5] zeigt uns, dass das Pythonprogramm beendet wurde und die Kommandozeile bereit ist für die nächste Eingabe.

Warum Kommandozeilenargumente verwenden?

Bisher haben wir in den Übungen Programme geschrieben, die genau eine Aufgabe erledigt haben. Ab und zu kamen dabei Nutzereingaben vor, z.B. wenn beliebige lateinische Wörter dekliniert werden sollten oder wenn Dateipfade angegeben werden mussten.

Im Kontext dieses Kurses war es kein Problem, die Programme interaktiv auszuführen: Wir haben das Programm gestartet, an der passenden Stelle die Eingabe getippt und das Programm lief weiter.

Mithilfe von Kommandozeilenargumenten können wir die gleiche Funktionalität erreichen, trennen dabei aber die Nutzereingaben vom eigentlichen Programmablauf. So kann z.B. ein Programm auf dem Server gestartet werden, von einem anderen Skript aufgerufen werden oder für nachts um 3 geplant werden, ohne dass Sie danebensitzen und Texte eingeben müssen.

Wenn Sie sich dafür interessieren, können Sie z.B. die Aufgabe 08-01 umschreiben. Dort haben wir lateinische Wörter dekliniert, die während der Laufzeit eingegeben wurden.

Ändern Sie das Programm so, dass das Eingabewort aus der Kommandozeile gelesen wird. Der Aufruf des Programms sieht dann so aus:

D:\python> python 08-01_String-Formatierung.py amica

Graphische Programminterfaces (GUIs)

Wir haben bisher immer nur im Editor und der Kommandozeile programmiert. Wenn wir eine Anwendung schreiben, die auch für End-User gedacht ist, ist es eine gute Idee, eine graphische Darstellung des Programms zu bauen.

Wir unterscheiden zwischen Frontend ("das, was man sieht") und Backend ("das, was im Hintergrund passiert"). Als Grundlage für Ihr Backend können Sie jedes Pythonmodul verwenden, dessen Funktionalität in Funktionen verpackt ist.

Das Frontend können Sie mit verschiedenen Frameworks umsetzen. Ein Paket, das bei der conda-Installation von Python direkt dabei ist, ist tkinter.

Der folgende Code erzeugt beim Ausführen ein Fenster mit einem Titel, einer definierten Höhe und Breite, sowie einem Knopf mit der Beschriftung "Fenster schließen". Wenn der Knopf gedrückt wird, wird das Fenster geschlossen und das Programm beendet.

Der Code ist diesem Online-Tutorial entnommen, das Schritt für Schritt die Elemente herleitet, die wir in tkinter verwenden können.

Die Zeile, die den Button erstellt, enthält auch die Information, was beim Klicken des Buttons passieren soll: command=self.client_exit. An dieser Stelle können Sie auch selbstdefinierte Funktionen angeben, die dann beim Klick ausgeführt werden. Natürlich müssen Sie sich dann Gedanken darüber machen, wie die Ergebnisse der Funktion auf geeignete Weise dargestellt werden können.

Hier das grundlegende Programm:

In [ ]:
# Quelle: https://pythonprogramming.net/python-3-tkinter-basics-tutorial/
# ACHTUNG: Beim Klick auf "Fenster schließen" wird der
# Interpreter beendet, der hier im Jupyter Notebook 
# enthalten ist. Kopieren Sie den Code in eine Datei
# und führen Sie ihn in VSCode oder von der Kommandozeile
# aus.
from tkinter import *


# Here, we are creating our class, Window, and inheriting from the Frame
# class. Frame is a class from the tkinter module. (see Lib/tkinter/__init__)
class Window(Frame):

    # Define settings upon initialization. Here you can specify
    def __init__(self, master=None):
        
        # parameters that you want to send through the Frame class. 
        Frame.__init__(self, master)   

        #reference to the master widget, which is the tk window                 
        self.master = master

        #with that, we want to then run init_window, which doesn't yet exist
        self.init_window()

    #Creation of init_window
    def init_window(self):

        # changing the title of our master widget      
        self.master.title("Unser erstes GUI-Programm")

        # allowing the widget to take the full space of the root window
        self.pack(fill=BOTH, expand=1)

        # creating a button instance
        quitButton = Button(self, text="Fenster schließen",command=self.client_exit)

        # placing the button on my window
        quitButton.place(x=0, y=0)

       

    def client_exit(self):
        exit()

# root window created. Here, that would be the only window, but
# you can later have windows within windows.
root = Tk()

root.geometry("400x300")

#creation of an instance
app = Window(root)

#mainloop 
root.mainloop()  

Threading (mehrere parallele Prozesse ausführen)

Unsere bisherigen Pythonprogramme liefen linear ab: Der Interpreter führte eine Zeile nach der anderen aus, manchmal als Schleifen, bis das Programm beendet wurde.

Mithilfe von Threading können wir den Interpreter mehrere Dinge parallel ausführen lassen. Ein Tutorial dazu finden Sie hier.

Threading ist vor allem dann nützlich, wenn große Datenmengen verarbeitet werden, also beispielsweise im Kontext von Machine Learning.

Weitere nützliche Pakete

  • spaCy und textblob erfüllen ähnliche Aufgaben wie NLTK. Dabei ist spaCy für einige Dinge besser geeignet als NLTK, dafür aber auch für Anfänger_innen möglicherweise schwieriger zu installieren und zu nutzen. textblob ist weniger umfangreich als die beiden anderen Pakete.
  • requests haben wir am 22.01.2019 erwähnt, als eine Datei aus dem Internet geladen wurde. Grundsätzlich kann dieses Paket verwendet werden, um URLs zu laden und ähnlich wie ein Browser zu agieren.
  • pronouncing ist ein Paket, das eine Schnittstelle zum Aussprachewörterbuch CMU Dictionary bereitstellt. Damit können Sie z.B. ermitteln, ob zwei englische Wörter sich reimen oder wieviele Silben eine englische Phrase hat.
  • numpy bietet eine Reihe mathematischer Funktionen, mit denen Sie wissenschaftlich arbeiten können, um z.B. Messdaten zu verarbeiten.

Zusammenfassung und Ausblick

Falls Sie Computerlinguistik studieren, wird Python Sie in den nächsten Semestern weiterhin begleiten. Auch einige Strategien und Tools, die wir behandelt haben, werden weiterhin relevant sein. Hier eine Auswahl an Dingen, die wir hier behandelt haben und die Ihnen demnächst wieder begegnen werden:

  • Rekursion ist hilfreich, wenn wir verschachtelte Strukturen verarbeiten. Syntaxbäume sind Beispiele für solche Strukturen und werden im Kurs "Einführung in die Syntax" (Modul L1, 2. Semester) behandelt.
  • Reguläre Ausdrücke sind ein Universalwerkzeug, das jede_r Computerlinguist_in kennen sollte. Sie werden im Kurs "Einführung in die Computerlinguistik" (Modul CL1, 2. Semester) ausführlicher behandelt.
  • Schleifen, bedingte Ausführung und Funktionen gibt es in vielen Programmiersprachen. Ihre Fähigkeiten, Programme zu strukturiert zu planen und umzusetzen, wird in jedem weiteren Programmierkurs hilfreich sein.
  • N-Gramme auf Zeichenbasis haben wir in der 14. Woche oberflächlich besprochen. Was man mit N-Grammen alles machen kann und wie die statistischen Hintergründe davon genau aussehen, wird im Kurs "Einführung in die Computerlinguistik" behandelt.

Wir freuen uns, dass Sie so erfolgreich an diesem Kurs teilgenommen haben, und wünschen Ihnen alles Gute für Ihr weiteres Studium!