Dekoratoren

Der Name Dekorator ist unglücklich gewählt, da Dekoratoren nicht dem GOF Dekorator Pattern entsprechen.

Motivation

Seit Python 2.2 - mit Einführung der New Style Klassen - unterstützt Python neben den Instanzmethoden auch Klassenmethoden und statische Methoden.
Durch die Klassenmethode wird die Methode an die Klasse selbst, durch eine statische Methode an den Namensraum der Klasse gebunden.
Leider war der erste syntaktische Wurf nicht der Beste.

 

class First(object):

def clsMethod(cls): pass
clsMethod= classmethod(clsMethod)


Mit Python 2.4 wurde die Syntax überarbeitet und erweitert. In Anlehnung an Java wird vor die zu dekoriertende Funktion oder Methode der Dekorator mittels @ referenziert.

Die Klasse Second

 

class Second(object):

def iMethod(self): print "invoked with class instance"
@classmethod
def cMethod(cls): print "invoked with class"

@staticmethod
def sMethod(): print "invoked through namespace"



erzeugt die folgende Ausgabe.

 

>>> sec=Second()
>>> sec.iMethod()

invoked with class instance
>>> sec.cMethod()
invoked with class
>>> sec.cMethod()

invoked with class
>>> Second.cMethod()
invoked with class
>>> Second.sMethod()

invoked through namespace



Decoratoren sind ein mächtiges Werkzeug in Python um aspektorientiertzu programmieren.

Definition

Ein Dekorator ist ein aufrufbares Python Objekt, das ein Argument annimmt - die zu dekorierende Funktion.

In Python wird zwischen Signatur bewahrenden und Signatur verändernden Dekoratoren unterschieden. Die klasssischen Beispiele für Signatur veränderten Dekoratoren sind die built-in staticmethod und classmethod. Durch sie wird die Bindung der Methode an die Klasseninstanz aufgehoben.

Vorteile

Dekoratoren erlauben es, deklarativ zu programmieren, indem der Funktion ein Dekorator vorangestellt wird. Dadurch wird die Kernfunktionalität um einen neuen Aspekt erweitert. Insbesondere das Kapseln von Aspekten (Logging, Tracing) in Dekoratoren ermöglicht es, die reine Funktionalität von der erweiterten Funktionalität zu trennen und diese wiederverwendbar zu halten. Aspekte der Programmfunktionalität wie Logging müssen nicht mehr mit der Kerfunktionalität verwoben werden.

 

