Ich fange gerade an, Python zu lernen.
Hier finden sich meine Erfahrungen mit Python, und die Programme, die ich damit schreibe.
Ich versuche meine Texte allgemeinverständlich zu schreiben und meinen Code sauber zu kommentieren und hoffe, dass sie für dich damit nützlich sind.
Und wenn du schon python3 installiert hast, probier doch mal das hier:
python3 -c "import antigravity;print(antigravity.__doc__)"
...dazu hätte ich gerne docs ;)
Ich habe einen kleines Programm geschrieben, das einen interagierenden Schwarm von Blobs zeigt.
Jeder der Blobs sucht sich am Anfang einen Partner und nähert sich ihm.
Wenn die beiden Partner sich zu nahe kommen, flieht einer von ihnen und sucht sich einen neuen Partner, dem er sich wieder annähert, bis er ihm zu nahe kommt.
Durch diese brechenden Zweierbeziehungen bleiben die Blobs zusammen, das heißt es entsteht ein Schwarmverhalten, obwohl jeder Blob nur an seinen aktuellen oder nächsten Partner denkt.
Das ganze ist in Python und pyglet (GUI) implementiert. Die einzige Abhängigkeit ist Python 2.5 [1].
Wenn du es anschauen willst, lade dir einfach den Blob Schwarm [2] herunter, entpacke sie und klick in dem Ordner die Datei blob_swarm.pyw an.
Alles andere sollte von selbst gehen.
Viel Spaß!
Anhang | Größe |
---|---|
blob_swarm-0.2.tar.gz [3] | 556.85 KB |
blob_swarm-0.3.tar.gz [2] | 855.62 KB |
Ein kleines Geschenk, das ich meiner Frau zum (bzw. am) Valentinstag 2008 geschrieben habe.
In seiner Gänze kann es allerdings in diesem Screenshot nicht genossen werden. Um das ganze animiert zu haben, musst du schon das Programm runterladen :) (es braucht nur Python [4] (>=2.5) ).
-> Blob Valentine 0.3 herunterladen [5]
Anhang | Größe |
---|---|
blob_valentine-0.3.tar.gz [5] | 255 KB |
blob_valentine_screenshot-2008-02-15.png [6] | 209.4 KB |
Hier führe ich Links und Texte, die meiner Meinung nach (mMn) essenziell für Python-Programmierer sind.
Die Liste wird sich wohl langsam erweitern.
Allgemein ist der Link zu den python Tools im SVN wichtig. Er enthält so interessante Dinge wie pygettext.py (das ich vorher stundenlang gesucht habe)...
Angefangen mit Übersetzung (l10n) und Internationalisierung (i18n), vll. irgendwann auch Multinationalisierung (m17n).
Hier teste ich kurz, wie lange ein selbstgeschriebenes Programm zum summieren einer Liste von Zahlen braucht und vergleiche sie mit der mitgelieferten Funktion sum().
Vorsicht: Die Daten hier sind von 2008. → Nachtrag 2016
Die Funktionen, die ich nutze sind:
def summieren(liste): """Summiere alle Zahlen der Liste manuell.""" # erst brauchen wir einen Zähler, # auf den wir alle Zahlen aufsummieren. gesamt = 0 # Dann summieren wir # alle Zahlen der Liste zu dem Zähler. for x in liste: # Das heißt, wir erhöhen ihn # für jede Zahl in der Liste um die Zahl. gesamt += x # und geben ihn dann zurück. return gesamt
und
def summieren(liste): """Summiere alle Zahlen der Liste mit sum().""" return sum(liste)
Die benötigte Zeit teste ich, indem ich die Funtion in einem doctest jeweils 100001 mal ablaufen lasse und prüfe, wie lange python braucht, um das Skript zu auszuführen.
Die vollständigen Quelltexte habe ich an die Seite angehängt.
Die Aufrufe sind damit:
time python summieren_selbst.py # Selbstgeschriebener Code
time python summieren_sum.py # Eingebaute Funktion
Und ich erhalte die Ergebnisse:
$ time python summieren_selbst.py
real 0m7.536s
user 0m7.430s
sys 0m0.030s
$ time python summieren_sum.py
real 0m4.362s
user 0m4.260s
sys 0m0.070s
Damit braucht meine selbstgeschriebene Summierfunktion etwa 74% länger als die eingebaute.
Fazit: Ich konnte mir bestätigen, dass es besser ist, die eingebaute Funktion zu nutzen, statt sie selbst zu schreiben. Der eingebaute Code ist effizienter.
Nachtrag 2016: Wenn die Geschwindigkeit der Summe wirklich kritisch für dein Programm ist, dann lagere sie aus. In Fortran und C++ braucht ein Programm für die gleiche Aufgabe nur 10-20% der Laufzeit (Vorsicht: Zwischen diesem Text und dem Nachtrag lagen 8 Jahre Entwicklung. Der Python-Code braucht jetzt nur noch 0.2s, der angehängte Fortran-Code [13] braucht 0.02s und der C++-code 0.07s. PyPy führt den neuen angehängten Python-code [14] in 0.07s aus). Im angehängten C++-code [15] habe ich statt 100000-mal über die gleiche Liste zu summieren über eine 100000-mal so lange Liste mit Zufallszahlen summiert, damit der Compiler nicht 99999 der Rechnungen als offensichtlich unnötig wegoptimiert ☺.
Anhang | Größe |
---|---|
summieren_sum.py.txt [16] | 476 Bytes |
summieren_selbst.py.txt [17] | 749 Bytes |
summe.cpp_.txt [15] | 767 Bytes |
summe.f90__.txt [18] | 221 Bytes |
summen.py_.txt [14] | 301 Bytes |
summen.f90 [13] | 307 Bytes |
Ich habe getestet, ob es einen Geschwindigkeitsunterschied zwischen zwei Arten des for loops über eine Liste von tuples gibt:
liste_von_tuples = [(1, 2), (3, 4), (5, 6)]
Es gibt 100.000 tuple, und jeder tuple enthält 2 Zufallszahlen.
Art 1:
for i in liste_von_tuples: res.append(i[1])
Art 2:
for i, j in liste_von_tuples: res.append(j)
Ich hatte erwartet, dass die zweite Art schneller ist, da bei Art 1 eine Zuweisungsoperation von zwei Werten und danach eine Auswahloperation und eine Zuweisungsoperation nötig sind, während bei Art 2 die Auswahloperation wegfällt.
Der Test bestätigte meine Ansicht.
Art 1 braucht bei der Liste mit 100.000 Tuples von je 2 Zufallszahlen zwischen 4% und 12% länger.
Fazit: Bei for loops über tuple, sollte Art 2 verwendet werden:
for i, j in liste_von_tuples: res.append(j)
Die verwendete und ausführlich kommentierte Testdatei ist angehängt.
Anmerkung: Meine Python Tests können jetzt auch mit Mercurial heruntergeladen werden: http://bitbucket.org/ArneBab/python_tests/ [19]
Ideen und Patches wären cool :)
Anhang | Größe |
---|---|
for-loops-geschwindigkeitstest.py [20] | 5.57 KB |
Ich habe mein erstes Programm mit PyQt [21] erstellt.
In guter Programmier-Tradition heißt es "Hallo Welt!", und zeigt einfach "Hallo Welt!" an.
Ich habe es allerdings recht ausführlich kommentiert, so dass ich denke, dass es PyQt-Neulingen ein paar interessante Einzelheiten zeigen und Programmier- und Python-Neulingen ein paar einfache erste Einsichten geben kann.
Ich habe es angehängt, stelle den Code aber auch diekt auf diese Seite, denn Python Code ist schön genug, dass ich ihn offen auf die Webseite stellen kann.
-----
#!/bin/env python # encoding: utf-8 ####################################################### # # # Hallo Welt! # # - Mein Erstes PyQt-Programm mit Kommentaren # # # # Copyright (C) 2007, Arne Babenhauserheide, GPL # # # # This program is free software; you can # # redistribute it and/or modify it under the terms of # # the GNU General Public License as published by the # # Free Software Foundation; either version 2 of the # # License, or (at your option) any later version. # # # # This program is distributed in the hope that it # # will be useful, but WITHOUT ANY WARRANTY; without # # even the implied warranty of MERCHANTABILITY or # # FITNESS FOR A PARTICULAR PURPOSE. See the GNU # # General Public License for more details. # # # # You should have received a copy of the GNU General # # Public License along with this program; if not, # # write to the Free Software Foundation, Inc., 51 # # Franklin Street, Fifth Floor, Boston, MA 02110-1301 # # USA. # # # ####################################################### # Erst holen wir das System-Framework. "sys" # Qt Programme brauchen von ihm die # Kommandozeilenparameter. import sys # Dann die benötigten Qt-Klassen. # Erstens das grundlegende Widget für Programme # QApplication from qt import QApplication # und Zweitens einen Knopf, den wir beschriften wollen # QPushButton from qt import QPushButton # und dann noch Signal und Slot, # damit ein Druck auf den Knopf # das Programm beenden kann. from qt import SIGNAL, SLOT # Jetzt können wir uns das Programm app definieren # und ihm die Kommandozeilenparameter übergeben: app=QApplication(sys.argv) # Der Knopf ist ähnlich schnell geschrieben. # Er erhält den Text "Hallo Welt!\nIch kann # (ein bisschen) PyQt!" # Das \n erzeugt einen Zeilenumbruch. # Einfallsreicherweise nennen wir ihn "button". button=QPushButton("Hallo Welt!\nIch kann (ein bisschen) PyQt!", None) # button=QPushButton(u"Hallo traumrose!\nIch liebe Dich!\nKüsst du mich?", None) # Jetzt machen wir ihn zum Haupt-Widget # des Programmes "app", # so dass er auf der obersten Ebene liegt: app.setMainWidget(button) # Dann sagen wir ihm, dass er angezeigt werden soll. button.show() # Kurz vor dem Abschluss fügen wir noch # als kleine Spielerei # etwas Interaktivität hinzu: # Das Programm soll sich beenden, # wenn der Knopf gedrückt wird. # Dafür verbinden wir das Signal "pressed()" # des Knopfes "button" # mit dem Slot "quit()" des Programmes app. app.connect(button, SIGNAL("pressed()"), app, SLOT("quit()")) # Als letztes führen wir das GUI-Programm aus. # Erst hier wird der Knopf wirklich sichtbar. app.exec_loop() # Um das zu testen kannst du das Programm einfach # Schritt für Schritt in der Python-Shell ausführen. # Das beendet mein erste Programm. # Ich hoffe, Du hattest Spaß beim Lesen!
Für meine Plotroutinen brauche ich oft Funktionen, die eine Liste an Messwerten durchgehen und nur diejenigen zurückgeben, die einem bestimmten Kriterium entsprechen. Wenn dabei die Anzahl der Messwerte in die Millionen geht, kann alleine schon die Liste der ungefilterten Messwerte den Arbeitsspeicher des Rechners sprengen.
Ich könnte jetzt einfach eine Funktion schreiben, die alle Werte liest, filtert und nur die Relevanten zurückgibt.
Das könnte dann etwa so aussehen:
def naivefilter(filepath, name): """Get only the relevant values.""" values = [] with open(filepath) as f: for line in f: if line.startswith(name): values.append(line) return values
Wirkt im Kleinen noch übersichtlich. Aber sobald wir die Anzahl der Filterkriterien erhöhen - oder andere Eingabewerte brauchen - wird die Funktion unübersichtlich. Und unübersichtlicher Code ist unschön zu warten.
Um das Problem zu minimieren, nutze ich Iteratoren, die nur jeweils die benötigten Werte im Speicher zu behalten, aber von Funktion zu Funktion weitergegeben werden können. Damit kann ich die Logik zur Auswahl der Werte aus der auslesenden Funktion heraushalten und erhalte damit zwei schlanke Funktionen.
Das ganze sieht dann beispielsweise so aus:
def allvalues(filepath): """Get all values from the file.""" with open(filepath) as f: for line in f: yield line
def valuesforstation(filepath, name): """Get all the values from a file which come from the given station.""" return tuple(line for line in allvalues(filepath) if line.startswith(name))
Wirkt anfangs etwas länger, aber was passiert, wenn wir ein anderes Filterkriterium haben wollen?
Dann fügen wir einfach eine andere Filterfunktion hinzu:
def valueswithoutX(filepath, X): """Get all the values from a file which do not contain the value X.""" return tuple(line for line in allvalues(filepath) if not X in line)
Damit haben wir das Auslesen und das Filtern der Daten sauber getrennt, ohne jemals alle Werte gleichzeitig im Speicher halten zu müssen.
Der Code ist wartbar, übersichtlich und effizient. Und damit ist er genau so, wie ich ihn haben will.
Und falls ihr euch fragt, warum ich am Ende einen tuple zurückgebe und keinen weiteren Iterator: Ich habe noch einen Caching-Dekorator, und der mag keine Iteratoren (weil die theoretisch unendlich viele Werte zurückgeben können und damit nicht immer gecacht werden können). Aber dazu kommen wir ein ander’ Mal…1 (bis dahin kann ich euch den Leitfaden zu Dekoratoren von Simon Franklin [22] empfehlen, wenn ihr mit fortgeschritteneren Techniken experimentieren wollt :) ).
PS: Ja, ich könnte auch eine Datenbank nutzen. Dann muss ich die allerdings füllen und aktuell halten und wäre so weiter weg von den Daten, auf denen ich eigentlich arbeite. Und die Gesamt-Aufgaben sind schon komplex genug, ohne einen weiteren Indirektionsschritt einzuführen…
PPS: Alle Codeschnipsel in Python [23], farbige Quelltext-Hervorhebung via M-x htmlize-region
in emacs [24] mit inline-css (einstellbar über M-x customize-variable htmlize-output-type
)
Soll heißen, dazu kommen wir vielleicht ein ander’ Mal, je nach meinem Zeitbudget… ↩
Ich habe einen kleinen Test gemacht, um zu schauen, wie die Speicherverwaltung von Python funktioniert.
Dafür habe ich eine Klasse erzeugt, die eine extrem lange Liste als Attribut hatte, so dass viel Ram verbraucht wurde.
Dann habe ich mehrfach eine Instanz dieser Klasse der gleichen Variable zugewiesen.
Das Ergebnis war, dass höchstens der doppelte Speicherbedarf einer einzelnen Instanz verbraucht wurde.
Außerdem habe ich herausgefunden, dass ein dict mit 3 Millionen unterschiedlichen Schlüsseln, die auf das gleiche Objekt zeigen (None), etwa doppelt so viel Ram braucht wie eine Liste, die diese Schlüssel als Werte hat.
Das Skript zum Testen habe ich angehängt und poste es auch nochmal hier.
Viel Spaß beim selbst mit Python spielen :)
#!/usr/bin/env python # encoding: utf-8 """This file tests the memory purging of python by creating an instance of a class over and over again. It shows, that the instance is created at only one of two places over and over again, so the maximum additional memory consumed when replacing an instance is the size of the instance The reason seems to be that the instance is first created and then assigned to the variable. Only after that, the previous object gets deleted. This seems easy to understand, because this behaviour is necessary to allow for recursive functions where the new value of the variable depends on its previous value. """ # We want random, so that no optimizing of same objects can be done. from random import random # Now we create a memory hungry class. class MemoryHungry(object): """MemoryHungry is a class which just has many attributes which consume memory. For me one instance of MemoryHungry takes about 140Mib with number_of_atts=3,000,000 - bab. When I use a dict where I assign keys with random names to the value None (all the same object), this takes about 250MiB per instance. - bab """ def __init__(self, number_of_atts=3000000, mode="list"): if mode == "list": self.liste = [] #: list of random numbers elif mode == "dict": self.liste = {} #: dict with random numbers as keys # We add random numbers to teh list to make sure, that no memory # usage can be optimized by grouping same objects or similar. for i in range(number_of_atts): self.blah = random() #: temporary random number # append self.blah to the list attribute of the class, so it consumes # memory (as object of the list reassigning self.blah doesn't delete the # value/instance of float): if mode == "list": self.liste.append(self.blah) if mode == "dict": self.liste[self.blah] = None # None is only one object, so what takes space here are the keys. # Also we create a class thirsty, which creates a float with the same content # over and over again. class MemoryThirsty(object): """MemoryThirsty is a class which just has many attributes which consume memory. For me one instance of MemoryThirsty only takes about 70Mib with number_of_atts=3,000,000, which is half the amount taken by the same number of random numbers -bab. """ def __init__(self, number_of_atts=3000000): self.liste = [] #: List of random numbers # We add random numbers to the list to make sure, that no memory # usage can be optimized by grouping same objects or similar. for i in range(number_of_atts): self.blah = 0.111111111111111111111111111111111 #: just a floating point number # append self.blah to the list attribute of the class, so it consumes # memory (as object of the list reassigning self.blah doesn't delete the # value/instance of float): self.liste.append(self.blah) ### Self-Test ### if __name__ == "__main__": # Create a MemoryHungry instance and give it to a variable over and over again. print "hunger" for i in range(10): # assign an instance of MemoryHungry to the variable hunger hunger = MemoryHungry(mode="dict") # And print the instance. print hunger, i pass print "hunger done" del hunger print "thirst" for i in range(10): # assign an instance of MemoryHungry to the variable hunger thirsty = MemoryThirsty() # And print the instance. print thirsty, i del thirsty # We're done. Say so. print "done"
Anhang | Größe |
---|---|
python_memory_cleaning_test.py.txt [25] | 3.56 KB |
→ Antwort auf Is it in this case : http://identi.ca/url/75523035 (see : [01:16] [26]1 — Julien-Claude Fagot, die eine Antwort war auf One more reason why you should not use “from bla import foo”: print __import__(obs.__class__.__module__).__file__ [27] — ArneBab
Datei: bla.py
def foo(): print "bla"
Interaktiver Test:
\>>> import bla \>>> bla.foo() bla \>>> def fu(): ... print "fu" ... \>>> fu() fu \>>> from bla import foo \>>> foo() bla \>>> bla.foo = fu \>>> bla.foo() fu \>>> foo() bla
Profifrage: Was passiert, wenn du from bla import foo
nach bla.foo = fu
ausführst?
Antwort:
\>>> import bla \>>> bla.foo() bla \>>> def fu(): ... print "fu" ... \>>> fu() fu \>>> bla.foo = fu \>>> bla.foo() fu \>>> from bla import foo \>>> foo() fu
Happy hacking!
dsop: if you use 'bla.foo', then yes, you can assign to bla.foo and you'll see the change. If you do 'from bla import foo', then your locally imported 'foo' will not 'see' changes to bla.foo. ↩
Links:
[1] http://python.org/download/
[2] https://www.draketo.de/files/blob_swarm-0.3.tar.gz
[3] https://www.draketo.de/files/blob_swarm-0.2.tar.gz
[4] http://python.org/download
[5] https://www.draketo.de/files/blob_valentine-0.3.tar.gz
[6] https://www.draketo.de/files/blob_valentine_screenshot-2008-02-15.png
[7] http://svn.python.org/view/python/trunk/Tools/?rev=38228#dirlist
[8] http://docs.python.org/lib/node740.html
[9] http://www.python.org/doc/current/lib/node738.html
[10] http://www.python.org/doc/current/lib/i18n.html
[11] http://www.python.org/workshops/1997-10/proceedings/loewis.html
[12] http://wiki.wxpython.org/Internationalization
[13] https://www.draketo.de/files/summen.f90
[14] https://www.draketo.de/files/summen.py_.txt
[15] https://www.draketo.de/files/summe.cpp_.txt
[16] https://www.draketo.de/files/summieren_sum.py.txt
[17] https://www.draketo.de/files/summieren_selbst.py.txt
[18] https://www.draketo.de/files/summe.f90__.txt
[19] http://bitbucket.org/ArneBab/python_tests/
[20] https://www.draketo.de/files/for-loops-geschwindigkeitstest.py_2.txt
[21] http://www.riverbankcomputing.co.uk/pyqt/
[22] http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/
[23] http://python.org
[24] http://gnu.org/s/emacs
[25] https://www.draketo.de/files/python_memory_cleaning_test.py_0.txt
[26] http://identi.ca/notice/100260976
[27] http://identi.ca/notice/100258278