Reinhard Wobst, r dot wobst at gmx dot de
@(#) Jan 06 2005, 17:46:54
Beispiel (Decodieren von quoted Printäppel):
#!/usr/bin/env python import sys from binascii import a2b_hex lns = sys.stdin.read() # file as one string lns = lns.replace('=\n', '') # join marked lines L = lns.split('=') # bring hex numbers at begin of elements 1,2,... ll = (map(lambda x: '%s%s' % (a2b_hex(x[:2]), x[2:]), L[1:])) # replace hex numbers by characters print ''.join([L[0]]+ll)
L = [1,3,4,5,7] for i in L: print i
Äquivalent zu:
L = [1,3,4,5,7] Li = iter(L) for i in Li: print i
Die Schleife ist zu verstehen als:
while 1: try: i = Li.next() print i except StopIteration: break
Li ist ein Iterator, entsprechend einer Klasse, die Methoden __iter__() und next() unterstützt (Details vgl. Library Reference, 2.3.5). Konzept von C++/STL her bekannt!
Anwendung:
... for i in Li: if not i&1: i=Li.next() print i
(hier noch kein Abfangen von StopIteration)
fd = open('Heiko', 'r') for line in fd: print line
fd ist bereits ein Iterator, iter(fd) gibt fd zurück. (Variable fd is überflüssig in diesem Beispiel!)
Klassisch:
for line in fd.readlines(): print line(alle Zeilen im RAM!) oder
while 1: line = fd.readline() if not line: break print line
Besseres Beispiel:
for i in range(10000000): ...hält 10 Mio. Listenelemente a 16 Byte im Speicher; deswegen schreib man schon immer
for i in xrange(10000000): ...xrange ist ein Iterator, eine "Element-Fabrik". Ist verallgemeinerbar:
Man kann auch Klassen mit entspr. Methoden versehen, die sich dann wie Container verhalten - vgl. Language Reference (Reference Manual), 3.3.5
Funktionen, die das yield - Statement enthalten, heißen Generatorfunktionen. Sie geben einen Iterator zurück, keinen Wert! So kann man xrange() selbst definieren:
def Xrange(N): n = 0 while n < N: yield n # return n += 1 # nächster Einsprungpunkt for i in Xrange(7): print i
'yield' ist ein "return, das den Zustand einfriert". Xrange() ist keine übliche Funktion mehr, sondern gibt eine Klasseninstanz mit next()-Methode zurück (ist aber nicht einfach als Klasse zu implementieren!):
NN = Xrange(7) # Initialisierung for i in NN: ...
Weiteres Beispiel: Verarbeiten der Worte eines Riesenfiles (notwendig als Stream):
import sys def read_word(filename): for line in open(filename, 'r'): for word in line.split(): yield word for word in read_word(sys.argv[1]): print word
Klassisch als Klasse, zum Vergleich:
class read_word: def __init__(self, filename): self.fd = open(filename, 'r') self.indx = self.lng = 0 def retword(self): while self.indx >= self.lng: line = self.fd.readline() if not line: return None self.linesp = line.split() self.lng = len(self.linesp)-1 if self.lng < 0: continue self.indx = -1 break self.indx += 1 return self.linesp[self.indx] rw = read_word(sys.argv[1]) while 1: word = rw.retword() if not word: break print word
Nachteil: Kein Destruktor bei Generatorfunktionen, im Beispiel: File wird nicht automatisch geschlossen. (Ausweg: in Klasse read_word eine close()-Methode definieren, oder __del__() definieren).
Bindung von Ressourcen an Lebenszeit von Objekten nur in C++, nicht in einer Skriptsprache mit Garbage Collector! (Problem bei Java - Python kennt wenigstens Destruktoren, die allerdings asynchron gerufen werden).
Kurzform von Generatorfunktionen, ähnlich zu list comprehensions:
(x*x for x in L)
Einzelheiten in Reference Manual, 5.2.5.
Beispiel:
ends = {} for sq in (x*x for x in xrange(1000000)): digits = '06%d' % sq last6 = digits[-6:] ends[last6] = 1 # irgendein Wert (hier 1) print '%d verschiedene letzte 6 Ziffern von Quadratzahlen' % len(ends.keys())
(Rechenzeit Athlon 1700: 6sec; 78184 verschiedene Endungen)
Vorteil gegenüber list comprehension: Speicherplatz gespart:
for sq in [x*x for x in L]berechnet zunächst gesamte Liste, dann erst wird "for sq in [...]" ausgewertet.
>>> a=2**30 >>> a 1073741824 >>> a * 2**16 70368744177664LHintergrund: maschinenunabhängig (wurde lange diskutiert)
Anwendung: set( iterator) oder set( sequence)
Gleiche Elemente werden zusammengefasst:
>>> set('Schlittermann') set(['a', 'c', 'e', 'i', 'h', 'm', 'l', 'n', 'S', 'r', 't'])
len(s) gibt Anzahl der Elemente an; weitere Methoden: s.union(t), s.intersection(t), s.difference(t), s.symmetric_difference(t), s.copy() ...
Analog: frozenset(...) Konstante Menge, ähnlich Liste - Tupel.
vgl.a. Library Reference, 2.3.7
>>> import decimal >>> a = decimal.Decimal('14.75') >>> b = decimal.Decimal('14.3') >>> a+b Decimal("29.05") >>> 14.75+14.3 29.050000000000001
Grundrechenoperationen wie üblich, nur bei Potenzen lediglich ganzzahlige Werte erlaubt; Rundungsalgorithmus kann angegeben werden (wichtig in Finanzwirtschaft).
Mehr dazu in "What's new in Python2.4?", 8. (Online-Doku)
Ungewohnte Syntax (in Anlehnung an Java):
@decorfct def fct(arg): ...
ist eine Art Wrapper um fct(): Es wird in Wirklichkeit mit decorfct(fct) gerechnet. decorfct hat als Argument ein callable und muss ein solches zurückgeben.
Sinnvolles Beispiel (Überprüfung der Argumente auf ganzzahligen Typ):
def require_int(func): def wrapper(arg): assert isinstance(arg, int) return func(arg) return wrapper @require_int def p1(arg): print arg
Näheres dazu in der Online-Doku "What's New in Python?", 5., und im folgenden Punkt.
Methoden von Python-Klassen haben standardmäßig die Klasseninstanz als erstes Element:
class SvenDietmar: def __init__(self, name): self.name = name def invite(self, month): print 'Der Stammtisch im Monat %s' % month ... print 3*'\t', self.name ...
(self = "this" in C++).
Aufruf: D2 = SvenDietmar("Dietmar"); D2.invite('Januar')
Analog zu in C++ gibt es statische Funktionen, die an die Klasse, nicht an die Instanz gebunden sind (z.B. zur Initialisierung klassengebundener Daten):
class SvenDietmar: def __init_stammtisch(): SvenDietmar.phrase = "findet wie üblich am" ... init_stammtisch = staticmethod(__init_stammtisch) # !
(staticmethod() fügt das Argument in die Methodenliste der Klasse ein.)
Aufruf: SvenDietmar.init_stammtisch()
Bevorzugte Schreibweise seit Python2.4:
class SvenDietmar: @staticmethod def init_stammtisch(): SvenDietmar.phrase = "findet wie üblich am" ...
Konkretes Beispiel:
class B: @staticmethod def init(arg): B.arg = arg def prt(self): print B.arg >>> B.init('holla') >>> A = B() >>> A.prt() holla
Weiter gibt es class methods, ebenfalls instanz-unabhängig definiert, aber klassenabhängig. Beispiel:
class A: name = 'A' # klassengebundene Variable @classmethod def init(cls,arg): # 1. Argument ist Klasse, nicht Instanz! cls.arg = '%s aus Klasse %s' % (arg, cls.name) def prt(self): print A.arg class B(A): # vererbt name = 'B' # klassengebundene Variable def prt(self): print B.arg >>> A.init('huhu') >>> B.init('holla') >>> C = A(); D = B() >>> C.prt(); D.prt() huhu aus Klasse A holla aus Klasse B
Eine direkte Entsprechung in C++ gibt es nicht!
Nachteil: Von in C implementierten Klassen konnte man bisher nicht erben.
Beispiel:
class A(int): def __init__(self, n): self.val = n def __lshift__(self, s): return ((self.val << s) & 0xffffffff) | (self.val >> (32-s)) x = A(1<<30) print 'x: %x' % x print 'x-1: %x' % (x-1) print 'x >> 2: %x' % (x >> 2) print 'x << 2: %x' % (x << 2) Ausgabe: x: 40000000 x-1: 3fffffff x >> 2: 10000000 x << 2: 1
Sinnvolleres Beispiel (aus der Doku):
class LockableFile(file): def lock (self, operation, length=0, start=0, whence=0): import fcntl return fcntl.lockf(self.fileno(), operation, length, start, whence)
Da von der neuen Funktion file() geerbt, kann man nun schreiben
fd = LockableFile('carsten.grm', 'w') fd.lock(...)
Das Modul posixfile wird dadurch überflüssig.
Einige Bemerkungen dazu:
Näheres dazu: Dokumentation zu Python2.2, "What's new in Python2.2" (online auf www.python.org). Dokumentation hier noch lückenhaft, diese Bemerkungen hier dienen nur als Orientierungshilfe!
>>> import this
Jedem empfohlen! (Zen of Python)
list comprehensions
[expr for varname in list] [expr for varname in list if cond]
Sollen nach PEP3000 map() und filter() ersetzen, sind aber nicht immer so einprägsam:
>>> def f(x): return x*x >>> map(f, [1,2,3,4]) [1, 4, 9, 16]
EAFP
= easier ask for forgiveness than permission: try - catch
(auch selbst definierte Ausnahmeklassen häufig!)
LBYL
= look before you leap, viele if-statements (und doch noch
etwas vergessen :-)
Eindeutig EAFP vorzuziehen, wie in moderneren Sprachen (seit C++).
Komplexität:
Ressourcenverbrauch:
Zusätzlich für AMD noch 1500 Zeilen unter NDA :-) (das Meiste "nebenbei"; April/Mai z.B. noch 5500 Zeilen C++)