Callback-Funktionen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
wrench139
User
Beiträge: 17
Registriert: Mittwoch 16. März 2022, 15:45

Hallo zusammen,

ich bin immer noch relativ unerfahren bzgl des Programmierens und bin nun auf das Thema der Callback-Funktion gestoßen.
Was ich bisher mitgenommen habe ist Folgendes: eine Callback-Funktion wird einer anderen Funktion als Übergabeparameter übergeben.
1. Frage: habe ich das soweit richtig verstanden?
2. Frage: wozu brauche ich dieses "Konstrukt"? Ich könnte die Callback-Funktion doch auch genauso gut innerhalb des Körpers der anderen Funktion einfach aufrufen ohne sie als Parameter zu übergeben. Hierzu habe ich mit folgenden Beispielcode ausgedacht um zu zeigen, dass beide Möglichkeiten den gleichen output liefern:

Code: Alles auswählen

def eine_callback_funktion(nutzer):
    print("Hallo %s. Hier wurde eine Callback-Funktion aufgerufen." % nutzer)


def keine_callback_funktion(nutzer):
    print("Hallo %s. Hier wurde der text ohne Callback-Funktion erstellt." % nutzer)


def eigentliche_Funktion_mit_callbackFunktion(eine_callback_funktion_A):
    nutzername = input("Bitte Namen eingeben: ")
    eine_callback_funktion_A(nutzername)


def eigentliche_Funktion_ohne_callbackFunktion():
    nutzername = input("Bitte Namen eingeben: ")
    keine_callback_funktion(nutzername)


eigentliche_Funktion_mit_callbackFunktion(eine_callback_funktion)
eigentliche_Funktion_ohne_callbackFunktion()
Danke schon mal!
Wrench
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Der Sinn daran ergibt sich, wenn fremder Code deine Callback Funktion aufruft. Da schreibt man ja normalerweise nicht den Code um, sondern nutzt die unveränderte Version des Pakets und konfiguriert es quasi mit dem gewünschten Verhalten mithilfe einer eigenen Funktion.
wrench139
User
Beiträge: 17
Registriert: Mittwoch 16. März 2022, 15:45

snafu hat geschrieben: Dienstag 21. Juni 2022, 17:04 Der Sinn daran ergibt sich, wenn fremder Code deine Callback Funktion aufruft. Da schreibt man ja normalerweise nicht den Code um, sondern nutzt die unveränderte Version des Pakets und konfiguriert es quasi mit dem gewünschten Verhalten mithilfe einer eigenen Funktion.
Die Antwort leuchtet mir noch nicht ein, warum man das jetzt mit einer Callback-Funktion machen muss. (vermutlich weil mir ein Beispiel/ Erfahrung fehlt).

Wenn ich anderen Code verwenden möchte, dieser aber noch angepasst werden muss, ist es letztlich doch egal, ob ich es als callback mache (also die "alte" Funktion als Paramater der neuen Funktion übergebe), oder ob ich die eine neue Funktion erstelle und innerhalb dieser dann die alte Funktion aufrufe.

Wozu also das Konstrukt einer callback-Funktion? Oder anders gefragt: was ist daran jetzt der Vorteil?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@wrench139: Beispiele finden sich ja in der Standardbibliothek und sogar in eingebauten Funktionen wie `filter()`, `map()`, oder `iter()`. Man kann halt solche Funktionen wie `filter()` oder `map()` schreiben die allgemein etwas machen und das was dann speziell passieren soll, kann der Benutzer der Funktionen als Argument als Rückruffunktion übergeben.

Code: Alles auswählen

In [49]: def is_even(number): 
    ...:     return number % 2 == 0 
    ...:                                                                        

In [50]: list(filter(is_even, range(11)))                                       
Out[50]: [0, 2, 4, 6, 8, 10]

In [51]: list(filter(lambda number: not is_even(number), range(11)))            
Out[51]: [1, 3, 5, 7, 9]
Und bei GUI-Code braucht man das, weil man da ja in der Regel angeben muss, was bei bestimmten Ereignissen passiert. Da übergibt man dann eine Funktion oder Methode die aufgerufen wird, wenn der Benutzer beispielsweise eine Schaltfläche anklickt.

