Python *args **kwargs – Funktionen mit variabler Parameter Anzahl

Parameter in Python

Meistens verwendet man in Funktionen und Methoden von Python eine ganz konkrete Anzahl von Parametern. Ich halte das für die verständlichste und sauberste Art und Weise eine Funktion zu beschreiben. Jemand der diese Funktionen benutzen möchte, weiß somit, wie diese konkret anzusprechen sind und im Idealfall, welche Typen bzw Objekte an welcher Position zu übergeben sind oder welche Parameter optional (default parameter values, bzw Standardparameter oder voreingestellter Parameter) übergeben werden können.

Hier findet man die genaue Definition für Parameter bzw Argumente: https://docs.python.org/3/glossary.html#term-argument

Ein einfaches Beispiel mit festen positionsbezogenen Parametern (positional arguments) und voreingestellten Parametern (keyword arguments):

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
 
def main(a_string, an_int, a_bool=True, a_float=1.0):
    """
    this is the main function, it combines all given parameters and prints its values
 
    :param a_string: a string parameter (as first argument)
    :param an_int: an integer parameter (as second argument)
    :param a_bool: an optional boolean parameter (as keyword argument)
    :param a_float: an optional float parameter (as keyword argument)
    """
    print(a_string, an_int, a_bool, a_float)
 
 
if __name__ == "__main__":
    main("only arguments", 1337)
    main("arguments and keywords", 1337, a_bool=False, a_float=1.23)
    main("arguments and keywords not sorted", 1337, a_float=1.23, a_bool=False)
    main("arguments and some keywords", 1337, a_float=2.34)
    main("argument and one argument keyworded", an_int=1337, a_bool=False)
    main(1337, "argument type does not matter", "T", False)
    main("this will fail")

Führt man dieses einfache Script aus, so ergibt sich folgende Ausgabe:

$ ./simple.py 
('only arguments', 1337, True, 1.0)
('arguments and keywords', 1337, False, 1.23)
('arguments and keywords not sorted', 1337, False, 1.23)
('arguments and some keywords', 1337, True, 2.34)
('argument and one argument keyworded', 1337, False, 1.0)
(1337, 'argument type does not matter', 'T', False)
Traceback (most recent call last):
  File "./simple.py", line 24, in <module>
    main("this will fail")
TypeError: main() takes at least 2 arguments (1 given)

args / kwargs * Notation

Ändert man hier nun die Parameter so, dass man die */** Notation benutzt, muss für die Parameter ein * gesetzt werden direkt gefolgt von der Parameter-Bezeichnung (hier hat sich die Programmiergemeinde auf args geeinigt bzw für die voreingestellten Parameter ein Doppelstern ** gefolgt vom Parameter-Namen (meistens kwargs ). Dabei werden die Parameter als Tupel übergeben und sind durch einfachte Iteration ansprechbar, die voreingestellten Parameter werden als Dictionary übergeben und können über ihr Schlüsselwort angesprochen werden.
z.B.

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
 
def main(*args, **kwargs):
    """
    this is the main function, it combines all given parameters and prints its values
 
    :param args: arguments tuple
    :param kwargs: keywords dictionary
    """
    print(args, kwargs)
 
 
if __name__ == "__main__":
    main("only arguments", 1337)
    main("arguments and keywords", 1337, a_bool=False, a_float=1.23)
    main("arguments and keywords not sorted", 1337, a_float=1.23, a_bool=False)
    main("arguments and some keywords", 1337, a_float=2.34)
    main(1337, "argument type does not matter", "T", False)
    main("this won't fail anymore")

Die Ausgabe hierzu:

$ ./simple.py 
(('only arguments', 1337), {})
(('arguments and keywords', 1337), {'a_bool': False, 'a_float': 1.23})
(('arguments and keywords not sorted', 1337), {'a_bool': False, 'a_float': 1.23})
(('arguments and some keywords', 1337), {'a_float': 2.34})
(('argument and one argument keyworded',), {'a_bool': False, 'an_int': 1337})
((1337, 'argument type does not matter', 'T', False), {})
(("this won't fail anymore",), {})

Vor- und Nachteile

Der große Vorteil besteht hier in der Flexibilität. Man kann die Funktion beliebig mit Parametern erweitern, ohne dass der aufrufende Code angepasst werden ‚muss‘.

Andererseits liegt hier auch die Gefahr, dass eben zwingend nötige Parameter maskiert werden und für den Programmierer nicht mehr klar erkennbar sind. Im schlimmsten Fall führt dies zu Fehlern. Würden wir z.B. das letzte Beispiel so anpassen, dass wir einen Parameter ganz konkret ansprechen, der aber durch den aufrufenden Code nicht als Argument übergeben wird, erzeugt dies einen Fehler.

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
 
def main(*args, **kwargs):
    """
    this is the main function, it combines all given parameters and prints its values
 
    :param args: arguments tuple
    :param kwargs: keywords dictionary
    """
    print(kwargs["hidden_parameter"])
 
 
if __name__ == "__main__":
    main("only arguments", 1337)
    # ...

Und die Ausgabe:

Traceback (most recent call last):
  File "./simple.py", line 16, in <module>
    main("only arguments", 1337)
  File "./simple.py", line 12, in main
    print(kwargs["hidden_parameter"])
KeyError: 'hidden_parameter'

Insgesamt gibt es fünf verschieden Varianten Parameter in einer Funktion zu definieren.  In einem extra Beitrag  gibts es zu diesem Thema noch mehr Beispiele.  Hier findet man ausserdem die komplette Liste der möglichen Parameter: https://docs.python.org/3/glossary.html#term-parameter