Künstliche Intelligenz: Python erlernen


Einführung

Python ist eine universelle, üblicherweise interpretierte höhere Programmiersprache. Sie ist einfach zu erlernen dank einer klaren und übersichtlichen Syntax. Python unterstützt mehrere Elemente moderener Programmiersprachen wie die Objektorientierung, die Aspektorientierung und die funktionale Programmierung. Entwickelt wurde die Sprache in den 1990er Jahren von Guido van Rossum. Der Name Python wurde in Anspielung an die Komikergruppe Monthy Python gewählt und nicht wie annehmbar abgeleitet aus einem Schlangenamen. Die aktuelle Version ist Python 3 (3.7). Frühere Versionen von Python sind nicht mehr kompatibel mit der aktuellen Version (Python 2).

Python überzeugt durch eine mächtige Standardbiliothek und eine hohe Anzahl von Fremdbibliotheken, die sehr viele Aspekte von alltäglichen Programmieraufgaben abdecken. Der überwiegende Teil davon ist plattformunabhängig, so dass auch grössere Python-Programme ohne Änderungen auf Linux, Windows und Mac laufen.

Python ist eine sehr gängige Programmiersprache zur Entwicklung von KI-Algorithmen. Jeder, der in das Themengebiet Deep Learning und Neuronale Netze einsteigen möchte, sollte in Python programmieren können. 

Objektorientierung als Einstieg

Wie gesagt ist Objektorientierung ein wesentlicher Aspekt von Python. Dieses Tutorial setzt deshalb voraus, dass Sie schon mit einer höheren Programmiersprache Kontakt hatten und wissen was Objekte sind, inklusive der Eigenschaften Vererbung, Polymorphismus und Datenkapselung. In diesem Tutorial ist das der Startpunkt, um eigene Python Programme zu entwerfen. Der Trail in diesem Tutorial ist, zuerst die Sprache anhand von objektorientierten Konzepten in den Griff zu kriegen und dann, funktionale als auch aspektorientierte Elemente zu erlernen. 

Inhalt

Die IDE

Im Prinzip braucht es für die Entwicklung von Python Programmen nur ein Texteditor und die Installation von Python 3. Es ist aber zweckmässig, für die Entwicklung von grösseren Programmen eine IDE (Integrated Development Environment) zu verwenden. Eine für Python spezell entwickelte Umgebung ist PyCharm. PyCharm gibt es als kostenlose Community Edition und als kommerzielle Version. Für das Lernen von Python ist aber die CE mehr als genügend.

Unter Ubuntu (aktuelle Version 19.04), kann die aktuelle Version von PyCharm mit dem Ubuntu Software Packager installiert werden. Ohne sich überhaupt gross mit apt herumzuschlagen, werden die notwendigen Abhängigkeiten gleich mit installiert.
  
PyCharm CE kann direkt über den Ubuntu Software Packager installiert werden

Viele Kommentare loben diese IDE

Übersicht über PyCharm CE

Nach der Installation müssen beim erstmaligem Start einige Dialoge bestätigt werden. So kann zu Beispiel das UI-Thema ausgewählt werden:

Auswahl des UI Themas

Nach der Grundkonfiguration (die am Anfang ruhig übersprungen werden kann), öffnet sich PyCharm
 Im obigen Dialog wird die Option "Create New Project" ausgewählt.

Ein Projekt mit dem Namen "FirstProject" erzeugen


Der "Project Explorer" in der Detailansicht

Für die Erzeugung von Python Code muss mit "New" ein "Python File" erzeugt werden:

Erzeugen eines Python-Files


 Das Python-File in der Projektstruktur:

Unten sichtbar, die neu erzeugte Datei main.py

Das obligatorische "Hello World

Klassen werden in Python über das Schlüsselwort class erzeugt. Der Konstruktor der Klasse heisst in Python __init__. Self ist immer gerade eine Referenz der Klasseninstanz auf sich selber. Das Wort def muss vor dem Konstruktor und jeder Methode stehen:


class HalloWelt:
    def __init__(self, text, name):
        self.text = text
        self.name = name

    def print(self):
        print(self.text + " " + self.name)


hallo = HalloWelt("Hallo", "Leopold")
hallo.print()

Ganz unten wird die Klasse HalloWelt instanziiert. Den beiden Variablen "text" und "Name" werden die Werte "Hallo" und "Leopold" zugewiesen. Am Schluss wird die Methode "print" aufgerufen. Variablen, die wie oben im Code definiert wurden, sind Instanzvariablen. Ihre Lebenszeit ist auf die Lebensdauer der Instanz beschränkt. Statische Klassenvariablen können folgendermassen erzeugt werden:


class Counter:
    counter = 0
    __privatecounter = 0

    def __init__(self):
        Counter.counter = Counter.counter + 1
        Counter.__privatecounter = Counter.__privatecounter + 1

    def print(self):
        print(Counter.counter)
        print (Counter.__privatecounter)


counter1 = Counter()
counter1.print()
counter2  = Counter()
counter2.print()
print(Counter.counter)
# print(Counter.__privatecounter) --> dies würde eine Exception auslösen, weil der Zugriff private ist

Statische Methoden werden auch durch eine Annotation unterstützt:


class StaticMethod:

    @staticmethod
    def hallo():
        print("Hallo")


StaticMethod.hallo()

Zugriffsrechte in der Klasse

Wie bei anderen objektorientierten Sprachen sind die Variablen bei Python innerhalb der Klasse gekapselt. Die Zugriffsrechte können ähnlich eingeschränkt werden wie bei C++ oder Java (private, protected, public). Allerdings ist zu beachten, dass es in Python die Möglichkeit gibt, Zugriffsrechte zu umgehen:


class Test:

    def __init__(self, public, private):
        self.public = public
        self.__private = private

    def print (self):
        print (self.public + " "+self.__private)


test = Test ("Hallo", "Welt")
test.print()

print(test.public)
print(test.__private)  #Wird Fehlermeldung ausgeben

Die Zugriffsrechte können beispielsweise folgendermassen umgangen werden:


print(test._Test__private)

Analog zu privaten Methoden in Java oder C++ können auch Methoden in Python privat gemacht werden:


class Quadrat:

    def __init__(self, seite):
        self.seite = seite

    def __berechne (self):
        return self.seite * self.seite

    def flaeche (self):
        print("Die Fläche beträgt: ", self.__berechne())


quadrat = Quadrat(12)
quadrat.flaeche()
# quadrat.__berechne() erzeugt Fehler

Vererbung

Zur Illustration dient folgendes Beispiel:

class Rechteck:

    def __init__(self, breite, laenge):
        self.breite = breite
        self.laenge = laenge

    def __berechne(self, was):
        if was == "flaeche":
            return self.breite * self.laenge
        if was == "umfang":
            return 2*self.breite + 2*self.laenge

    def flaeche(self):
        return self.__berechne("flaeche")

    def umfang(self):
        return self.__berechne("umfang")

    def print (self):
        print ("Die Fläche des Rechtecks beträgt: ", self.flaeche(), "Der Umfang des Rechtecks beträgt: ", self.umfang())


class Quadrat(Rechteck):

    def __init__(self, seite):
        super(Quadrat, self).__init__(seite, seite)

    def print (self):
        print ("Die Fläche des Quadrats beträgt: ", self.flaeche(), "Der Umfang des Quadrats beträgt: ", self.umfang())


rechteck = Rechteck(2, 5)
rechteck.print()

quadrat = Quadrat(4)
quadrat.print()

Nebst der Einfachvererbung ist es auch möglich, von mehreren Klassen zu erben, wie folgendes Beispiel zeigt:

class Farbe:

    def __init__(self, farbe):
        self.farbe = farbe


class Stil:

    def __init__(self, stil):
        self.stil = stil


class Hose(Farbe, Stil):

    def __init__(self, farbe, stil):
        Farbe.__init__(self, farbe)
        Stil.__init__(self, stil)

    def __str__(self):
        return "Die Hose ist %s und hat einen %s Stil"%(self.farbe, self.stil)


hose = Hose("blau", "modischen")
print(hose)

In diesem Beispiel wurde die Methode def __str__ definiert. Diese Methode ist der Outputstream, der die print-Methode entgegennimmt. Ist sie vorhanden, so wird der Return-Value dieser Methode genommen.

Operatoren redefinieren (overloading)

In Python ist es möglich Operatoren zu redefinieren. Die gängisten Operatoren sind sicher +,-,* und /. Jedoch gibt es noch jede Menge anderer Operatoren die redefiniert werden können:


Operator Expression Methodendefinition
Addition p1 + p2 def __add__(self, other)
Subtraktion p1 - p2 def __sub__(self, other)
Multiplikation p1 * p2 def __mul__(self, other)
Division p1 / p2 def __truediv__(self, other)
Potenz p1 ** p2 def __pow__(self, other)
Rest (Modulo) p1 % p2 def __mod__(self, other)
Bitweises Linksschieben p1 << p2 def __lshif__(self, other)
Bitweises Rechtsschieben p1 >> p2 def __rshift__(self, other)
Bitweises AND p1 & p2 def __and__(self, other)
Bitweises ODER p1 | p2 def __or__(self, other)
Bitweises XOR p1 ^ p2 def __xor__(self, other)
Bitweises NOT ~p1 def __invert__(self, other)
Floor Division p1 // p2 def __floordiv__(self, other)

Auch hierzu ein Beispiel:


class ListevonListen:

    def __init__(self, elemente):
        self.elemente = elemente

    def __add__(self, andereListe):
        resultat = [self.elemente, andereListe.elemente]
        return ListevonListen(resultat)

    def __str__(self):
        returnvalue = ""
        for item in self.elemente:
            returnvalue = returnvalue + str(item)
        return returnvalue

    def getListe(self, index):
        return self.elemente[index]


liste1 = ListevonListen ([1, 2, 3, 4])

liste2 = ListevonListen ([5, 6, 7])

liste3 = liste1 + liste2

print(liste3)   #ergibt [1, 2, 3, 4][5, 6, 7]
print(liste3.getListe(0))  #ergibt [1, 2, 3, 4]
print(liste3.getListe(1))

Methoden überladen

Eine Methode ist dann überladen, wenn sie mit unterschiedlicher Argumentenanzahl oder mit verschiedenen Typen von Argumenten aufgerufen werden kann. In Python gibt es nicht direkt eine Methodenüberladung, jedoch kann eine variable Anzahl von Argumenten übergeben werden:


class VarArgs:
    def __init__(self):
        pass

    @staticmethod
    def multipliziere (*args):
        result = 1
        for multiplikator in args:
            result = multiplikator * result
        return result


resultat = VarArgs.multipliziere(1, 2, 3, 4, 5, 6, 7, 8, 9)
print(resultat)

Loops (Schleifen)

Unten ist ein Beispiel gegeben wie die WHILE und die FOR Loops in Python verwendet werden:

class LoopTest:

    def __init__(self):
        pass

    def whileTest(self, iterationen):
        i = 0
        while i < iterationen:
            print(i)
            i += 1

    def forTest(self, iterationen):
        for i in range(iterationen):
            print(i)

    def forTest2(selfs, *args):
        for arg in args:
            print (arg)

    def forTest3(self, string):
        for str in string:
            print (str + " ")
        else:
            print ("Finished")

    def forTest4(self, maxNumber):
        minNumber = 5
        step = 5

        for number in range (minNumber, maxNumber,step):
            print (number)

    def forTest5(self):
        maxNumber = 11
        for number in range (0, maxNumber, 1):
            if number == 5:
                continue
            print(number)
            if number == 10:
                break

    def calculateSin(self, angle):
        sinx = 0
        for counter in range (12):
            sinx = sinx + (-1)**counter*angle**(2*counter+1)/self.fac(2*counter+1)
        print(sinx)

    def fac(self, number):
        if number > 0:
            return number * self.fac(number-1)
        return 1


loopTest = LoopTest()
loopTest.whileTest(10)      # 0,1,2,3,4,5,6,7,8,9
loopTest.forTest(10)        # 0,1,2,3,4,5,6,7,8,9
loopTest.forTest2("Guten", "Morgen", "Mathilde")  # Guten Morgen Mathilde
loopTest.forTest3("Parse mich")   #Parse michFinished
loopTest.forTest4(46)    #5, 10, 15, 20, 25, 30, 35, 4, 45
loopTest.forTest5()      #0. 1, 2, 3, 4, 6, 7, 8, 9, 10
loopTest.calculateSin(3.1415926536/4)   #0.7071067811883518
print(loopTest.fac(4))  #24

Conditions (Bedingungen)

Ohne bedingte Anweisungen läuft kaum ein Programm. Deshalb hier ein Beispiel für die Verwendung von IF und ELSE.


import random as r