Zu Deiner Nachfrage: Die Bibliothek die Callbacks nimmt weiss ja nicht was Du in bestimmten Fällen machen willst, und Du kannst den Code ja nicht ändern. Was an Deinem Beispiel auch nicht ”überzeugt” ist das es da tatsächlich nicht gebraucht wird, weil es nur eine Callback-Funktion zu der Funktion gibt, die den verwendet. Das kann man natürlich umschreiben so dass kein Callback gebraucht wird. Sinnvoll wird das erst wenn es mindestens zwei Alternativen gibt die man da übergeben kann.

Mal ein Spielzeugbeispiel einer allgemeinen Funktion zur Eingabe von verschiedenen Datentypen:

Code: Alles auswählen

#!/usr/bin/env python3


def ask(prompt, convert=str):
    while True:
        text = input(prompt)
        try:
            return convert(text)
        except ValueError:
            print("Ungültige Eingabe! Bitte versuche es erneut.")


def main():
    text = ask("Bitte einen Text eingeben: ")
    integer_number = ask("Bitte eine ganze Zahl eingeben: ", int)
    number = ask("Bitte eine Zahl eingeben: ", float)
    print([text, integer_number, number])


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
wrench139
User
Beiträge: 17
Registriert: Mittwoch 16. März 2022, 15:45

Erstmal vielen Dank für die Antwort.

Die Beispiele für die Standardbibliotheken finde ich in dem Zusammenhang nicht wirklich hilfreich.
Ja ich verstehe, dass man bei Funktionen aus Standardbibliotheken einer Funktion eine andere übergibt; kein Problem bis hierhin.

Aber warum sollte ich mir angewöhnen meine oder irgendwelche "special"-Funktionien als Parameter zu übergeben anstatt die Funktion einfach innerhalb der anderen aufzurufen? Ich persönlich finde das nämlich irgendwie viel leichter zu durchschauen (wie gesagt, habe erst wenig Erfahrungen im Programmieren).
Sinnvoll wird das erst wenn es mindestens zwei Alternativen gibt die man da übergeben kann.
Das könnte man dann doch vermutlich auch mit if-Blöcken abfragen, wenn man mehrere Alternativen hat, oder täusche ich mich da?

Und das Spielzeugbeispiel verstehe ich nicht im Zusammenhang, weil es doch genau meine Fragen/ Zweifel zum Sinn einer Callback-Funktion bestärkt.
In dem Beispiel definiere ich doch die Funktion "ask" und rufe die dann ganz primitiv drei mal innerhalb der Funktion "main" auf. Also ist das doch keine Callback-Funktion, oder habe ich da etwas Grundlegenderes nicht verstanden?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Natürlich kann man mit if Blöcken arbeiten. Und fasst dann nachträglich Code an, der eigentlich nicht angefasst werden müsste. Und darum geht es nun mal auch beim programmieren: Abstraktionen finden, mit denen man gut arbeiten kann, und die ein gewisses Muster für einen bekannt erledigen.

Bei dem Spielbeispiel ist der Vertrag einfach: wiederhole die Eingabe, bis der callback keinen ValueError schmeißt. Das kann int sein, float, aber auch eine eigene Funktion die datetimes nutzt, oder beliebige Objekte aus den Eingaben liest. Damit ist das also sehr flexibel, wohingegen dein Ansatz das nicht ist, weil es an einer Stelle eine immer größer werdende Anzahl von Spezialfällen sammelt. Die aber da nicht hingehören.
wrench139
User
Beiträge: 17
Registriert: Mittwoch 16. März 2022, 15:45

