Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Funktionen

Leverage the power of code recycling with functions. For interactive reading and executing code blocks Binder and find b04-pyfun.ipynb, or install Python and JupyterLab locally.

Was sind Funktionen?

Funktionen sind eine bequeme Möglichkeit, Code in handliche, wiederverwendbare und besser lesbare Blöcke zu teilen, die helfen, Code zu strukturieren. Funktionsblöcke können parametrische Argumente annehmen und wiederverwendbar sind. So sind Funktionen ein Schlüsselelement zum Teilen von Code und Arbeiten in Teams. Die Grundstruktur einer Python-Funktion beinhaltet:

  • Ein def Schlüsselwort, gefolgt von einem Funktionsnamen mit Arguments in Klammern und einem Codeblock.

  • Die Art der Argumente, die eine Funktion erhalten kann, sind:

    • Erforderliche Argumente: arg

    • Default Keyword Argumente (mit Standardwerten): arg=value

    • Optionale Argumente: *args

    • Optionale Keyword Argumente: **kwargs

Mit optionalen (Schlüsselwort-)Argumenten werden Funktionen robuster und flexibler. Der Codeblock einer Funktion wird ähnlich wie Schleifen eingezeichnet:

def my_function(argument1, *args, **kwargs):
    something = ...  # do something with argument1, *args, and **kwargs
    return something

Ein Grundbeispiel

Nur eine Handvoll Länder (insbesondere die Vereinigten Staaten) nutzen im Alltag noch imperiale Einheiten, während die meisten Länder die Système international d’unités (SI-Einheiten) verwenden. Lassen Sie uns eine einfache Funktion schreiben, um den imperialen Einheitsbenutzern zu helfen, feet (imperial) in meter (SI) zu konvertieren.

Im folgenden Beispiel ist der Funktionsname feet_to_meter und die Funktion akzeptiert ein Argument, das feet ist. Die Funktion gibt das feetArgument multipliziert mit einem conversion_factor von 0.3048 zurück, was dem Umrechnungsfaktor von Fuß zu Metern entspricht. In diesem einfachen Beispiel kann die conversion_factor-Variable nicht extern modifiziert werden und existiert nur im namespace der Funktion.

def feet_to_meter(feet):
    conversion_factor = 0.3048
    return conversion_factor * feet

Funktion Anrufe

Um eine Funktion zu rufen, muss sie vor dem Aufruf definiert werden. Die Funktion kann im gleichen Skript oder in einem anderen Skript definiert werden, das dann als Modul (read more about modules and packages in the next section) importiert werden kann. Dann können wir beispielsweise die oben definierte feet_to_meter-Funktion wie folgt anrufen:

feet_value = 10
print("{0} feet are {1} meters.".format(feet_value, feet_to_meter(feet_value)))
10 feet are 3.048 meters.

Optionale Argumente *args