class RateEineZahl:

    def __init__(self):
        self.computerZahl = int(r.random()*100+1)
        self.anzahlVersuche = 0

    def __zahleingabe (self):
        print ("Gebe Deine Zahl ein - sie muss zwischen 1 und 100 liegen:")

        self.DeineZahl = input()

    def __gameloop (self):
        while True:
            try:
                self.__zahleingabe()
                self.DeineZahl = int(self.DeineZahl)
                self.anzahlVersuche +=1
            except:
                print("Keine gültige Zahl!")
                continue

            if self.DeineZahl==self.computerZahl:
                print("Gut geraten, Deine Zahl entspricht meiner Zahl ", self.computerZahl)
                print("Du hast ", self.anzahlVersuche, " Versuche gebraucht")
                break
            elif self.DeineZahl < self.computerZahl:
                print ("Die Zahl ist kleiner als meine Zahl")
            else:
                print ("Die Zahl ist grösser als meine Zahl")

    def start (self):
        self.__gameloop()


zahleRaten = RateEineZahl()
zahleRaten.start()

Multithreading

Python unterstützt sowohl die leichtgewichtigen Threads als auch Parallelität auf Prozessebene. Zuerst werden die leichtgewichtigen Threads betrachtet. Für die Verwendung muss das Modul "threading" importiert werden. Die Methode "threading.Thread" erwartet eine Methodensignatur, wo dann der parallel auszuführende Code steht. Die Methodensignatur wird als Target bezeichnet.   


import threading as t


class PrimeFinder:

    def __init__(self):
        pass

    def worker1(self, zahl):

        nextdivisor = 2
        while True:
            if zahl % nextdivisor == 0:
                print("Keine Primezahl ist durch ", nextdivisor, " teilbar!")
                break
            if nextdivisor == int(zahl/2) + 1:
                print("Diese Zahl ist eine Primzahl")
                break
            nextdivisor += 1

    def worker2(self, zahl):
            primzahlen = [2]
            if zahl <= 1:
                print("")
                return
            if zahl <= 2:
                print(primzahlen)
                return
            nextZahl = 3
            nextDivisor = 2
            while True:

                if nextDivisor * nextDivisor > nextZahl:
                    primzahlen.append(nextZahl)
                    nextDivisor = 2
                    nextZahl += 1
                    continue

                if nextZahl % nextDivisor == 0:
                    nextDivisor = 2
                    nextZahl+=1
                    continue

                if nextZahl > zahl:
                    break

                nextDivisor += 1

            print(primzahlen)


primeFinder = PrimeFinder()
t2 = t.Thread(target=primeFinder.worker1(31))
t2.start()

t1 = t.Thread(target=primeFinder.worker2(31))
t1.start()
t1.join()
t2.join()


Im obigen Beispiel werden zwei Threads aufgespannt. Ähnlich wie bei Java werden die Threads mit "start" gestartet und mit "join" wird gewartet bis der Thread terminiert hat. Der "erste" Worker überprüft, ob eine Zahl eine Primzahl ist und der zweite Thread gibt alle Primzahlen innerhalb einer oberen Grenze an. Ein weiteres Beispiel, diene zur Suche nach Dateien im Dateisystem:


import threading as t
from pathlib import Path


class FileFinder:

    def __init__(self, fileName, rootDirectory):
        self.fileName = fileName
        self.rootDirectory = rootDirectory

    @staticmethod
    def __getdirectories (currentRoot):
        directories = []
        if type(currentRoot)==Path:
            path = currentRoot
        else:
            path = Path (currentRoot)

        for d in path.iterdir():
            if d.is_dir():
                directories.append(d)
        return directories

    def __findindirectory (self, directory):
        path = directory

        for f in path.iterdir():
            if f.is_file():
                if f.name == self.fileName:
                    return True

        return False

    def __walk(self, rootDirectory):
        root = Path(rootDirectory)

        result = self.__findindirectory(root)
        if result:
            print ("Habe Datei im Verzeichnis ", root, " gefunden")
            return
        if not result:
            for d in FileFinder.__getdirectories(root):
                wt = t.Thread(target=self.__walk(d))
                wt.start()

    def search(self):
        self.__walk(self.rootDirectory)


fileFinder = FileFinder("Hallo.txt", "/home")
fileFinder.search()








Dieses akademisch simple Beispiel hilft zwar, in den Grundzügen die Threads innerhalb von Python zu verstehen, aber gibt noch keine Auskunft darüber wie Threads in der Praxis anzuwenden sind. Von der Programmiersprache Java her, ist bekannt, dass Blöcke und Methoden mit dem Schlüsselwort "synchronized" vor gleichzeitigem Zugriff geschützt werden können. Die bekannten Monster "Deadlock", "Starvation" und "Race-Conditions" können so in den Griff gekriegt werden. Wie sieht das unter Python aus?


Keine Kommentare:

Kommentar veröffentlichen