OK, also zusammenfassend: Die Callback-Funktionen ersparen mir bei komplexerem Code eine Menge Arbeit. Sofern es nicht zu komplex ist, könnte man aber genauso gut einfach andere Funktionen innerhalb des Körpers einer Funktion aufrufen.
Bei dem Spielbeispiel ist der Vertrag einfach: wiederhole die Eingabe, bis der callback keinen ValueError schmeißt
Ah ich glaube ich verstehe es jetzt; das heißt, dass sich die eigentliche Callback-Funktion hier erst beim Aufruf der "ask"-Funktion innerhalb dieser "ask"-Funktion befindet. Wobei die Callback-Funktion drei Ausprägungen haben kann. Also es kann die str(), int(), oder float() die eigentliche Callback-Funktion sein.
Habe ich das so richtig verstanden? (Falls ja: dann war das Beispiel mit der main()-Funktion an dieser Stelle etwas verwirrend; als kleines Feedback meinerseits)
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@wrench139: Wer sagt denn das Du Dir angewöhnen sollst Funktionen als Parameter zu übergeben? So pauschal macht das doch gar keinen Sinn.

Du kannst Alternativen mit ``if``-Blöcken nur dann aufrufen wenn Du die Alternativen vorher alle kennst.

`ask()` ist keine Callback-Funktion sondern *erwartet* solche. Es wird doch drei mal mit einer anderen Funktion aufgerufen. Also einmal implizit weil `str` der Defaultwert ist, aber auch mit `int()` und `float()`.

Das Beispiel mal um ein paar weitere Datentypen erweitert:

Code: Alles auswählen

#!/usr/bin/env python3
import ast
from datetime import datetime as DateTime
from fractions import Fraction


def parse_fraction(text):
    try:
        return Fraction(text)
    except ZeroDivisionError:
        raise ValueError("denominator must not be 0")


def parse_date(text):
    return DateTime.strptime(text, "%Y-%d-%m").date()


def parse_expression(text):
    try:
        return ast.parse(text, mode="eval")
    except SyntaxError:
        raise ValueError("syntactically incorrect")


def ask(prompt, convert=str):
    while True:
        text = input(prompt)
        try:
            return convert(text)
        except ValueError:
            print("Ungültige Eingabe! Bitte versuche es erneut.")


def main():
    text = ask("Bitte einen Text eingeben: ")
    integer_number = ask("Bitte eine ganze Zahl eingeben: ", int)
    number = ask("Bitte eine Zahl eingeben: ", float)
    fraction = ask("Bitte einen Bruch eingeben: ", parse_fraction)
    date = ask("Bitte ein Datum im Format JJJJ-MM-TT eingeben: ", parse_date)
    expression = ask(
        "Bitte einen syntaktisch korrekten Python-Ausdruck eingeben: "
    )

    print([text, integer_number, number, fraction, date])
    print(ast.dump(expression))


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Nein. Der Sinn von Callback-Funktionen ist, dass man Code ausführen kann, den der Client - möglicherweise ein anderer Programmierer an einem anderen Ort zu einer anderen Zeit - an eine Funktion übergeben kann, damit diese ihn ausführt ohne ihn kennen zu müssen. Viele Frameworks funktionieren mit Callbacks. Angenommen, du würdest ein solches Framework schreiben, würdest du dann wollen, dass die Benutzer deines Frameworks dir immer den Code schicken, den sie ausgeführt haben wollen, damit du ihn einbaust? Was, wenn sie dir fehlerhaften Code schicken? Musst du den dann korrigieren? Woher weißt du überhaupt, dass der Code fehlerhaft ist, den sie dir geschickt haben? Vielleicht arbeiten die ja in einem Umfeld wo der Code genau das ist, was sie brauchen? Wie entscheidest du das? Per Telefon/email/snail mail?

Sieh dir die Funktion filter() aus dem itertools Modul an. Wenn du, sagen wir, alle Zahlen die ohne Rest durch drei teilbar sind, aus einer Zahlenliste filtern willst, würdest du gerne möglicherweise monatelang darauf warten wollen, dass dein Request für diesen Code von den Entwicklern der Standard-Bibliothek in dieselbe eingebaut wird? Und was machst du, wenn die entscheiden, dass sie deinen Code nicht in der Standard-Bibliothek haben wollen?

Und weil du geschrieben hast, dass man das ja auch mit if-Abfragen lösen könnte: Das ist genau das, was man vermeiden möchte. if-Abfragen sind statisch. Sie sind fest in den Code eingebaut. Jedes mal, wenn eine neue Alternative dazukommt, muss man seinen gesamten Code danach absuchen, wo überall entsprechende if-Abfragen stehen und diese von Hand anpassen. Besonders lustig wird das dann, wenn man an Code arbeitet, den man nicht selbst geschrieben hat.

Das Prinzip hinter dieser Art der Programmierung nennt sich übrigens Polymorphismus. Andere zu googelnde Begriffe wären separation of concerns und coupling and cohesion.
In specifications, Murphy's Law supersedes Ohm's.
wrench139
User
Beiträge: 17
Registriert: Mittwoch 16. März 2022, 15:45

`ask()` ist keine Callback-Funktion sondern *erwartet* solche. Es wird doch drei mal mit einer anderen Funktion aufgerufen.
So hab ich es ja auch geschrieben (zugegebenermaßen vielleicht etwas umständlich) ;-)

Dann probiere ich es nochmal anders:
man verwendet Callback-Funktionen, wenn man innerhalb einer Funktion oder auf ein Objekt eine spezielle Funktion anwenden möchte. Dabei möchte man sicherstellen, dass die Callback-Funktion auch wirklich die passende ist und kann optional noch Parameter mit übergeben.

So richtig?

(Sorry, ich hoffe ich nerve nicht allzu sehr. Es ist für mich schwierig ein Gefühl dafür zu bekommen, wie man etwas sinnvoll in Code umsetzt)
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@wrench139: Du hattest Deinen Beitrag schon abgeschickt als ich meinen noch geschrieben hatte, darum die ”Überschneidung”, dass ich noch mal beschrieben hatte das `ask()` selbst keine Callbackfunktion ist.

Ich weiss nicht so recht ob Deine Beschreibung so gut ist. Zum Beispiel das unterstrichene „oder“ verstehe ich nicht.

Die Funktion oder Methode die den Callback entgegen nimmt, erwartet in aller Regel eine bestimmte Signatur, denn irgendwann muss der Callback ja aufgerufen werden.

Letztlich läuft es wie pillmuncher schrieb auf Polymorphismus hinaus. Das ist eng verwandt mit Objektorientierung, denn im Grunde ist ein Objekt das man irgendwo übergibt ja auch eine Sammlung von Callbacks — den Methoden, die eine Funktion/Methode dann aufruft. Und letztlich ist eine Funktion/Methode ja auch ein Objekt das eine `__call__()`-Methode hat. Und umgekehrt kann man auch andere Objekte als Funktionen/Methoden dort übergeben wo ein Callback erwartet wird, solange die eine entsprechende `__call__()`-Methode implementieren. Den speziellen Fall, der ja ein „code smell“ ist, das man eine Klasse mit nur einer öffentlichen Methode neben der `__init__()` hat, kann man durch eine Funktion, und gegebenenfalls `functools.partial()` ohne Klasse abbilden.

Callbacks kann man wie (andere) Objekte überall dort einsetzen wo eine Funktion oder Methode nicht nur Daten erwartet, sondern auch Verhalten. Und sowohl in der objektorientierten Programmierung als auch in der funktionalen Programmierung sind ``if``/``elif``/``else`` oft ein Punkt wo man sich überlegt ob das Verhalten ist, welches von Bedingungen abhängig ist, und ob das deshalb nicht besser weniger starr als Methode oder Funktion ausgedrückt werden kann.

Mal ein weiteres Beispiel mit der ersten Aufgabe zum „Intcode“-Prozessor vom Advent of Code 2019. Die Aufgabe war das man ein Programm hat, das aus einer Folge von Zahlen besteht, mit Anfangs folgenden Bedeutungen 1 steht für Addition und 2 für Multiplikation, wobei jeweils die folgenden 3 Zahlen die Operanden und das Ziel für das Ergebnis beschreiben. Wobei die Zahlen jeweils für den Index stehen wo die Operanden und der Platz für das Ergebnis in der Folge von Zahlen stehen. Also in Maschinensprache klassische indirekte Adressierung. Die Zahl 99 steht für „Programmende“.