@logging
@synchronized
def myFunction( ...



Michele Simionato faßt auf http://www.phyast.pitt.edu/~micheles/python/documentation.html#id5 die Vorteile der Dekoratoren zusammen:

   * decorators help reducing boilerplate code;
* decorators help separation of concerns;
* decorators enhance readability and maintenability;
* decorators are very explicit.

Techniken

Implementierung

Ein Dekorator muß ein Python Objekt sein, das mit einem Argument aufgerufen werden kann.
Sowohl Funktionen mit inneren Funktionen (Closures) als auch Objekte (Funktoren), die aufrufbar sind, eignen sich, um Dekoratoren zu implementieren.

Funktion - Closures

def decorator(func):

def closure(*__args,**__kw):
print "Hello", func
try:

return func(*__args,**__kw)
finally:
print "Good Bye", func
return closure

Funktor - callable Objekt

class  Decorator(object):
def __init__(self,func):

self.__func = func
def __call__(self,*__args,**__kw):

print "Hello", self.__func
try:
return self.__func(*__args,**__kw)

finally:
print "Good Bye", self.__func

Anwendung

>>> @decorator
... def tdec(a,*b,**c): print a,b,c

...
>>> @Decorator
... def tDec(a,*b,**c): print a,b,c

...
>>> tdec(1,2,l=5)
Hello <function tdec at 0x4f3e668>

1 (2,) {'l': 5}
Good Bye <function tdec at 0x4f3e668>

>>>
>>> tDec(1,2,l=5)
Hello <function tDec at 0x2ad6d9a08488>

1 (2,) {'l': 5}
Good Bye <function tDec at 0x2ad6d9a08488>

Zustandsbehaftete Dekoratoren

Typischerweise bauen Dekoratoren einen Zustand auf. Hierzu bieten sich Funktoren mit statischen Variablen an. Die einfache Funktion present schreibt die Namen aller Anwesenden in eine Datei.

 

def present(name):

open("/home/grimm/Participant.txt","a").write(name+"\n")



Wer zu spät kommt, wird notiert! Dazu verwende ich die den Funktor ToLate und binde damit die Funktion present neu.

 

class ToLate(object):

__toLate=[]
def __init__(self,func):
self.__func=func
def __call__(self,name):

import time
ToLate.__toLate.append(time.ctime() + ": " + name)

self.__func(name)
@classmethod
def getAll(cls):

return ToLate.__toLate

 

 

>>> present("Du")
>>> present("Ich")
>>> present=ToLate(present)

>>> present("Winter")
>>> present("Kaiser")
>>> for i in ToLate.getAll(): print i

...
Sat Apr 5 18:46:35 2008: Winter

Sat Apr 5 18:46:40 2008: Kaiser

>>>

Funktionskomposition

Die Dekoration von Funktionen läuft auf eine Funktionskompositions hinaus. So wird der Ausdruck

 

@g
@f
def foo(): pass



auf

 

foo=g(f(foo).



abgebildet

  • Beispiel
>>> def third(f):

... f.__name__ += third.__name__
... return f

...
>>> def second(f):
... f.__name__ += second.__name__

... return f
...
>>> @third
... @second
... def first(): print first.__name__

...
>>> first()
firstsecondthird

Funktionsattribute anpassen

Der Decorator decorator hat noch ein Schönheitsfehler. Er reicht die Funktionsattributte nicht durch.

 

>>> @decorator
... def test(): pass
...
>>> test()

Hello <function test at 0x2ad26cd4e5f0>
Good Bye <function test at 0x2ad26cd4e5f0>
>>> test.__name__

'closure'



decorateFuncAttr ist ein einfacher Decorator, der einen Decorator dahingegen dekoriert, daß er die Funktionsattribute des Decorators und der zu dekorierenden Funktion durchreicht.

 

def decorateFuncAttr(decorator):

"decorate the function attributes"
def new_decorator(f):
g = decorator(f)

g.__name__ = f.__name__
g.__doc__ = f.__doc__

g.__dict__.update(f.__dict__)
return g
new_decorator.__name__ = decorator.__name__

new_decorator.__doc__ = decorator.__doc__

new_decorator.__dict__.update(decorator.__dict__)

return new_decorator

 

  • die Funktionsattribute des Decorators

 

 

  >>> @decorateFuncAttr
... def decorator(func):

... def closure(*__args,**__kw):
... print "Hello", func

... try:
... return func(*__args,**__kw)

... finally:
... print "Good Bye", func
... return closure

...
>>> decorator.__name__
'decorator'

 

  • die Funktionsattribute der zu dekorienden Funktion

 

 

>>> @decorator
... def test(): pass

...
>>> test()
Hello <function test at 0x2ad26cd5cc08>
Good Bye <function test at 0x2ad26cd5cc08>

>>> test.__name__
'test'

 

Beispiele

Die folgendene Beispiele spiegeln typische Beispiele für Dekoratoren wieder.

Für meine Beispiele will ich die Funktion fibonacci verwenden, die die Fibonacci Zahlen berechnet.

 

def fibonacci(n):

"Return the nth fibonacci number."
if n in (0,1):
return n
return fibonacci(n-1) + fibonacci(n-2)



Ich habe schon mal etwas vorbereitet.

 

>>> for i in range(0,100,2):

... beg= time.time()
... print "fibonacci(%2d)= %10d: in %10.4f seconds" % (i,fibonacci(i),time.time()-beg)

...
fibonacci( 0)= 0: in 0.0000 seconds
fibonacci( 2)= 1: in 0.0000 seconds

fibonacci( 4)= 3: in 0.0000 seconds
fibonacci( 6)= 8: in 0.0000 seconds

fibonacci( 8)= 21: in 0.0000 seconds
fibonacci(10)= 55: in 0.0001 seconds

fibonacci(12)= 144: in 0.0002 seconds
fibonacci(14)= 377: in 0.0004 seconds

fibonacci(16)= 987: in 0.0011 seconds
fibonacci(18)= 2584: in 0.0029 seconds

fibonacci(20)= 6765: in 0.0076 seconds
fibonacci(22)= 17711: in 0.0200 seconds

fibonacci(24)= 46368: in 0.0394 seconds
fibonacci(26)= 121393: in 0.1020 seconds

fibonacci(28)= 317811: in 0.2676 seconds
fibonacci(30)= 832040: in 0.6980 seconds

fibonacci(32)= 2178309: in 1.8305 seconds
fibonacci(34)= 5702887: in 4.7899 seconds

fibonacci(36)= 14930352: in 12.4315 seconds
fibonacci(38)= 39088169: in 32.6308 seconds

fibonacci(40)= 102334155: in 85.8567 seconds
fibonacci(42)= 267914296: in 227.0623 seconds

fibonacci(44)= 701408733: in 594.6344 seconds
fibonacci(46)= 1836311903: in 1558.6466 seconds
....

KeyboardInterrupt

Gedächnis aufbauen - memoize

class memoized(object):

"""Decorator that caches a function's return value each time it is called.
If called later with the same arguments, the cached value is returned, and
not re-evaluated.
"""
def __init__(self, func):

self.func = func
self.__name__= func.__name__

self.cache = {}
def __call__(self, *args):

try:
return self.cache[args]
except KeyError:

self.cache[args] = value = self.func(*args)

return value
except TypeError: # uncachable
return self.func(*args)

@memoized
def fibonacci(n):
"Return the nth fibonacci number."
if n in (0,1):
return n
return fibonacci(n-1) + fibonacci(n-2)

 

 

>>> for i in range(0,201,10):

... beg= time.time()
... print "fibonacci(%3d)= %45d: in %12.10f seconds" % (i,fibonacci(i),time.time()-beg)

...
fibonacci( 0)= 0: in 0.0000369549 seconds
fibonacci( 10)= 55: in 0.0000820160 seconds

fibonacci( 20)= 6765: in 0.0000691414 seconds
fibonacci( 30)= 832040: in 0.0000689030 seconds

fibonacci( 40)= 102334155: in 0.0000669956 seconds
fibonacci( 50)= 12586269025: in 0.0000658035 seconds

fibonacci( 60)= 1548008755920: in 0.0000650883 seconds
fibonacci( 70)= 190392490709135: in 0.0000669956 seconds

fibonacci( 80)= 23416728348467685: in 0.0000660419 seconds
fibonacci( 90)= 2880067194370816120: in 0.0000710487 seconds

fibonacci(100)= 354224848179261915075: in 0.0000720024 seconds
fibonacci(110)= 43566776258854844738105: in 0.0000681877 seconds

fibonacci(120)= 5358359254990966640871840: in 0.0000679493 seconds
fibonacci(130)= 659034621587630041982498215: in 0.0000681877 seconds

fibonacci(140)= 81055900096023504197206408605: in 0.0000669956 seconds
fibonacci(150)= 9969216677189303386214405760200: in 0.0000669956 seconds

fibonacci(160)= 1226132595394188293000174702095995: in 0.0000669956 seconds
fibonacci(170)= 150804340016807970735635273952047185: in 0.0000681877 seconds

fibonacci(180)= 18547707689471986212190138521399707760: in 0.0000669956 seconds
fibonacci(190)= 2281217241465037496128651402858212007295: in 0.0000679493 seconds

fibonacci(200)= 280571172992510140037611932413038677189525: in 0.0000669956 seconds

 

 

>>> fibonacci(1000)
...
File "<stdin>", line 13, in __call__

File "<stdin>", line 4, in fibonacci
RuntimeError: maximum recursion depth exceeded in cmp

 

 

>>> for i in range(0,20001,200):

... beg= time.time()
... print "fibonacci(%5d)= %d \nin %12.10f seconds" % (i,fibonacci(i),time.time()-beg)

...
.
.
.
fibonacci(19800)= 40345206940436314295042729139120459614144379881223162153049978480362014160056661005129017241
6
40668700336802670930475273181397108497771429743086347829129056684235039853067953448156722991298180378733190957
2
03497202417176821085040467794918166179805860675932776169843744908716442021731995003435283253463320451502901729
741551954230277175347224057155287262037326081486512995161256751251556710653441551957228260902408150439910956050
686982916064265364296658839303806149063535501482556463552311622305566047086390931110726552435413255631397045223
856858598233036947901417570900824625537459862423483537361413422799314751416212487814577754676382669022710537348
341902856017549317143660608124342787885536042058529108266676514452230634597189967541705619446325488683600069864
390015415130351696842652754460919006308823963342335674908068116994120932322688063872861224803539434800417451065
169421873140477152028533407939231616905475189122834974732277574593969245279902952167169497996102009518784264917
790188314922579905132644103602738435268739871297004160536451850850135000900739650487751913758420645118567090624
894345143527337521368542109864035715955603327189468112523664619841196879017158303058611656264912951567799205230
413948158277891815136531977688527970559004644987726546614762658704790069224324948542617229074543915767920043261
737636587547332835452399593409501184577405620375481465593141259472493935298038545497406278782727899188413610380
733467472461815137404687164169310983060323866177001449400175378569899005032114216009828586342990247168442313077
852803547126143351942116964931350888286890265378123212234347282935495770608555590127480899671624015394755422063
363199955579490070535287854730774777689756192857360494554978149037093308418939187068176841964980879859987790902
601931767710868808168331905231447073451865953497065409753876343937473540649828027825358683156004032211844498095
083614884921654303992887756734450358463473394016734849880324908416755433123174652760556975185061420201252965087
539961485724351714966539921982774695629812109332238689220867889558447398984315640248786990956741797119019192856
650260102145403281059639493258505669127123343596970082977410313043838332570304146216326827189835233711880399779
994744533068341833774356313726440392625646624658946270185872057789734082500570649244526218643529641311039159088
054004979471023690386835120664851218059454441848497881521623651435379320030368856820457375916293091618962784463
148944678977347451765845532539738513126173425207679925501351207298051499000893329086460796667041320583411728519
138760779051385081141536555268941325380066085562765631157242184979054062589629488674512459076367053497200318281
482910944731852730465003722614146270911434927057985866874893771657792931371214776002793241930378606963910986944
026653696477511912009920479133181852283335542611749918288744551667961967721989245498804441210668039914634232644
660377186904347774636953338759109074326516003609360253087185009424843964561295979813230483931893224863320690493
333564380915200883436464993330351690193277854598216130612951863145012873222693508976667700396702669353983668505
544207394288294759353802147612503467163462914769363422044655885602897460031883717325798220135841761663782874443
918837458147919648578498212170326638650361056567296244955603161266324569940563466373606432023268840170563881238
709872523557885114181135898400521183499978361449445318433761212653054967305833001826786855692567083383039787440
001318734202792292984830193491260873790261273605832866052429221685265640101549088198718025465534467340732277948
571665248644080048558001927083871249370685873521008494620141150190648792696352741080354705464713450180599261682
629976637327066138525439583984667355648117212711348540352771857478071099283267196870533925125911769624097950235
998866218066895886491499964625384559788182818936477821390073911791155105766815343879957078556374514944986033341
531863232842141119906757672887846255032595942632470177238341813025653871951108861749826555628967311476830627721
751955698151388684488964072334705269810685438183867999369562745240469416693163000803035366077976357330017047362
0652958678407354688780134235959976797797004653600
in 0.0028040409 seconds
fibonacci(20000)= 25311623237323612422401550035206072917663564858024852789519298419913127817605413152301534234
6
37588316374434882192110376890336735314627428853297240715551876180269316304491931589227713316423020303319710986
8
92357808434782585027792002936356518974833096860428609963644435145587721560436914041558195729849717542785131124
8
79858927182295933294835785314191488053802816242609003629935569166386139399770746850161882585843123291395263935
5
80968408129704229524185589918557723068824425748555892371652199122382013111847490751373229876560498663053669137
3
49244258226813389665074638551802362835824098611992123238359478911437654149133450084560220094557042108916377919
1
12654751677697044773348591098225900537749329784656510238514479206013101062889578943015925020615605281312030727
7
86774914434209218225907099104486173291561353554646208917884595660815728248895142963506709508242082451706676017
2
64170911279999999411499130104245320468819582854094684632118975822150754365155840162978745721839079492572862616
0
86124013796394847131011381204046717321904513278814332010251840275416961241144634886653593858709103314761566658
8
94598320927103041596370197072979884178487670110854252718755880086714224914340051152883343438377787922823835767
3
63414144102489940815648302023638205041900745045666125159651346656832893561887275494637328300758118515749615586
6
92788473632798705953200998446768794571964325359733571283053902904713494802587518128903147797235081042295251617
4
06439844239786596382330744631003665005719772345084647100781025813048232354365181450744828248129965116141619333
1
33898896309353201395070759921005610775340282072575742577062782013083026426346781125910918430826657216971178387
2
64317667411587435542988645609932555476084966868501858046597902171224265351332533714222506844861134573418279116
2
55171288154473259585479121132423672019906722306813088191959410161560019619547002415765537507376815522568454211
5
93868583994334500459039751670842528768488480859101569416032934240677930972711288068175149065316524077631183081
6
23770334632035146575312104131491912135954552803876310306655945891836015753400271729972224890816311447288736218
0
55286487685113689486395229755390469953957076889389788470846215864735295466789582262550423899987181413030550360
6
07720038877730384223669138203977485507931781672201933460174300241344961411459918962277418425157189978986272699
1
82369204534939466582738704732645231191337654476532950228864291749426530146565219094696131849836714314659349654
8
94255159810675460873423483507242075835444361072940876379750251478462545269384424356449282310278687013948190911
3
29123974757137875936127583648126875567251464566468789121692742192097081666786681521849415785902019531440305193
8
19222732526666526717175263186066767545561703793509563420954556127802021999226153927855724817479134355608669954
3
25786809712439668681100165813956963109225198036858374607953583846180172154681228804422523436845472336685023132
3
93283526713181306042474604521341218333052843987264385737877984996127609394624279229176592630463330840072080566
3
19968563155396982340229534522115056756291536378672526950569253452200840200716112205757008412683026389952728421
6
09942196326845753641801609918848850918582599962996271486144566966614127450405199815755438048474639974223265638
9
70438037329703974884716449061833101446912436491495423946915249720239351906336728273061165257128829591084342116
5
24656211447020153366574595321340269152145099608774305958442875853502902345475645748487531102811015459315472258
1
17634417102174529796681780252864601583246588529041057924724681089961354766372120575081921769109004228269695234
3
89853320675970934540219240771017842159365396388086244201214597182860594018236142132143260042704717528027256258
1
09537877138988461442569098351163712350195270131802040301676015670642685738206979488689826309041646851617830880
7
65069643173037097085740527472044052827859656046776741925698519186436518357552426702936128519206967323205455622
8
61103321400659127515511101349162562378848440013663666540550797219858167148039524293015580969682022616988370960
9
03778630177970204880448266288174628668543213567873056356535776198779879981136679289548409720228335057085875619
0
2023411398915823487627297968947621416912816367516125096563705174220460639857683971213093125
in 0.0027449131 seconds

Programmverlauf verfolgen

Zeitverlauf

def timestamp(func):

"For each invoking of the function print the timestamp"
def wrappedFunc():
print "[%s] %s() called" %( time.ctime(),func.__name__ )
return func()
return wrappedFunc

 

 

>>> class Man: pass
...
>>> class Woman: pass

...
>>> @timestamp
... def createMan(): Man()
...
>>> @timestamp

... def createWoman(): Woman()
...
>> for i in range(20):

... random.choice((createMan,createWoman))()
...
[Mon Mar 31 22:38:17 2008] createMan() called

[Mon Mar 31 22:38:17 2008] createMan() called

[Mon Mar 31 22:38:17 2008] createWoman() called

[Mon Mar 31 22:38:17 2008] createMan() called

[Mon Mar 31 22:38:17 2008] createWoman() called

[Mon Mar 31 22:38:17 2008] createMan() called

[Mon Mar 31 22:38:17 2008] createMan() called

[Mon Mar 31 22:38:17 2008] createMan() called

[Mon Mar 31 22:38:17 2008] createWoman() called

[Mon Mar 31 22:38:17 2008] createWoman() called

[Mon Mar 31 22:38:17 2008] createMan() called

[Mon Mar 31 22:38:17 2008] createMan() called

[Mon Mar 31 22:38:17 2008] createMan() called

[Mon Mar 31 22:38:17 2008] createWoman() called

[Mon Mar 31 22:38:17 2008] createMan() called

[Mon Mar 31 22:38:17 2008] createMan() called

[Mon Mar 31 22:38:17 2008] createMan() called

[Mon Mar 31 22:38:17 2008] createMan() called

[Mon Mar 31 22:38:17 2008] createWoman() called

[Mon Mar 31 22:38:17 2008] createMan() called

Aufrufhäufigkeit

class countcalls(object):
"Decorator that keeps track of the number of times a function is called."

__instances = {}
def __init__(self, f):
self.__f = f
self.__numCalls = 0
countcalls.__instances[f.__name__] = self
def __call__(self, *args, **kwargs):

self.__numCalls += 1
return self.__f(*args, **kwargs)

@staticmethod
def count(f):
"Return the number of times the function f was called."
return countcalls.__instances[f].__numCalls

@staticmethod
def counts():
"Return a dict of {function: # of calls} for all registered functions."
return dict([(f, countcalls.count(f)) for f in countcalls.__instances])

 

 

>>> @countcalls
... def createMan(): Man()

...
>>> @countcalls
... def createWoman(): Woman()
...
>>> for i in range(2000):

... random.choice((createMan,createWoman))()
...
>>> countcalls.counts()

{'createMan': 1015, 'createWoman': 985}
>>> createMan()

>>> countcalls.counts()
{'createMan': 1016, 'createWoman': 985}

 

 

>>> @countcalls
... def fibonacci(n):

... "Return the nth fibonacci number."
... if n in (0,1):

... return n
... return fibonacci(n-1) + fibonacci(n-2)

...
>>> fibonacci(30)
832040
>>> countcalls.counts()
{'fibonacci': 2692537}

 

 

>>> @countcalls
... @memoized
... def fibonacci(n):

... "Return the nth fibonacci number."
... if n in (0,1):

... return n
... return fibonacci(n-1) + fibonacci(n-2)

...
>>> fibonacci(30)
832040
>>> countcalls.counts()
{'fibonacci': 59}

Programmfluß

class traced:
def __init__(self,func):
self.func = func
def __call__(self,*__args,**__kw):

print "entering", self.func
try:
return self.func(*__args,**__kw)

finally:
print "exiting", self.func

 

 

>>> @traced
... def a(): b()
...

>>> @traced
... def b(): c()
...
>>> @traced
... def c(): pass

...
>>> a()
entering <function a at 0x2b4ae6259c80>
entering <function b at 0x2b4ae6259c08>
entering <function c at 0x2b4ae6259aa0>

exiting <function c at 0x2b4ae6259aa0>
exiting <function b at 0x2b4ae6259c08>
exiting <function a at 0x2b4ae6259c80>

Profilen

class profiler(object):
"Returns for each invoked function the executing time, depending and undepending on the signature"
__profileTime = {}

__erg=0
def __init__(self,func):
self.func = func
self.__startKey= None

self.__begin= None
def __call__(self,*__args,**__kw):

import time
if ( self.__startKey is None ):

self.__startKey= (__args,__kw)
self.__begin= time.time()
__erg=self.func(*__args,**__kw)
if ( self.__startKey == (__args,__kw)):

self.__actKey= (self.func.__name__,__args,__kw )

abs= time.time() - self.__begin
profiler.__profileTime[str(self.__actKey)]= profiler.__profileTime.setdefault(str(self.__actKey),0) + abs
profiler.__profileTime[self.func.__name__]= profiler.__profileTime.setdefault(self.func.__name__,0) + abs
self.__startKey= None

return __erg
@staticmethod
def sum():
return profiler.__profileTime

 

 

>>> @profiler
... def testMal(a,*args,**kargs): pass

...
>>> testMal(1)
>>> testMal(2)
>>> testMal(1,(1,2,3))

>>> testMal(1,(1,2,3),b=5)
>>> for i in range(10000): testMal(3)

...
>>> profiler.sum()
{"('testMal', (1, (1, 2, 3)), {'b': 5})": 8.106231689453125e-06, "('testMal', (1, (1, 2, 3)), {})": 9.059906005859375e-06,

"('testMal', (3,), {})": 0.021481037139892578, 'testMal': 0.021514415740966797, "('testMal', (2,), {})": 8.106231689453125e-06,

"('testMal', (1,), {})": 8.106231689453125e-06}
>>> @profiler
... def fibonacci(n):

... "Return the nth fibonacci number."
... if n in (0,1):

... return n
... return fibonacci(n-1) + fibonacci(n-2)

...
>>> fibonacci(20)
6765
>>> for i in profiler.sum().items(): print i

...
("('testMal', (1, (1, 2, 3)), {'b': 5})", 8.106231689453125e-06)
("('testMal', (1, (1, 2, 3)), {})", 9.059906005859375e-06)
("('testMal', (3,), {})", 0.021481037139892578)

('testMal', 0.021514415740966797)
("('testMal', (2,), {})", 8.106231689453125e-06)
("('fibonacci', (20,), {})", 0.068626165390014648)

("('testMal', (1,), {})", 8.106231689453125e-06)
('fibonacci', 0.068626165390014648)

Logging

class logger:
"Write a log entry, including the signature, for each invoked function"
def __init__(self,func):

self.func = func
self.__logFile= open("/home/grimm/test/logger","a")

def __call__(self,*__args,**__kw):
import time
import inspect
logMessage= "invoked" + str(self.func) + " with " +
str(inspect.getargvalues( inspect.currentframe() )[3:] ) + " at " + str(time.ctime())

print logMessage
print >> self.__logFile , logMessage

self.__logFile.flush()
return self.func(*__args,**__kw)

 

 

>>> @logger
... def funcTest(a,*args,**kwargs): pass

...
>>> funcTest(5)
invoked<function funcTest at 0x6e9e60> with ({'self': <__main__.logger instance at 0x6ed5f0>,

'inspect': <module 'inspect' from '/usr/local/python/2.5.1/lib/python2.5/inspect.py'>,
'_logger__kw': {}, '_logger__args': (5,),

'time': <module 'time' from '/usr/local/python/2.5.1/lib/python2.5/lib-dynload/time.so'>},)
at Tue Apr 1 23:05:12 2008

>>> funcTest(5,2,3,4,abcd=4)
invoked<function funcTest at 0x6e9e60> with ({'self': <__main__.logger instance at 0x6ed5f0>,

'inspect': <module 'inspect' from '/usr/local/python/2.5.1/lib/python2.5/inspect.py'>,
'_logger__kw': {'abcd': 4}, '_logger__args': (5, 2, 3, 4),

'time': <module 'time' from '/usr/local/python/2.5.1/lib/python2.5/lib-dynload/time.so'>},)
at Tue Apr 1 23:06:55 2008

Synchronisation

def synchronized(func):
"Synchronize the decorated function"

def wrapper(self,*__args,**__kw):
try:

rlock = self._sync_lock
except AttributeError:
from threading import RLock

rlock = self.__dict__.setdefault('_sync_lock',RLock())

rlock.acquire()
try:
return func(self,*__args,**__kw)

finally:
rlock.release()

Annotationen

def attrs(**kwds):
"Enrich the decorated function with attributes"
def decorate(f):

for k in kwds:
setattr(f, k, kwds[k])

return f
return decorate

 

 

>>> @attrs(version="2.2",author="Guido van Rossum",test=lambda:"inner function")

... def func(): pass
...
>>> func.version
'2.2'

>>> func.author
'Guido van Rossum'
>>> test.test()
'inner function'

Vor- und Nachbedingungen einer Funktionen

def accepts(*types):
"Check for each invoked function the right number and types of the arguments"
def check_accepts(f):

assert len(types) == f.func_code.co_argcount
def new_f(*args, **kwds):

for (a, t) in zip(args, types):

assert isinstance(a, t), \
"arg %r does not match %s" % (a,t)

return f(*args, **kwds)
new_f.func_name = f.func_name
return new_f
return check_accepts

 

 

def returns(rtype):
"Check the right type of the return value"

def check_returns(f):
def new_f(*args, **kwds):

result = f(*args, **kwds)
assert isinstance(result, rtype), \

"return value %r does not match %s" % (result,rtype)
return result
new_f.func_name = f.func_name
return new_f
return check_returns

 

 

>>> @accepts(int,float)

... def intFloat(a,b): pass
...
>>> intFloat(1,1.0)

>>> intFloat(1,1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>

File "<stdin>", line 7, in new_f

AssertionError: arg 1 does not match <type 'float'>

 

 

>>> @returns(list)
... def retList(a): return a

...
>>> retList(range(5))
[0, 1, 2, 3, 4]

>>> retList(tuple(range(5)) )
Traceback (most recent call last):

File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in new_f

AssertionError: return value (0, 1, 2, 3, 4) does not match <type 'list'>

>>>

 

 

>>> class MyInt(int): pass

...
>>> @returns(MyInt)
... @accepts(int,MyInt)

... def a(a,b): return b
...
>>> a(4,MyInt())

0
>>> a(MyInt(),MyInt())
0
>>>

Handler registrieren

def onexit(f):
"Register an exit handler"
import atexit
atexit.register(f)

return f

 

 

@onexit
def anonymous(): print "Good Bye"

Repository

Python Decorator Library

Die Python Decorator Library versteht sich als Repository für diverse Dekoratoren.

Decorator Modul

Das Decorator Modulvon Michele Simionato ist eine weitere Quelle vieler Dekoratoren.

Performance

  • Fragestellung: Wie teuer ist die Indirektion eines Funktors?

Gegeben seien die drei Minimaldekoratoren decoFunc , DecoClassNew und DecoClassOld und die einfache Funktion nullFunction . Wie teuer ist die Indirektion eines Dekorators?

 

def decoFunc(func):
def closure(*__args,**__kw):

return func(*__args,**__kw)
return closure

class DecoClassNew(object):

def __init__(self,func):
self.__func = func
def __call__(self,*__args,**__kw):

return self.__func(*__args,**__kw)

class DecoClassOld():

def __init__(self,func):
self.__func = func
def __call__(self,*__args,**__kw):
return self.__func(*__args,**__kw)

def nullFunction(): pass



Die Funktion performanceTest(number) führt sowohl die nullFunction direkt aus als auch mit den drei Dekoratoren.

 

def performanceTest(number):
import time
print "invocation of def nullFunction(): pass"
def nullFunction(): pass

time1= time.time()
for i in xrange(number):

nullFunction()
time2=time.time()
print "time for " + str(number) + " invocations: " + str(time2-time1) ,"\n"

for i in (decoFunc,DecoClassNew,DecoClassOld):
print "decorator "+ str(i) + " of def nullFunction(): pass"

@i
def nullFunction(): pass
time1= time.time()

for i in xrange(number):
nullFunction()
time2=time.time()

print "time for " + str(number) + " invocations: " + str(time2-time1) ,"\n"

 

  • 1.000.000 Aufrufe später

 

 

>>> performanceTest(1000000)

invocation of def nullFunction(): pass
time for 1000000 invocations: 0.170559883118

decorator <function decoFunc at 0x2b780b071758> of def nullFunction(): pass
time for 1000000 invocations: 0.491285085678

decorator <class '__main__.DecoClassNew'> of def nullFunction(): pass
time for 1000000 invocations: 0.729209184647

decorator __main__.DecoClassOld of def nullFunction(): pass
time for 1000000 invocations: 0.953649997711

 

  • Ergebnisse:
    • 1.000.000 Aufrufe benötigen unter 1 Sekunde
    • Zeitverbrauch(Dekorator als Closure) < Zeitverbrauch(Dekorator als new-style class) < Zeitverbrauch(Dekorator als old-style class)
    • der direkte Aufruf der Funktion ist um den Faktor 3-6 schneller als der Aufruf über die Dekoratoren

Ausblick

Ab Python 2.6 werden Klassendekoratoren unterstützt. Damit existiert eine weitere, elegante Art, das SingletonPattern umzusetzen.

 

def singleton(cls):
"Modifies the decorated class to a singleton"
instances = {}
def getinstance():

if cls not in instances:
instances[cls] = cls()

return instances[cls]
return getinstance

@singleton
class MyClass: pass

Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare