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++)