Wenn man das jetzt mit einem ”grossen” ``if``/``elif``/``else`` für die einzelnen Instruktionen implementiert, könnte das so aussehen:

Code: Alles auswählen

def run(memory):
    instruction_pointer = 0
    while instruction_pointer is not None:
        opcode = memory[instruction_pointer]
        if opcode == 1:
            memory[memory[instruction_pointer + 3]] = (
                memory[memory[instruction_pointer + 1]]
                + memory[memory[instruction_pointer + 2]]
            )
            instruction_pointer += 4
        elif opcode == 2:
            memory[memory[instruction_pointer + 3]] = (
                memory[memory[instruction_pointer + 1]]
                * memory[memory[instruction_pointer + 2]]
            )
            instruction_pointer += 4
        elif opcode == 99:
            instruction_pointer = None
        else:
            raise ValueError(
                f"illegal opcode {opcode} at {instruction_pointer}"
            )
Es fällt auf, dass der Code für die beiden Rechenoperationen nahezu gleich ist. Und wenn man sich jetzt überlegt, dass da vielleicht um Aufgabenteil 2 noch weitere Operationen nach dem Muster hinzu kommen, die man hier durch kopieren, einfügen, und leicht anpassen umsetzen würde, regt sich bei den meisten Programmieren der Wunsch das in eine Funktion heraus zu ziehen. Und meistens identifiziert man dann ja die Daten in denen sich die Kopien unterscheiden, und die man dann als Argumente an die herausgezogene Funktion übergibt. Nur ist der Unterschied hier kein ”passiver” Wert, sondern die Rechenoperation, also Verhalten. Netterweiser gibt es alle Operatoren die Python hat, im `operator`-Modul schon als Funktionen, so dass man das so lösen kann:

Code: Alles auswählen

from operator import add, mul as multiply


def execute_binary_operation(operation, memory, instruction_pointer):
    memory[memory[instruction_pointer + 3]] = operation(
        memory[memory[instruction_pointer + 1]],
        memory[memory[instruction_pointer + 2]],
    )
    return instruction_pointer + 4


def run(memory):
    instruction_pointer = 0
    while instruction_pointer is not None:
        opcode = memory[instruction_pointer]
        if opcode == 1:
            instruction_pointer = execute_binary_operation(
                add, memory, instruction_pointer
            )
        elif opcode == 2:
            instruction_pointer = execute_binary_operation(
                multiply, memory, instruction_pointer
            )
        elif opcode == 99:
            instruction_pointer = None
        else:
            raise ValueError(
                f"illegal opcode {opcode} at {instruction_pointer}"
            )
Im nächsten Schritt kann man sehen, dass die ``if``/``elif``/``else``-Zweige über ”Verhalten” entscheiden. Allgemein kann man sagen, jeder Zweig führt eine Operation aus die (potentiell) den Speicher und den Zeiger auf die aktuelle Instruktion verändert. Das kann man zum Vertrag für eine Funktion machen, dass man sagt, jede Operation ist eine Zahl (Opcode) die auf eine Funktion abgebildet werden kann, die Speicher und Instruktionszeiger bekommt, eventuell den Speicher verändert und den neuen Instruktionszeiger zurück gibt. Und schon kann man das aus der Funktion heraus ziehen, die sehr allgemein halten, und den Befehlssatz des Prozessors als Argument übergeben:

Code: Alles auswählen

from functools import partial
from operator import add, mul as multiply


def execute_binary_operation(operation, memory, instruction_pointer):
    memory[memory[instruction_pointer + 3]] = operation(
        memory[memory[instruction_pointer + 1]],
        memory[memory[instruction_pointer + 2]],
    )
    return instruction_pointer + 4


def execute_halt(_memory, _instruction_pointer):
    return None


def run(instruction_set, memory):
    instruction_pointer = 0
    while instruction_pointer is not None:
        opcode = memory[instruction_pointer]
        execute = instruction_set.get(opcode)
        if not execute:
            raise ValueError(
                f"illegal opcode {opcode} at {instruction_pointer}"
            )

        instruction_pointer = execute(memory, instruction_pointer)


