basics michael poeltl © 2011,2013

Funktionen I

Einleitung

Eine Funktion in python ist ein Objekt vom Typ function mit einer eigenen object-id - es kann also wie jedes andere Objekt behandelt werden, mit dem feinen Unterschied, dass dieser Objekt-Typ zusätzlich callable (aufrufbar) ist.

>>> def eine_func():
...     pass
...
>>> print( type( eine_func ))
<class 'function'>
>>> print( id( eine_func))
137241068
>>>

Wir haben ja bereits Funktionen da und dort in Aktion gesehen. Nämlich jene, die mit dem python-Interpreter mitkamen und daher als
builtin-Funktionen (builtin ==> fix eingebaut) bezeichnet werden.

>>> len( [1,2,3] )
3
>>>

In diesem Beispiel haben wir eine Funktion namens len() aufgerufen.
len ist der Name, der auf das Funktionsobjekt im Arbeitsspeicher zeigt. Mit den angehängten runden Klammern (auf/zu) deuteten wir an, dass es sich um eine Funktion handelt.
In diesen runden Klammern werden in den meisten Fällen der Funktion Argumente (Parameter) übergeben. Seltener kommt es vor, dass eine Funktion keine Argumente benötigt.
Wir wissen, dass die Funktion len() die Länge eines Objekts bestimmt. Also, wieviele Buchstaben hat dieses Wort/dieser Satz (str-Objekt), oder wieviele items sind in der Liste xyz.
Die Funktion gibt dann das Ergebnis (ein Objekt eines gewissen Typs) an die aufrufende Stelle zurück, sodass wir wieder einen identifier darauf zeigen lassen können. ODER, man setzt das zurückgegebene Objekt als Argument in enie andere Funktion ein, oder gibt es direkt mittels print() aus.

Eine Funktion in python gibt IMMER ein Objekt an die aufrufende Stelle zurück!
Wenn der Programmierer (mittels return) nicht hinschreibt, was zurückgegeben werden soll, dann gibt diese Funktion per default None zurück.

>>> def erste():
...     print ('fang mich')
... 
>>> text = erste()
fang mich
>>> print (text)
None
>>>

Wir sehen, dass die Funktionsdefinition mit dem keyword def eingeleitet wird. Dann kommen der Funktionsname und in runden Klammern die Argumente.
Im obigen Beispiel haben wir die Funktion aufgerufen. Diese hatte eine print()-function, aber kein return-statement (gibt also nix Besonderes zurück, ein Objekt OHNE WERT).
Es wurde dann der Versuch unternommen, diese Ausgabe von print() einzufangen, und text darauf zeigen zu lassen. Ging aber nicht, denn das str-Objekt wird in die Ausgabe (stdout) geschickt, und auffangen kann man nur das, was return zurückwirft. Wenn kein return angeführt wurde, dann ist es so, als würde ein
return None
die Funktion beenden. Und somit zeigte text auf das zurückgeworfene None-Objekt.
Ich kann nur auf das Objekt referenzieren, das die Funktion zurückgibt, aber nicht auf die Ausgabe, die diese Funktion produzierte.

>>> def zweite():
...     return 'fang mich'
... 
>>> text = zweite()
>>> print (text)
fang mich
>>>

In der Fachwelt der Programmierei wird (gern) zwischen Funktion und Prozedur unterschieden.
function (gibt return value an caller (Funktions-Aufrufer) zurück) und procedure (function, die keinen Wert an Aufrufer zurückliefert) - Für mich ist es so, dass es sich in python immer um eine Funktion handelt, da ja auch immer 'was zurückgeeben wird, auf das man einen Namen zeigen lassen kann.
Andere meinen, dass auch in python Funktionen ohne explizites return eine Prozedur sei --> ein Streit um des Kaisers Bart.

Unterhalb der def-Zeile (mit Funktionsnamen und in Klammern gefasste Argumentliste) folgt der Funktionskörper. Ein unter der def-Zeile gleichweit eingerückter (indented) Block.
Der kann mit einem sogenannten docstring (Info-/Hilfetext) beginnen. Von der python-Foundation wird also coding-style-Vorgabe vorgeschlagen, noch eine Leerzeile unterhalb der def-Zeile einzufügen. Muss gestehen, dass ich mich selten bis gar nie daran halte (sollte ich ändern).

>>> def hilfe_doc():
...     ''''
... ich bin ein docstring
...     '''
...     return 'doctest'
...
>>> help(drittes)

Help on function drittes in module __main__:

drittes()
    '
    ich bin ein docstring

Mit q kommt man wieder aus dem Hilfetext 'raus.
Der docstring landet dann in namespace der function im __doc__.

>>> print (hilfe_doc.__doc__)
'
ich bin ein docstring

>>>

Bei der Definition der Funktion legt man fest, ob sie (k)ein oder mehrere Parameter (Argumente) übergeben bekommen soll, wobei das zu übergebende Objekte jeglichen Typs sein kann.

Beim Aufruf der Funktion wird eine eigene name->Objekt-Tabelle vom Typ dict als eigener namespace angelegt.
Trifft der Interpreter auf einen Namen in der Funktionsdefinition, sucht der Interpreter

.
Das schreibt sich so einfach, aber diese Sache mit den namespaces muss erstmal *sickern*, also kein Problem, wenn Du das nicht auf Anhieb verstehst. Zumindest war es bei mir so, dass ich einige Zeit brauchte, um da VOLL durchzublicken.

Wir unternehmen nun den Versuch, eine vorhandene built-in-function nachzubauen. Meine Wahl traf hier auf die built-in-function abs().
abs() gibt den Betrag einer Zahl wieder.

>>> print (abs(-5))
5
>>> print (abs(5))
5
>>> print (abs(-5.2))
5.2
>>>

Wie könnte nun unsere Funktion aussehen?

>>> def own_abs(n):
...     '''
...     returns in any case the positive value of an int/float.
...     >>> own_abs(-5)
...     5
...     >>>
...     '''
...     return n if n >= 0 else -n
...
>>> print (own_abs(-3.14))
3.14
>>>

Unser own_abs() funktioniert bis auf eine Ausnahme (komplexe Zahlen) gleich wie abs(). Okay, wir haben es jetzt nicht abgesichert gegen *falsche* (unpassende) Objekte, wie str oder anderes unpassende Objekte übergeben werden.
Du siehst aber, wie simpel es ist, eine Funktion zu schreiben.

>>> print (abs(2+2j))
2.82842712475
>>>

own_abs() lässt sich nun an mehreren Stellen aufrufen, und der große Vorteil ist der, dass wir den code nur ein Mal tippen mussten, und diesen öfter einsetzen können (re-usability of code).

Noch ein bekanntes Beispiel:

>>> def kehrwert(x):
...     return ((x == 0) and 'error') or 1/x

>>> print (kehrwert(0))
error
>>> print (kehrwert(0.2))
5.0
>>> print (kehrwert(21))
0.047619047619
>>>

EINE FUNKTION TUT IN DER REGEL EIN DING.
Also keine eierlegenden Wollmilchsäue als Funktion schreiben.

>>> import math
>>> math.factorial(4)
24
>>>

factorial() gibt es zwar schon im math-library, doch hier ein weiterer einfacher Nachbau durch eine eigene Funktion, zwecks Training. Später werden wir auf noch einfachere Nachbauten dieser Funktion stoßen). Irgendwann werden wir aber damit aufhören, weil es sich empfiehlt, die factorial()-Funktion des math-Moduls stattdessen einzusetzen.

def fakt(n):
    '''
        returns the factorial from n
        Syntax: fraktal(n)
    '''
    erg = 1
    while n > 0:
        erg *= n
        n -= 1
    return erg

>>>

Positionsparameter

>>> def zaehle(eins,zwei,drei):
...     print (eins,zwei,drei)
... 
>>> zaehle(1,2,3)
1 2 3
>>>

Jeder Parameter hat eine fixe Position (fixe Stelle) innerhalb der runden Klammern -> Positionsparameter. Und somit kann jedes übergebene Objekt mit dem richtigen (vorgesehenen) Namen verbunden werden
Würde die Reihenfolge der übergebenen Parameter falsch bzw. anders sein, würde falsch gezählt werden.
Wenn mehr Parameter oder weniger der Funktion übergeben werden als in der Funktionsdefinition vorgesehen, so endete das mit einer TypeError-Exception.

