Sunday, September 25, 2011

Python: Automatische Werte fuer ganzzahlige Konstanten

Manchmal braucht man Konstante, die fortlaufend nummeriert sind. Diese werden dann zur Fallunterscheidung und / oder als Listenindex verwendet oder um oder-verknuepfte Flags zu uebertragen.


BEFORE_READ = 1
AFTER_READ = 2
BEFORE_WRITE = 3
AFTER_WRITE = 4
BEFORE_CREATE = 5
AFTER_CREATE = 6
BEFORE_UNLINK = 7
AFTER_UNLINK = 8
BEFORE_SEARCH = 9
AFTER_SEARCH = 10
BEFORE_WRITE_CREATE = 11
AFTER_WRITE_CREATE = 12

EVENT_FIRST = 1
_NUM_TRIGGER_EVENTS = 10
EVENT_LAST = 12

_FLAG_EXEC_ON_EACH_RECORD = 1
_FLAG_TRANSFORM_MANY2X_VALUES = 2
_FLAG_EXTEND_FIELD_LIST = 4

_trigger_funcs = [False] + [[] for x in range(_NUM_TRIGGER_EVENTS)]


Besonders waehrend der Entwicklung, aendert sich die Reihenfolge oder es werden Elemente entfernt oder hinzugefuegt. Die "Wartung" der Konstanten ist dabei recht laestig. Python hat keinen Datentyp fuer Enumerationen (Java) und auch keine Schluesselworter, um solche zu definieren (C / C++). Jedoch bietet Python mit Generatoren und Coroutienen Werkzeuge, mit denen dieser "Mangel" (nicht boese sein, mir fiel kein anderes Wort ein :-)) behoben werden kann.

Im folgenden Codeausschnitt wird eine Funktion definiert, die im Anschluss sofort als Generator und Coroutine initialisiert wird.


def _SequenceGenerator():
i = 0
while True:
y = (yield i if i else 1)
if y == None:
if not i:
i = 1
else:
i += 1
elif y == 'reset':
i = 0
sequence = _SequenceGenerator()
sequence.send(None)


Sollen nun Konstanten definiert werden, sendet man zunaecht die Zeichenfolge 'reset' an den Generator (Coroutine) und weist dann den Konstante von 1 an aufstegende Werte durch Aufruf der 'next()'-Methode zu. Um einen Wert mehrfach zu verwenden sendet man einen von None (und 'reset') unterschiedlichen Wert.


import tools
tools.sequence.send('reset')

EVENT_FIRST = tools.sequence.send(True)

BEFORE_READ = tools.sequence.next()
AFTER_READ = tools.sequence.next()
BEFORE_WRITE = tools.sequence.next()
AFTER_WRITE = tools.sequence.next()
BEFORE_CREATE = tools.sequence.next()
AFTER_CREATE = tools.sequence.next()
BEFORE_UNLINK = tools.sequence.next()
AFTER_UNLINK = tools.sequence.next()
BEFORE_SEARCH = tools.sequence.next()
AFTER_SEARCH = tools.sequence.next()

_NUM_TRIGGER_EVENTS = tools.sequence.send(True)

BEFORE_WRITE_CREATE = tools.sequence.next()
AFTER_WRITE_CREATE = tools.sequence.next()

EVENT_LAST = tools.sequence.send(True)

tools.sequence.send('reset')

_FLAG_EXEC_ON_EACH_RECORD = 1 << (tools.sequence.next() - 1)
_FLAG_TRANSFORM_MANY2X_VALUES = 1 << (tools.sequence.next() - 1)
_FLAG_EXTEND_FIELD_LIST = 1 << (tools.sequence.next() - 1)


Der Generator-Code kann in ein globales 'tools'-Modul ausgelagert und bei Bedarf importiert und so oft aufgerufen werden wie noetig.

Diese Methode ist nicht Thread-sicher.

Friday, September 23, 2011

Python: Schneller als dict.get()

Um einen Wert aus einem Dictionary zu holen aber beim Fehlen des Schluessels einen Standardwert zurueck zu geben, benutz man die get()-Methode des Dictionarys.

>>> d = {}
>>> v1 = d.get('k1', False)

In Laufzeitkritischen Situation ist aber die Verwendung des 'in' Operators in Verbindung mit der bedingten Zuweisung (var = <true_val> if <condition> else <false_val>) zu empfehlen.

>>> d = {}
>>> v1 = d['k1'] if 'k1' in d else False

Diese Variante ist mehr als doppelt so schnell. Ein Grund dafuer ist, dass kein neuer Stackframe (wie beim Aufruf der Methode) initialisiert werden muss.

>>> from timeit import repeat
>>> # using in-operator, key exists
... repeat("v1 = d['k1'] if 'k1' in d else False","d = {'k1':1,'k2':2,'k3':3}")
[0.07450103759765625, 0.07420086860656738, 0.08083891868591309]
>>> # using in-operator, key does not exist
... repeat("v1 = d['k1'] if 'k1' in d else False","d = {'k2':2,'k3':3}")
[0.0692899227142334, 0.07552504539489746, 0.06921982765197754]
>>> # using get-method, key exists
... repeat("v1 = d.get('k1', False)","d = {'k1':1,'k2':2,'k3':3}")
[0.17818498611450195, 0.17094182968139648, 0.1709129810333252]
>>> # using get-method, key does not exist
... repeat("v1 = d.get('k1', False)","d = {'k2':2,'k3':3}")
[0.1866598129272461, 0.17163419723510742, 0.1735219955444336]

Wednesday, September 7, 2011

Rekursives Loeschen aller .svn Verzeichnisse

Windows:
for /f "usebackq tokens=*" %g in (`dir /b /ad /s .svn`) do rmdir /s /q %g

Richtige Betriebssysteme:
find -type d -name '.svn' -delete