def main():
    instruction_set = {
        1: partial(execute_binary_operation, add),
        2: partial(execute_binary_operation, multiply),
        99: execute_halt,
    }
    memory = [1, 1, 1, 4, 99, 5, 6, 0, 99]
    run(instruction_set, memory)
    print(memory)
Wenn man sich jetzt vorstellt, dass der Befehlssatz um x zusätzliche Instruktionen erweitert wird, ist das so viel übersichtlicher, flexibler, und einfacher zu testen. Man kann jede Instruktion(sfamilie) separat testen und man braucht die `run()`-Funktion nicht anfassen wenn man Instruktionen hinzufügt (solange das Modell des Prozessors bei Speicher + Instruktionszeiger bleibt), und man kann die gleiche `run()`-Funktion für verschiedene Befehlssätze verwenden. Prozessorvarianten mit mehr oder weniger Befehlen oder Fehlern in der Umsetzung einzelner Befehle sind nicht unüblich. Es macht also Sinn einen Prozessoremulator an der Stelle flexibel zu halten.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
wrench139
User
Beiträge: 17
Registriert: Mittwoch 16. März 2022, 15:45

Ok, vielen Dank für eure Bemühungen mir es zu verdeutlichen.
Ich weiss nicht so recht ob Deine Beschreibung so gut ist. Zum Beispiel das unterstrichene „oder“ verstehe ich nicht.
Hatte die Objekte noch zusätzlich mit reingenommen, weil ich bisher immer nur von Funktionen, die mit Funktionen arbeiten, gesprochen habe. Habe dann aber gelesen, dass Callbacks auch auf Objekte (bzw. Instanzen) angewendet werden können; daher das "oder".

Danke auch nochmal für die Code-Beispiele. Ich bin mir zwar nicht wirklich sicher, ob ich sie in Gänze verstanden habe, aber es gibt mir zumindest mal ein Gefühl dafür, wie man die Callbacks verwenden kann.

Falls ich weitere Fragen haben sollte, weiß ich jetzt ja wo ich mich melden kann :)
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Noch interessanter wird es übrigens, wenn der Callback als innere Funktion in etwas wie build_callback() definiert wird. Die Builder-Funktion kann dann zB bestimmte Variablen vorbereiten, die von der Callback-Funktion benutzt werden, jedoch in dem Callback selbst nicht definiert wurden und dem Callback auch nicht als Argumente übergeben werden müssen. Anschließend daran gibt der Builder den Callback zurück und du hast jetzt einen Knoten im Hirn. ;)
wrench139
User
Beiträge: 17
Registriert: Mittwoch 16. März 2022, 15:45

snafu hat geschrieben: Donnerstag 23. Juni 2022, 18:29 Noch interessanter wird es übrigens, wenn der Callback als innere Funktion in etwas wie build_callback() definiert wird. Die Builder-Funktion kann dann zB bestimmte Variablen vorbereiten, die von der Callback-Funktion benutzt werden, jedoch in dem Callback selbst nicht definiert wurden und dem Callback auch nicht als Argumente übergeben werden müssen. Anschließend daran gibt der Builder den Callback zurück und du hast jetzt einen Knoten im Hirn. ;)
Da ich eine build_callback()-Funktion (noch) nicht kenne: kein Knoten, da der Faden dafür noch nicht mal vorhanden ist :D Aber falls ich in Zukunft mal irgendwie darüber stolpern sollte, hab ich jetzt zumindest mal gehört, dass es so etwas gibt.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@wrench139: Naja, `partial()` ist ja so eine Funktion die eine Funktion bekommt und eine Funktion als Ergebnis liefert.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
wrench139
User
Beiträge: 17
Registriert: Mittwoch 16. März 2022, 15:45

__blackjack__ hat geschrieben: Freitag 24. Juni 2022, 13:10 @wrench139: Naja, `partial()` ist ja so eine Funktion die eine Funktion bekommt und eine Funktion als Ergebnis liefert.
Ah, stimmt. Das kam in einem Tutorial schon einmal vor. Habe es aber seitdem nicht mehr verwendet. Danke ;-)
Antworten