Das nicht-optionale feetArgument in der obigen Funktion mit einem optionalen Argument *args ermöglicht die Umwandlung von so vielen Längenwerten, wie die Funktion erhält. Die folgenden Zeilen erklären Schritt für Schritt, wie das funktioniert.

  1. Stellen Sie sicher, dass jeder die Eingabe- und Ausgabeparameter der Funktion versteht, indem Sie inline docstrings mit einem Paar von dreifachen Doppelapostrophen (""") einfügen, die Eingabeparameter (:params parameter_name: definition) und die Funktionsrückgabe (:output: definition) umfassen.

  2. Standardmäßig gehen wir davon aus, dass mehrere Werte bereitgestellt werden. Daher wird eine Liste mit dem Namen value_list zu Beginn der Funktion sofort erstellt, während conversion_factor dieselbe bleibt wie bisher.

  3. Ein for-loop über *args identifiziert und verarbeitet die Argumente. Warum ein Vorspiel?
    Python sammelt automatisch *args in ein Tupel, und deshalb können wir über *args iterieren, auch wenn die angegebenen Werte nicht als Liste oder Tupel weitergegeben wurden.

  4. The for-loop in the try code block includes a try - except statement to verify if the provided values (arguments) are numeric and can be converted to meters. If the try block runs successfully, the expression arg * conversion_factor appends the converted argument arg to value_list.

  5. Schließlich gibt das returnword die Wertliste zurück.

def feet_to_meter(*args):
    """ 
    :param *args: numeric values in feet
    :output: returns list of values in meter
    """
    value_list = []
    conversion_factor = 0.3048
    for arg in args:
        try:
            value_list.append(arg * conversion_factor)
        except TypeError:
            print(str(arg) + " is not a number.")
    return value_list

Mit der neu definierten und flexibleren Funktion können wir jetzt feet_to_meter mit so vielen Argumenten wie nötig anrufen:

print("Function call with 3 values: ")
print(feet_to_meter(3, 1, 10))

print("Function call with no value: ")
print(feet_to_meter())

print("Function call with non-numeric values:")
print(feet_to_meter("just", "words"))

print("Function call with mixed numeric and non-numeric values:")
print(feet_to_meter("just", "words", 2))
Function call with 3 values: 
[0.9144000000000001, 0.3048, 3.048]
Function call with no value: 
[]
Function call with non-numeric values:
just is not a number.
words is not a number.
[]
Function call with mixed numeric and non-numeric values:
just is not a number.
words is not a number.
[0.6096]

Optionale Keyword Argumente **kwargs

In den letzten Absätzen haben wir die feet_to_meterfunktion flexibler gemacht, so dass sie jetzt so viele Argumente wie nötig erhalten kann. Bisher kann die interne conversion_factor-Variable nicht von außerhalb der Funktion geändert werden, was die Flexibilität begrenzt. Stellen Sie sich vor, dass wir diese Funktion für einen Historiker schreiben. In der Vergangenheit waren kaiserliche Einheiten in vielen Kulturen (z.B. Griechisch, Roman oder Chinesisch) mit unterschiedlichen Längendefinitionen zwischen 0,250 m und 0,335 m weit verbreitet. Das bedeutet, dass der Historiker Flexibilität in Bezug auf den Umrechnungsfaktor braucht, während wir weiterhin 0,3048 m als Standardwert verwenden wollen. Diese Anforderung kann mit optionalen Keyword-Argumenten **kwargs umgesetzt werden und so funktioniert sie im Code-Block unten:

  1. **kwargs nach *args in der Funktion def parentheses (die Reihenfolge der *args, **kwargs ist wichtig).

  2. Behalten Sie conversion_factor = 0.3048 als Standardwert (wir wollen, dass die Funktion auch ohne Keyword-Argument funktioniert).

  3. Ähnlich wie bei der *args-Anweisung identifiziert Python automatisch Variablen, die mit ** als optionale Keyword-Argumente beginnen (in der Tat spielt der Name args und kwargs keine Rolle - die *-Zeichen sind wichtig). Der Unterschied zu *args ist, dass Python als Wörterbuch **kwargs identifiziert.

  4. Ein for-loop iteriert über den kwargs-Dictionary und die if-Anweisung identifiziert jedes optionale Keyword-Argument, das den String "conv" als Conversion factor enthält.

  5. Eine try-except-Anweisung testet, wenn der angegebene Wert für das Keyword-Argument numerisch ist, indem man eine Konvertierung an float() versucht.

Der Rest der Funktion bleibt unverändert.

def feet_to_meter(*args, **kwargs):
    """ 
    :param *args: numeric values in feet
    :output: returns list of values in meter
    """
    value_list = []
    conversion_factor = 0.3048
    for key, value in kwargs.items():
        if "conv" in key:
            try:
                conversion_factor = float(value)
                print("Using conversion factor = " + str(value))
            except (ValueError, TypeError):
                print(str(value) + " is not a number (using default value 0.3048).")
    
    for arg in args:
        try:
            value_list.append(arg * conversion_factor)
        except TypeError:
            print(str(arg) + " is not a number.")
    return value_list

Testen Sie verschiedene Conversion-Faktoren mit der neu definierten Flexibilität der feet_to_meter-Funktion:

print("Function call with 3 values and a conversion factor of 0.25: ")
print(feet_to_meter(3, 1, 10, conv_factor=0.25))

print("Function call with 3 values and a conversion factor of 1/7 with slightly different name: ")
print(feet_to_meter(3, 1, 10, conversion_factor=1/7))

print("Function call with 2 values with default conversion factor: ")
print(feet_to_meter(25, 10))
Function call with 3 values and a conversion factor of 0.25: 
Using conversion factor = 0.25
[0.75, 0.25, 2.5]
Function call with 3 values and a conversion factor of 1/7 with slightly different name: 
Using conversion factor = 0.14285714285714285
[0.42857142857142855, 0.14285714285714285, 1.4285714285714284]
Function call with 2 values with default conversion factor: 
[7.62, 3.048]

Default Keyword Arguments

Keyword Argumente können auch standardmäßig definiert werden. Das nachfolgende Beispiel zeigt, wie die conversion_factor in den def-Funktions-Parthesen standardmäßig definiert werden kann. Beachten Sie, dass conversion_factor nach optionalen Argumenten *args definiert werden muss.

def feet_to_meter(*args, conversion_factor=0.3048):
    """ 
    :param *args: numeric values in feet
    :output: returns list of values in meter
    """
    value_list = []
   
    for arg in args:
        try:
            value_list.append(arg * conversion_factor)
        except TypeError:
            print(str(arg) + " is not a number.")
    return value_list

Jetzt können wir feet_to_meter mit oder ohne oder mit einem Umrechnungsfaktor und nach einer Werteliste verwenden:

print("Function call with a conversion factor of 0.313 and two values: ")
print(feet_to_meter(1, 10, conversion_factor=0.313))
                    
print("Function call with 3 values without any conversion factor: ")
print(feet_to_meter(3, 1, 10))
Function call with a conversion factor of 0.313 and two values: 
[0.313, 3.13]
Function call with 3 values without any conversion factor: 
[0.9144000000000001, 0.3048, 3.048]

Funktion Wrappers und Decorators

Wenn mehrere Funktionen ähnliche Linien enthalten, sind Chancen, dass diese Funktionen durch die Verwendung von Funktionsumschlägen und Dekoratoren weiter faktorisiert werden können. Ein typisches Beispiel ist ein Lizenz-Checkout (z.B. zur Verwendung eines kommerziellen Python-Moduls/Pakets, wie Esris arcpy) oder wenn wir eine wiederkehrende Fehlermeldung mit try - except-Anweisungen verwenden möchten.

Betrachten Sie beispielsweise zwei oder mehr Funktionen, die numerische Ausgabe von der Benutzereingabe empfangen, verarbeiten und erzeugen sollen. Diese Funktionen können so aussehen:

def multiply_arguments(*args):
    result = 1.0
    try:
        for arg in args:
            result *= arg
        print("The result is: " + str(result))
    except TypeError:
        print("ERROR: The calculation could not be performed (input arguments: %s)" % str(args))
    except ValueError:
        print("ERROR: The calculation could not be performed (input arguments: %s)" % str(args))
    return result

def sum_up_arguments(*args):
    result = 0.0
    try:
        for arg in args:
            result += arg
    except TypeError:
        print("ERROR: The calculation could not be performed (input arguments: %s)" % str(args))
    except ValueError:
        print("ERROR: The calculation could not be performed (input arguments: %s)" % str(args))   
    return result

Beide Funktionen beinhalten die Aussage print("The result is: " + str(result)), um die Ergebnisse an die Python-Konsole zu drucken (z.B. um Zwischeninformationen zu erhalten) und nur auf gültigen (d.h. numerischen) Eingaben mit Ausnahme (try - except) Aussagen zu laufen. Wir möchten jedoch, dass unsere Funktionen sich nur auf die Berechnung konzentrieren, und hier hilft eine Wrapper-Funktion.

Eine Wrapper-Funktion kann definiert werden, indem zunächst eine Standardfunktion definiert wird (z.B. def verify_result) und dann eine weitere Funktion (func) als Argument übergeben wird. In dieser Funktion (verify_result) können wir dann eine verschachtelte def wrapper()-Funktion aufstellen, die func umarmt. Es ist wichtig, sowohl optional *args als auch optionales Keyword **kwargs in der Wrapper-Funktion und den Anruf an func zu verwenden, um den Wrapper so flexibel wie möglich zu gestalten.

def verify_result(func):
    def wrapper(*args, **kwargs):
        try:
            result = func(*args, **kwargs)
            print("Success. The result is %1.3f." % float(result))
            return result
        except TypeError:
            print("ERROR: The calculation could not be performed because of at least one non-numeric input (input arguments: %s)" % str(args))
            return 0.0
        except ValueError:
            print("ERROR: The calculation could not be performed because of non-numeric input (input arguments: %s)" % str(args))
            return 0.0
    return wrapper

Jetzt können wir einen @-decorator verwenden, um die oben genannten math-Funktionen in der verify_result(fun)-Funktion einzupacken. Wenn Python das schöne, codedekorierende @-Zeichen liest, sucht es automatisch die nach dem @-Zeichen definierte Umschlingungsfunktion, um die folgende Funktion zu wickeln.

@verify_result
def multiply_arguments(*args):
    result = 1.0
    for arg in args:
        result *= arg
    return result

@verify_result
def sum_up_arguments(*args):
    result = 0.0
    for arg in args:
        result += arg
    return result

Die beiden Funktionen (multiply_arguments und sum_up_arguments) können wie üblich aufgerufen werden:

multiply_arguments(3, 4)
multiply_arguments(3, 4, "not a number")
sum_up_arguments(3, 4)
sum_up_arguments("absolutely", "no", "valid", "input")
Success. The result is 12.000.
ERROR: The calculation could not be performed because of at least one non-numeric input (input arguments: (3, 4, 'not a number'))
Success. The result is 7.000.
ERROR: The calculation could not be performed because of at least one non-numeric input (input arguments: ('absolutely', 'no', 'valid', 'input'))
0.0

Die obige Wrapperfunktion gibt auch die gewickelten Funktionsergebnisse zurück. Um jedoch eingebaute Funktionsattribute zu verwenden (z.B. den Namen der Funktion mit __name__, die Funktion docstring mit __doc__ oder das Modul, in dem die Funktion mit __module__) außerhalb des Wrappers definiert ist, benötigen wir die Wrapperfunktion, um die eingewickelte (dekorierte) Funktion selbst zurückzugeben. Dies kann wie folgt geschehen:

def error_func(*args, **kwargs):
    return 0.0

def verify_result(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except TypeError:
            print("ERROR: The calculation could not be performed because of at least one non-numeric input (input arguments: %s)" % str(args))
            return error_func(*args, **kwargs)
        except ValueError:
            print("ERROR: The calculation could not be performed because of non-numeric input (input arguments: %s)" % str(args))
            return error_func(*args, **kwargs)
    return wrapper

Beachten Sie den Unterschied: Die wrapper Funktion gibt nun statt der numerischen Variablen func(*arg, **kwargs) zurück. Wenn die Funktion wegen ungültiger Eingabe nicht ausgeführt werden kann, wird der Wrapper eine Fehlerfunktion (error_func) zurückgeben, die die Konsistenz der Wrapperfunktion gewährleistet. Man kann denken, dass die Fehlerfunktion, die 0,0 zurückgibt, veraltet ist, weil die Ausnahmeaussagen direkt 0,0 zurückgeben könnten. 0,0 ist jedoch eine float-Variable, während error_func eine Funktion ist und der Funktions-Wrapper immer denselben Datentyp zurückgeben sollte, unabhängig von einer Ausnahme-Höhe (Fehler) oder einer erfolgreichen Ausführung. Das macht Code konsequent.

Dieser Absatz zeigt Beispiele für die Verwendung der Dekorateure in Form eines @-Zeichens, um eine Funktion zu wickeln. Decoratoren sind auch in Python-Klassen nützlich, beispielsweise wenn eine Klassenfunktion statische Werte zurückgibt. Lesen Sie mehr über Dekorateure in Klassen später im Kapitel unter object orientation and classes.

Iteratoren und Generatoren

Ein charakteristisches Merkmal von list, tuple und dictionary-Datentypen ist ihre iterability, die durch ihre __iter__-in-Methode bereitgestellt wird. So ist die Iterability der Grund, warum wir schreiben können:

for e in [1, 2, 3]: print(e)
1
2
3

Neben Iterationen ermöglicht Python auch die Erzeugung von Generatoren (d.h. Generatorfunktionen). Anstelle einer return-Anweisung endet eine Generatorfunktion mit einer yield-Anweisung, die Daten zurückgibt, solange eine next()-Funktion (erheblicher Schritt in Iterationen) aufgerufen wird. Eine Anwendung eines Generators ist beispielsweise die Abflachung von geschachtelten Listen (d.h. Sublisten entfernen und alle Variablen direkt in eine nicht gesäte Liste schreiben):

from collections.abc import Iterable

def flatten(nested_list):
    for e in nested_list:
        if isinstance(e, Iterable) and not isinstance(e, str):
            for x in flatten(e):
                yield x
        else:
            yield e
            
a_nested_list = [[1, 2, 3], ["a", "b", "c"]]
flattened_list = list(flatten(a_nested_list))
print(flattened_list)
[1, 2, 3, 'a', 'b', 'c']

Lambda Funktionen

Lambda (λ) calculus ist eine formale Sprache zum Ausdruck der rechnergestützten Funktionsabstraktion und wurde in den 1930er Jahren von der mathematischen Alonzo Kirche eingeführt. Lambda-Funktionen stammen aus der funktionellen Programmierung und stellen kurze, anonyme (d.h. ohne Namen) Funktionen dar. Obwohl Python nicht inhärent eine funktionelle Programmiersprache ist, wurden funktionelle Konzepte frühzeitig in Python umgesetzt, z.B. mit dem map(), filter() und reduce() Funktionen sowie dem lambda Operator.

In Python kann eine anonyme (namelose) Lambda-Funktion jede Anzahl von Argumenten annehmen, kann aber nur einen Ausdruck haben. Die Argumente bestehen aus einer komma getrennten Variablenliste und der Ausdruck verwendet diese Argumente. Die syntax von lambdafunktionen ist:

lambda arguments : expression

Das folgende Beispiel veranschaulicht eine lambda-Funktion mit einem Argument und fügt 1 zum Argument hinzu:

add_one = lambda number : number + 1
print(add_one(1))
2

Das war nett, aber ganz nutzlos. Hier ist ein Beispiel für eine etwas nützlichere Lambda-Funktion, die zwei Eingabeargumente zusammenfasst:

sum_up = lambda x, y : x + y
print(sum_up(1, 5))
6

Die oben gezeigte Funktion zur Umwandlung von Füßen in Meter kann auch als Lambda-Funktion geschrieben werden:

feet_to_meter = lambda ft_value : ft_value * 0.3048
print(feet_to_meter(10))
3.048

Using a lambda function made the code shorter and more efficient. In addition, to evaluate the feet_to_meter lambda function for multiple values, we can use the map() function. The syntax of a map() function is:

result = map(function, sequence)

wobei sequence ein list oder ein tuple sein kann. So können wir ein tuple von vier Werten auswerten:

four_ft_values = (4, 9.7, 7, 2)
print(list(map(feet_to_meter, four_ft_values)))
[1.2192, 2.95656, 2.1336, 0.6096]

Die list()-Funktion konvertiert den map()-Ausgang in eine -Liste, um die map()-Funktion auszuwerten (andersfalls wäre das Ergebnis so wie <map object at ...>).

Wenn die feet_to_meter-Funktion nicht an einem anderen Ort im Code benötigt wird, kann man auch schreiben:

print(list(map(lambda x : x * 0.3048, (4, 9.7, 7, 2))))
[1.2192, 2.95656, 2.1336, 0.6096]

Ein weiteres Feature von Python ist die filter(function, list)-Funktion, die eine elegante Lösung darstellt, um diese Elemente aus einer Liste herauszufiltern, für die die Funktion True zurückgibt. Der folgende Codeblock zeigt eine filter, die alle Zahlen aus einer some_numbers-Liste eliminiert, die durch drei geteilt werden kann.

some_numbers = list(range(1, 10))
print(list(filter(lambda x: x % 3, some_numbers)))
[1, 2, 4, 5, 7, 8]

Früher wurde in Python die reduce()-Funktion zum Zusammenführen von Listeneingabe in einen Wert implementiert. Pythons Originalautor Guido van Rossum entfernte ihn jedoch aus dem eingebauten Namensraum in Python 3 – er lebt jetzt in functools.reduce (lesen Sie seinen Post), weshalb er hier nicht als eingebaut bezeichnet wird.

Erfolgsprüfung lernen

Nehmen Sie den Lernerfolgstest für dieses Jupyter Notebook.