{ "cells": [ { "cell_type": "markdown", "metadata": { "tags": [] }, "source": [ "# Einführung in Python\n", "\n", "## Einordnung\n", "\n", "Python hat eine andere Designphilosophie als C, C++, Perl, Lisp, Pascal, aber viele Einflüsse aus früheren Sprachen. Durch einen aktiven Community-Prozess entwickelt sich die Sprache immer weiter.\n", "\n", "Python ist konzipiert als Sprache, die besonders gut lesbar ist, und deren Syntax es Programmierer\\*innen erlaubt, Konzepte mit weniger Codezeilen auszudrücken. Klar: Python ist eine high-level-Programmiersprache, weiter weg von konkreten Maschinenmodellen als C.\n", "Ähnlich wie Java verwendet Python eine Garbage Collection, sodass man nicht explizit Speicher reservieren und freigeben muss, um Variablen zu belegen.\n", "\n", "Mit Python lassen sich funktionale Programmierparadigmen verwenden wie in R oder Haskell, aber auch objektorientierte Paradigmen wie in C# oder Java.\n", "Anders als diese Sprachen ist Python zwar auch statisch getypt, aber dynamisch gebunden. So kann ein Variablenname einmal einen String zugewiesen bekommen, direkt danach einen Boolean und danach einen Integer. Wer striktere Typen bevorzugt, kann mittlerweile mit type annotations und speziellen Lintern arbeiten.\n", "\n", "Es gibt zwei klassische Arten, Python zu verwenden.\n", "Mit einer REPL (read-eval-print loop), ähnlich einer Shell in der Kommandozeile, lässt sich schnell prüfen, ob kurze Codeschnipsel syntaktisch korrekt sind und sich so verhalten, wie man erwartet.\n", "Jede\\*r Anwender\\*in von Python sollte sich mit dem Interpreter (also der REPL) vertraut machen.\n", "\n", "Durch kompilieren eines Programms (.py) zu Python Bytecode (.pyc) und ausführen des Bytecodes durch Python lassen sich komplexere Aufgaben lösen. Aus einer Datei heraus lassen sich andere als Module importieren.\n", "\n", "Ursprünglich nutzte man zum Hinzufügen von Paketen (so heißen libraries bzw. Bibliotheken bei Python) zu einer Python-Umgebung das Programm pip (Pip Installs Packages). Dieses greift z.B. auf den PyPI (Python Package Index) zurück (analog zum CPAN für Perl, CRAN für R, CTAN für TeX). Erst, wenn man ein Paket heruntergeladen und installiert hat, kann man es in seinen Code importieren und verwenden.\n", "\n", "Inzwischen wird im Bereich Data Science meist der Package Manager conda eingesetzt (der auch für R Umgebungen verwendet werden kann). Conda kann Abhängigkeiten zwischen Paketversionen besser verwalten als Pip.\n", "\n", "Python wird für den Data Science und Machine Learning Bereich auch in Distributionen angeboten, also gebündelt mit einigen ausgewählten Paketen. Für die Übungsaufgaben verwenden wir später die Distribution Anaconda mit Jupyter Notebooks.\n", "\n", "Ein (Jupyter/IPython) Notebook ist ein Dokument, welches aus mehreren Zellen besteht. Jede Zelle kann entweder Text enthalten, Mathematik (LaTeX), oder auch Python-Code, der dann direkt ausgeführt wird (wie eine Browser-gestützte REPL).\n", "Implementiert sind diese Notebooks (.ipynb) als JSON-Dokumente.\n", "\n", "Dieses Buch ist auch eine Sammlung von Notebooks, die von Jupyter-Books zu einem zusammenhängenden Vorlesungsskript kompiliert werden." ] }, { "cell_type": "markdown", "metadata": { "jp-MarkdownHeadingCollapsed": true, "tags": [] }, "source": [ "## Weiterführende Literatur\n", "\n", "Natürlich gibt es zu Python mittlerweile viel Material. Da die Sprache sehr lebendig ist, lohnt es sich, darauf zu achten, dass man mit python3 arbeitet. Die aktuellste Version, während diese Zeilen geschrieben werden, ist Python 3.10. Dieses Skript wurde größtenteils mit Python 3.8 erstellt.\n", "\n", "Eine sehr nützliche Resource ist\n", "[PEP 8, der Python Style Guide](http://www.python.org/peps/pep-0008.html),\n", "in dem die üblichen Code Konventionen festgehalten sind.\n", "Da Whitespace (Einrückung insb.) bei Python syntaktisch relevant ist, lohnt es sich, den Style Guide einmal zu überfliegen.\n", "\n", "PEP steht für [Python Enhancement Proposal](https://www.python.org/dev/peps/), das ist der Community-Prozess zur Weiterentwicklung der Sprache.\n", "\n", "Die [offizielle Dokumentation der Sprache](https://docs.python.org/3.8/) ist ausführlich und sehr nützlich. So kann man für eine bestimmte Aufgabe oft im [Python Modulindex](https://docs.python.org/3.8/py-modindex.html) ein mitgeliefertes Modul finden, dass diese Aufgabe erfüllen kann.\n", "\n", "Für erfahrene Programmierer\\*innen wurde das frei verfügbare [Dive Into Python](https://diveintopython3.net/) geschrieben. Sehr ausführlich über Installation, Konfiguration, IDEs, Coding Styles und Common Gotchas kann man im frei verfügbaren [Hitchhiker's Guide to Python](https://docs.python-guide.org/) nachlesen. Eine freundliche langsame Einführung in Python für Wissenschaftler\\*innen gibt es mit der [Whirlwind Tour of Python](https://jakevdp.github.io/WhirlwindTourOfPython/) (auch wenn man das PDF unter CC0-Lizenz des bei O'Reilly erschienenen Buchs mittlerweile etwas suchen muss).\n", "\n", "Auf Deutsch ist unter anderem der [Python-Kurs von Bernd Klein](https://www.python-kurs.eu/python3_kurs.php) gut und aktuell.\n", "\n", "Wer gern etwas über die skurrileren Seiten (auch der Implementierung) lernen möchte, ist mit [WTF Python](https://github.com/satwikkansal/wtfpython) gut bedient." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Wichtigstes\n", "\n", "Um die Dokumentation und den Code von Python-Modulen schnell zu verstehen, muss man syntaktische Eigenarten und Konzepte von Python kennen. Hier sind einige der wichtigsten zusammengestellt.\n", "\n", "### Datentypen\n", "\n", "**Zahlen** werden entweder als Integer (int) oder als Floating Point Number (float) repräsentiert, und es gibt sogar komplexe Zahlen (complex)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "23 ist <class 'int'> und 23.0 ist <class 'float'>\n", "und 23+2j ist <class 'complex'>\n", "Addieren wir int und floats gibt's <class 'float'>\n", "Division von Ganzzahlen ergibt float, also ist 5/2 = 2.5\n", "Wenn wir das nicht wollen, können wir rechnen 5//2 = 2\n" ] } ], "source": [ "print(\"23 ist\", type(23), \"und 23.0 ist\", type(23.0))\n", "print(\"und 23+2j ist\", type(23+2j))\n", "print(\"Addieren wir int und floats gibt's\", type(23+23.0))\n", "print(\"Division von Ganzzahlen ergibt float, also ist 5/2 =\", 5/2)\n", "print(\"Wenn wir das nicht wollen, können wir rechnen 5//2 =\", 5//2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Strings** (Zeichenketten) sind grundsätzlich Unicode, wobei auf UTF-8 zurückgegriffen wird, wenn nichts anderes spezifiziert wird. Das bedeutet heutzutage im Alltag meist, dass die Quelltexte auch mit Umlauten und Akzentzeichen klarkommen, aber bei relativ alten Dateien (gerade aus der Python2-Ära) evtl. das Encoding geändert bzw. spezifiziert werden muss. Uns wird das nicht beschäftigen.\n", "\n", "**Dictionaries** sind Abbildungen mit endlichem Definitionsbereich, man notiert z.B. den mathematischen Sachverhalt $f \\colon \\{a,b\\} \\to \\mathbb{R},\\ a \\mapsto 1,\\ b \\mapsto 2$ und $f(a) = 1$ als" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Syntaktischer Zucker erlaubt es gelegentlich, auf Anführungszeichen zu verzichten.\n", "Alles wie geplant\n", "{'a': 1, 'b': 2} is a <class 'dict'>\n", "Man muss aufpassen, wo eckige und wo runde Klammern hinkommen\n" ] } ], "source": [ "f = {\"a\":1, \"b\":2}\n", "if(f == dict(a=1, b=2)):\n", " print(\"Syntaktischer Zucker erlaubt es gelegentlich, auf Anführungszeichen zu verzichten.\")\n", "if(f[\"a\"] == 1):\n", " print(\"Alles wie geplant\")\n", "print(f, \"is a\", type(f))\n", "try:\n", " print(f[\"c\"])\n", " print(\"an diese Stelle kommt der Interpreter nie\")\n", "except KeyError:\n", " f[\"c\"] = \"Man muss aufpassen, wo eckige und wo runde Klammern hinkommen\"\n", "finally:\n", " print(f[\"c\"]) # aber jetzt!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Tupel** sind längenfixierte unveränderliche Listen, also z.B. Paare oder Tripel. Achtung: die Addition ist nicht elementweise, sondern eine Listenkonkatenation, die ein neues (vom alten verschiedenes) Tupel erzeugt:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(2, 3) is a <class 'tuple'> of length 2\n", "(2, 3) + (4, 5, 6) = (2, 3, 4, 5, 6)\n", "(0, 1) * 3 = (0, 1, 0, 1, 0, 1)\n" ] } ], "source": [ "paar = (2,3)\n", "print(paar, \"is a\", type(paar), \"of length\", len(paar))\n", "tripel = (4,5,6)\n", "print(paar, \"+\", tripel, \"=\", paar+tripel)\n", "print((0,1), \"* 3 =\", (0,1)*3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Mengen** sind ungeordnete Listen, auch die notiert man mit geschweiften Klammern:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{1, 2, 3}\n" ] }, { "data": { "text/plain": [ "True" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Menge = {3,2,1}\n", "print(Menge)\n", "Nochnemenge = set((2,3,1))\n", "Menge == Nochnemenge" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Listen** sind geordnet und können verlängert werden. Unter der Haube verwendet Python je nach Länge und Implementierung des jeweiligen Interpreters oder Compilers dann einen Array mit fester Länge (der dann notfalls getauscht wird, wenn die Liste wächst) oder eine linked List oder so. Als Anwender merkt man davon nichts und es ist für Performancefragen in der Regel irrelevant. Eine list comprehension ist eine Art, über eine Liste zu iterieren um eine weitere zu erstellen." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[3, 1, 4, 1, 5, 9] vom Typ <class 'list'> hat Länge 6\n", "[3, 1, 4, 1, 5, 9, -1, -1, -1]\n", "[3, 1, 4, 1, 5, 9, 0]\n", "[1, 2, 3]\n", "[5, 3, 6, 3, 7, 11, 2]\n" ] } ], "source": [ "liste = [3,1,4,1,5,9]\n", "if(5 in liste):\n", " print(liste, \"vom Typ\", type(liste), \"hat Länge\", len(liste))\n", "print(liste + [-1,-1,-1])\n", "liste += [0]\n", "print(liste)\n", "tripel = (1,2,3)\n", "print(list(tripel))\n", "# hier kommt die list comprehension:\n", "neue = [x+2 for x in liste]\n", "print(neue)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Slicing** nennt man es, wenn man einen Teil einer Liste in Python auswählt mit der Doppelpunkt-Syntax." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "range(0, 10) <class 'range'>\n", "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] <class 'list'>\n", "[1, 2, 3, 4, 5, 6, 7, 8]\n", "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", "es geht auch negativ: [9]\n", "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n" ] } ], "source": [ "zahlen = range(0,10) # Achtung, geht mit 0 los, 10 ist nicht dabei.\n", "print(zahlen, type(zahlen))\n", "zahlen = list(zahlen)\n", "print(zahlen, type(zahlen))\n", "print(zahlen[1:9]) # hier ist auch die 9 nicht dabei.\n", "print(zahlen[:5] + zahlen[5:])\n", "print(\"es geht auch negativ:\", zahlen[-1:])\n", "print(zahlen[:-1] + zahlen[-1:])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Generatoren** sind Objekte, die Listen in einer Hinsicht verallgemeinern: man kann sich das nächste Element geben lassen. Es muss aber keine ganze Liste im Speicher gehalten werden (und auch nicht im voraus berechnet werden), daher sind Generator-Ausdrücke an vielen Stellen die richtige Wahl für Datenverarbeitungs-Pipelines innerhalb Python.\n", "Man kann jede Liste und jedes Tupel als Generator verwenden und auch die range-Objekte sind Generatoren." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<generator object <genexpr> at 0x7f12b85e4660> hat den Typ <class 'generator'>\n", "2\n", "3\n", "4\n", "5\n", "6\n" ] } ], "source": [ "gen = (x + 2 for x in range(0,5))\n", "print(gen, \"hat den Typ\", type(gen))\n", "# und erst jetzt wird die Berechnung ausgeführt:\n", "for x in gen:\n", " print(x)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Gleichheit und Identität\n", "\n", "*Wertgleichheit* wird mit dem Operator `==` geprüft,\n", "*Referenzgleichheit* mit dem Schlüsselwort `is`,\n", "das entspricht einer Wertgleichheit der `id`s." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n", "False\n", "False\n", "False\n", "True\n" ] } ], "source": [ "print(123 == 121 + 2)\n", "a = [1,2,3]\n", "b = a[:] # Kopie\n", "print(id(a) == id(b))\n", "print(a is b)\n", "print(a is [1,2,3])\n", "print(a == b)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Typenzugehörigkeit* prüfen wir mit der Methode `isinstance`." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "True\n", "False\n", "<class 'int'>\n", "True\n" ] } ], "source": [ "print(isinstance(\"a\", str))\n", "print(isinstance(123, bool))\n", "print(type(123))\n", "print(isinstance(123, object))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Noch schwächer als `==` ist ein Vergleich der darstellenden Strings (das ist etwas anderes als eine Typenkonversion zum Typ `str`). Dabei soll `repr()` einen möglichst eindeutigen String liefern, während `str()` einen möglichst lesbaren String liefern soll. Oft lässt sich der `repr`-String als Python-Code evaluieren mit `eval`, der eine Kopie des Objekts erzeugt." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "echt verschiedene dict Objekte haben verschiedene Repräsentationen:\n", "( {'key1': 1, 'key2': 2} == {'key1': '1', 'key2': '2'} ) = False\n", "aber bei dict Objekten ist str() das gleiche wie repr()\n", "( {'key1': 1, 'key2': 2} == {'key1': 1, 'key2': 2} ) = True\n", "{'key1': 1, 'key2': 2}\n", "True\n", "False\n", "3 != '3'\n" ] } ], "source": [ "a = dict(key1=1, key2=2)\n", "b = {\"key1\":'1', \"key2\":'2'}\n", "print(\"echt verschiedene dict Objekte haben verschiedene Repräsentationen:\")\n", "print(\"(\", a, \"==\", b, \") =\", a == b)\n", "print(\"aber bei dict Objekten ist str() das gleiche wie repr()\")\n", "print(\"(\", repr(a), \"==\", str(a), \") = \", repr(a) == str(a))\n", "\n", "c = eval(repr(a))\n", "print(c)\n", "\n", "print(str(3) == str(\"3\"))\n", "print(repr(3) == repr(\"3\"))\n", "print(repr(3), \"!=\", repr(\"3\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "<!--\n", "### Funktionale Programmierung\n", "\n", "\n", "### map, filter, reduce, zip\n", "\n", "### lambda\n", "\n", "### \n", "-->\n", "\n", "\n", "### Objektorientierte Programmierung" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "test 123\n", "<class '__main__.meineKlasse'>\n" ] }, { "data": { "text/plain": [ "True" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class meineKlasse():\n", " def meineMethode(self, methodisch):\n", " print(methodisch, self)\n", " \n", " def __init__(self):\n", " self.attribut = 123\n", " \n", " def __repr__(self):\n", " return str(self.attribut)\n", " \n", "x = meineKlasse()\n", "x.meineMethode(\"test\")\n", "print(type(x))\n", "isinstance(x, object)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Der Polymorphismus geht so weit, dass man oft nicht wissen muss, ob eine Variable nun auf eine Klasse oder eine Methode verweist - wenn es `Callable` ist, kann man `()` dahinter hängen und bekommt etwas.\n", "\n", "\n", "### Privat und Öffentlich\n", "\n", "Es gibt (im Gegensatz zu z.B. Java) in Python keine Möglichkeit, etwas zu verstecken (private, protected, etc.) außer durch Konvention: wenn ein Objektattribut oder eine Methode mit einem einzelnen Underscore vorangestellt gekennzeichnet ist, soll sie als \"privat\" betrachtet werden.\n", "\n", "### Dictionaries und Tupel sind überall\n", "\n", "Tupel lassen sich auspacken:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(1, 2, 3) 1 2 3\n" ] } ], "source": [ "t = (1, 2, 3)\n", "a, b, c = t\n", "print(t, a, b, c)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In einer Methodendeklaration gibt es die Möglichkeit, beliebig viele Positionsargumente zuzulassen:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(1, 2, 3, 'vier')\n", "(2, 3, 'vier')\n" ] } ], "source": [ "def methode(*args):\n", " return args\n", "\n", "methode()\n", "print(methode(1,2,3,\"vier\"))\n", "\n", "def neuemethode(argmusssein, *args):\n", " return args\n", "\n", "print(neuemethode(1,2,3,\"vier\"))" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "neuemethode() missing 1 required positional argument: 'argmusssein'\n" ] } ], "source": [ "try:\n", " neuemethode()\n", "except TypeError as e:\n", " print(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ebenso kann man mit Schlüsselwortargumenten umgehen:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'key': 'value'}\n", "methode() takes 0 positional arguments but 1 was given\n" ] } ], "source": [ "def methode(**kwargs):\n", " return kwargs\n", "\n", "methode()\n", "print(methode(key=\"value\"))\n", "\n", "try:\n", " methode(\"123\")\n", "except TypeError as e:\n", " print(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Insgesamt kann man eine Folge von zwingenden Positionsargumenten, eine Folge von spezifizierten Schlüsselwortargumenten und beliebiege weitere zulassen:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "((3, 4, 5), {'kwarg3': 'test'})" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def methode(posarg, posarg2, *args, kwarg=None, kwarg2=True, **kwargs):\n", " return args, kwargs\n", "\n", "#methode() # <--- geht nicht\n", "\n", "methode(1, 2, 3, 4, 5, kwarg2=False, kwarg3=\"test\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Um direkt ein ganzes `dict`-Objekt als Schlüsselwortargumente zu übergeben, kann man dies entpacken (und genau so Tupel):" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "((3, 4, 5), {'kwarg3': 'test'})\n", "(1, 2, 3, 4, 5)\n", "1 2 3 4 5\n" ] } ], "source": [ "args = (1, 2, 3, 4, 5)\n", "kwargs = {\"kwarg2\": False, \"kwarg3\": \"test\"}\n", "print(methode(*args, **kwargs))\n", "\n", "print(args)\n", "print(*args)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Auch Objektattribute werden in einem `dict` verwaltet:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'x': 1, 'y': 2}\n" ] }, { "data": { "text/plain": [ "True" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class neueKlasse():\n", " def __init__(self):\n", " self.x = 1\n", " self.y = 2\n", " \n", "neuesObjekt = neueKlasse()\n", "print(neuesObjekt.__dict__)\n", "neuesObjekt.__dict__[\"z\"] = 3\n", "neuesObjekt.z == 3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "[Link zum Comic (Randall Munroe, CC-BY-NC 2.5)](https://xkcd.com/353)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.2" } }, "nbformat": 4, "nbformat_minor": 4 }