>>> zaehle(1,2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: zaehle() takes exactly 3 positional arguments (2 given)
>>>

aha - die reden von arguments; Parameter vs Arguments; hm - ich *glaub* beides sind Wörter fürs selbe, oder?
Die Parameter können bereits default-values haben.

>>> def zaehle(erstes=0,zweites=1,drittes=2):
        '''
        syntax: zaehle([erstes=n[,zweites=n[,drittes=n]]])
        '''
...     print (erstes,zweites,drittes)
...
>>> zaehle()
0 1 2
>>> zaehle(zweites=8)
0 8 2
>>> zaehle(drittes=22,zweites=8)
0 8 22
>>>

Das Setzen von default-Werten ist doppelt praktisch: Zum einen kann dadurch ein Parameter beim Aufruf der Funktion ausgelassen werden, und zum anderen kann man das gleich als keyword-argument heranziehen.

parameter by name (keyword arguments)

Man kann beim Aufruf der Funktion (wie oben gezeigt) auch explizit param1=xyz schreiben.

>>> zaehle(drittes=5,erstes=3,zweites=4)
3 4 5
>>>

In diesem Fall braucht man beim Übergeben der Parameter nicht mehr auf die Reihenfolge zu achten.
Du kannst aber nicht so ohne weiteres Positionsparameter und param by name variieren, wie es uns im nächsten Beispiel vorgeführt wird.

>>> zaehle(4,drittes=5,erstes=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: zaehle() got multiple values for keyword argument 'erstes'
>>> zaehle(4,drittes=5,zweites=3)
4 3 5
>>> zaehle(drittes=5,4,erstes=3)
  File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
>>>

Zuerst bekamen wir die Meldung got multiple values for keyword argument 'erstes'.
Klar. Wenn ich an die erste Position ein Objekt übergebe, dann wird intern erstes auf das übergebene Objekt referenziert. Wenn dann aber noch zusaätzlich erstes=<object> steht (keyword argument), dann geht die Eindeutigkeit verloren, denn es gibt plötzlich zweimal die Anweisung, den Namen erstes auf ein Objekt zu referenzieren.
Ich behaupte 'mal, dass das zu 99.9% ungewollt ist. Der python-Interpreter sieht das genauso und raised daher eine Exception, mit der er die Programmiererin darauf aufmerksam macht.

Im zweiten Beispiel hat es geklappt - keine Zweideutigkeiten mehr. Positionsparameter vor keyword arguments.
und im letzten Beispiel kracht es mit einem SyntaxError und einer ganz klaren Ansage: non-keyword arg after keyword arg. Der Interpreter regt sich also nicht auf, weil der Positionsparameter an zweiter Stelle steht und danach noch zweites referenziert wird, was wir ja schon weiter oben hatten (multiple values). Nein - er sagt ganz klar: KEINE Positionsparameter NACH keyword arguments!

param by name läuft im Fachjargon als keyword argument. Sehr praktisch, wenn jeder Parameter einen default-value hat, und die Reihenfolge, in der die einzelnen Parameter übergeben werden, egal sein soll!

>>> zaehle(erstes=0.5,drittes=1.5)
0.5 1 1.5
>>>

Na, ja - ein besseres Beispiel, um die Nützlichkeit hervorzuheben ist folgendes kurz skizziert (pseudocode-like).
Abfrage von files im CWD;

def fileinfo(size=False, create_date=False, mod_date=False, owner=False[, ... ]):
    '''
       docstring
    '''
    # get file name
    # und dann
    if size:
        #getsize - add to list
    elif create_date:
        #get cdate add to list
    elif ...
        ...
    return file_info_structure_from_cur_dir

Alles, was beim Aufruf der Funktion auf True gesetzt wird, wird dann bei der fileinfo ausgegeben.
Code ist auch leichter lesbar und anwendbar (beim Funktionsaufruf)

Funktionen können so auch sehr unflexibel/flexibel gestaltet werden. Hier ein Beispiel, das ich mir von Pro Python (p. 54) ausborge:

>>> def add_prefix(string):
...     '''
...     adds a 'pro_'-prefidx before the string provided
...     '''
...     return 'pro_' + string
...
>>>

Der Prefix pro_ ist hier fix in Stein gemeißelt (hard coded). Wollte jemand anderer Deinen code wiederverwenden wollte, müsste sie zuerst die Funktion umschreiben (anpassen), denn pro_ passte zwar für Deine Applikation, aber nicht für andere. Denke kurz nach; wie könnte die Funktion aussehen, wo der Prefix variabel gehalten werden kann und dennoch Deinen Ansprüchen genügt?
So geht's

>>> def add_prefix(string, prefix='pro_'):
...     '''
...     Adds a prefix before the string provided
...     '''
...     return prefix + string
...
>>> print (add_prefix('michael'))
pro_michael
>>> print (add_prefix('michael', 'herr '))
herr michael
>>>

Damit haben wir einen zufriedenstellenden Grad an Flexibilität erreicht, und Deine Applikation läuft so, wie Du das wolltest.

Funktionsname als Parameter

>>> from math import tan, exp, sqrt
>>> #f ist eine math-function
>>> def berechne_liste(f,n):
...     ergebnis = list(range(n))
...     for i in range(n):
...         ergebnis[i] = f(i)
...     return ergebnis
...
>>> berechne_liste(tan,5)
[0.0, 1.5574077246549023, -2.185039863261519, -0.1425465430742778, 1.1578212823495775]
>>> berechne_liste(exp,5)
[1.0, 2.718281828459045, 7.38905609893065, 20.085536923187668, 54.598150033144236]
>>> berechne_liste(sqrt,5)
[0.0, 1.0, 1.4142135623730951, 1.7320508075688772, 2.0]
>>>

Oder, ein Mix aus Positionsparameter mit keyword-arguments:

>>> def wertetabelle(f, max=3, step=0.5):
...     x=0
...     while x <= max:
...         print (x, '\t', f(x))
...         x += step
...
>>> from math import sin
>>> wertetabelle(sin, max=1, step=0.2)
0        0.0
0.2      0.198669330795
0.4      0.389418342309
0.6      0.564642473395
0.8      0.7173560909
1.0      0.841470984808
>>>

Man hätte die Funktion auch so aufrufen können:

>>> wertetabelle(step=0.2, f=sin, max=1)
>>>

variable Anzahl von Argumenten

*args

Es kann passieren, dass man die Anzahl der Argumente, die der Funktion übergeben werden sollen, beim Erstellen des Funktionsobjekts nicht kennt.

Das löst man mit *args, oder bei den Positionsparamtern könnte am Ende *args stehen; da werden alle Objekte in einem tuple gesammelt. Hier nun ein erster Test:

>>> def test(a,b=3,*weissnicht):
...     print (a,b,repr(weissnicht))
...
>>> test(20,'ich','du',8,'manchmal','hofrat',2*10)
20 ich ('du', 8, 'manchmal', 'hofrat', 20)
>>> test(4)
4 3 ()
>>>

Das zeigte uns, was es tut. Ein konkreteres Beispiel könnte folgendes sein:
Wir sind Einkaufen im Supermarkt, und führen einen Einkaufswagen mit. Da wir objektorientiertes Programmieren noch nicht können/kennen, werden wir uns eine Funktion konstruieren, ide uns hilft, Waren in den Einkaufswagen zu bekommen.

>>> einkaufswagen = []
>>> def in_wagen(ware):
...     '''
...     legt ware in den einkaufswagen
...     '''
...     einkaufswagen.extend(ware)
...     return None
...
>>> in_wagen('milch')
>>> in_wagen('kaese')
>>> in_wagen('kekse', 'salz')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: in_wagen() takes exactly 1 positional argument (2 given)
>>>

Unsere Funktion tut das, was wir wollen. Es legt eine Ware in den Einkaufswagen. Aber, wie oft kommt es vor, dass wir mehr als ein Produkt gleichzeitig in den Einkaufswagen legen wollen? Öfter, als wir glauben. Wieviele auf einmal? Das wissen wir vorher nicht -> unser Argument hat also eine variable Länge:

>>> def in_wagen(*ware):
...     '''
...     legt ware in den einkaufswagen
...     '''
...     einkaufswagen.extend(ware)
...     return None
...
>>> print (einkaufswagen)
['m', 'i', 'l', 'c', 'h', 'k', 'a', 'e', 's', 'e']
>>> einkaufswagen=[]
>>> in_wagen('salz','butter','milch','tee')
>>> print (einkaufswagen)
['salz', 'butter', 'milch', 'tee']
>>>

Nicht nur, dass wir für jedes Produkt einen function-call brauchten, es hat uns auch noch die den string in einzelne characters zerlegt und so in die Liste eingefügt.
Die zweite Variante sammelte alle Waren, die wir mit einem Arm halten konnten in einem Tuple-Objekt (*ware) und legte diese gemeinsam in den Einkaufswagen.

Variable Anzahl von keyword arguments

**kw

Es gibt dann noch den Parameter, den zwei ** vorangestellt werden.
Das sind dann beliebig viele kw=value Paar-Angaben (keyword-Arguments), die als dict in die Funktion kommen.

>>> def funk(**kw):
...     print (kw)
...     print (type(kw))
...
>>> funk(a='1',b='2',c='3')
{'a': '1', 'c': '3', 'b': '2'}
<class 'dict'>
>>>

Folgendes Beispiel hab' ich irgendwann von irgendwo herausgeschrieben.

>>> def whatmoon(**moon):
...     for planet in moon:
...         print (planet, ' : ', moon[planet])
...
>>> whatmoon(erde='mond', mars='phobos and deimos')
erde  :  mond
mars  :  phobos and deimos
>>>

Wieder einmal beeindruckend, was man alles machen kann!
Hier muss man u.U. aufpassen, denn bei *args hatten wir ein tuple, welches immuteable ist. Hier aber haben wir nach **kw ein dictionary, welches muteable ist - es könnte also zu Datenverlust kommen, wenn man da nicht aufpasst!

Deutlich zu sehen ist die variable Anzahl der keyword-arguments (key=value), und dass diese im Funktionsblock (als Gesamtheit) wie ein dict behandelt werden.

Kombinationen der Argumenttypen

Jetzt haben wir doch einiges über die diversen Argumente kennengelernt, mit denen man eine Funktion in python füttern kann. Die lassen sich auch kombinieren, aber da hat python ein paar Regeln, die wir uns hier ansehen werden.
Im Grunde kennen wir folgende Argument-Typen:

Es ist sinnvoll, die (verpflichtend anzugebenden) Positionsparameter ganz an den Anfang der gesamten Argumenteliste zu stellen. Danach jene mit einem default-Wert versehen Argumente (keyword-arguments), die man angeben kann, aber nicht muss. Und zuguterletzt die variablen Positionsparameter und keyword-arguments.
Zum Beispiel:

>>> def bsp_func(eins, zwei='',*drei, **vier):
...     pass
...
>>>

Man kann, wenn man wollte, die variablen Positionsparameter (*args) auch vorangstellen, und dann könnten dem nur noch keyword-arguments oder/und variable keyword-argumets (**kw) folgen. Folgte ein Positionsparameter, dann müste dieser beim Aufruf der Funktion wie ein keyword-Argument mittels (key=value) eingetragen werden!

>>> def bsp2_func(*vier, drei='',zwei='', **eins):
...     pass
...
>>> bsp2_func(18,23,9,neun='acht')
>>>

Das funktionierte, weil es keine Positionsparameter extra zu behandeln galt.

>>> def bsp3_func(*vier, drei, zwei='', **eins):
...     pass
...
>>> #kein Error -> func-definition passt also
>>> bsp3_func(1,2,3,4,5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bsp3_func() needs keyword-only argument drei
>>> bsp3_func(1,2,3,4,5, drei='ach')
>>> 

Jetzt ist es wohl allen ganz klar, worauf es ankommt!
Setzen wir vielleicht nocheinmal so eine prefix-function ein:

>>> def prefix2_func(prefix, *textbausteine,  wortrenner):
...     return wortrenner.join([ baustein for baustein in textbausteine])
...
>>> prefix2_func('Herr', 'michael','poeltl',' ')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: prefix2_func() needs keyword-only argument wortrenner
>>> prefix2_func('Herr', 'michael','poeltl',wortrenner=' ')
'michael poeltl'
>>>

Wieder bekamen wir die Bestätigung, dass beim Aufruf der Funktion prefix2_func() unser Positionsparameter als keyword-argument einzutragen ist (keyword-only argument wortrenner).

global,nonlocal

die built-in-functions vars() gibt namespace (scope) als dict zurück.
(dir() spuckt 'quasi' nur die keys von vars() aus)

>>> dir(math)
['__doc__', '__file__', '__name__', '__package__', 'acos', 'acosh',
'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos',
'cosh', 'degrees', 'e', 'exp', 'fabs', 'factorial', 'floor', 'fmod',
'frexp', 'fsum', 'hypot', 'isinf', 'isnan', 'ldexp', 'log', 'log10',
'log1p', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan',
'tanh', 'trunc']
>>> list(vars(math).keys())
['pow', 'fsum', 'cosh', 'ldexp', 'hypot', 'acosh', 'tan', 'asin', 'isnan',
'log', 'fabs', 'floor', 'atanh', 'modf', 'sqrt', '__package__', 'frexp',
'degrees', 'pi', 'log10', '__doc__', 'asinh', 'exp', 'atan', 'factorial',
'__file__', 'copysign', 'ceil', 'isinf', 'sinh', '__name__', 'trunc',
'cos', 'e', 'tanh', 'radians', 'sin', 'atan2', 'fmod', 'acos', 'log1p']
>>>

oh - die Reihenfolge passt noch nicht - müsst 'mir
sorted(list(vars(math).keys()))
schreiben, damit es gleich wie bei dir(math) 'rauskommt.

Jeder Funktionsaufruf *bewirkt* einen Funktionsnamespace.
Gleichzeitig hat aber die Funktion auch vollen Zugriff auf den globalen namespace.
wird ein Name in der Funktion definiert, dann kommt dieser Name in den function-scope -
Gibt es denselben Namen im functon-scope und im globalen scope, dann kommt der im function-scope zum Zuge.
will man in einer Funktion einen globalen Namen verändern, dann muss man das explizit anführen.

>>> def lokal():
...     x=1
...     y=2
...     print (locals())
... 
>>> lokal()
{'y': 2, 'x': 1}
>>> def lokal():
...     x=1
...     y=2
...     print ('locals() is: {},\nvars() is {}\nglobals() is {}'.format(locals(),vars(),globals()))
>>> lokal()
locals() is: {'y': 2, 'x': 1},
vars() is {'y': 2, 'x': 1}
globals() is {'__builtins__': , '__package__': None, 'atexit': , 'lokal': , 'x': 'global', 'y': 'global', '__name__': '__main__', '__doc__': None}
>>> 
>>> def changex():
...     global x
...     x = 'hurra'
... 
>>> vars()['x']
'global'
>>> changex()
>>> vars()['x']
'hurra'
>>>

globals() gibt den globalen scope aus; dieser Funktion kann man keine Argumente übergeben (locals() auch nicht).
vars() gibt den *aktuellen* (current) namespace aus oder jenen, der per übergebenen Parameter angefragt wurde, wenn dieser Teil des aktuellen namespace ist).

Somit haben wir also:
dir()
globals(), locals(), vars()
ach jo - es fehlt noch
nonlocal

nonlocal neu dazugekommen mit python3
syntax:
nonlocal name[, name[, ...]]
hinter nonlocal steht ein Name oder mehrere durch Beistrich getrennt

Die Namen nach nonlocal beziehen sich auf den naechsthöheren namespace.
Einfach gestricktes Beispiel:

>>> def eins():
...     x = 1
...     def zwei():
...       nonlocal x
...       print (x)
...     zwei()
...
>>> eins()
1
>>>

Die Funktion zwei() sieht man nur im scope von der eins() - ist also *von aussen* NICHT ansprechbar/sichtbar.

Funktionen testen

Mithilfe des profile-Moduls kann man die performance einer Funktion testen.
Wir wenden uns zuerst der run()-Funktion zu.

>>> import profile
>>> profile.run("whatmoon(erde='mond', mars='phobos and deimos')")
erde  :  mond
mars  :  phobos and deimos
         7 function calls in 0.000 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 :0(exec)
        2    0.000    0.000    0.000    0.000 :0(print)
        1    0.000    0.000    0.000    0.000 :0(setprofile)
        1    0.000    0.000    0.000    0.000 :1(whatmoon)
        1    0.000    0.000    0.000    0.000 :1()
        0    0.000             0.000          profile:0(profiler)
        1    0.000    0.000    0.000    0.000 profile:0(whatmoon(erde='mond', mars='phobos and deimos'))

>>>

Hier geht es zum Seitenanfang und da geht es zurück zur python-Übersicht