functools — Funktionen höherer Ordnung und Operationen auf aufrufbaren Objekten

Quellcode: Lib/functools.py


Das Modul functools ist für Funktionen höherer Ordnung bestimmt: Funktionen, die auf andere Funktionen wirken oder andere Funktionen zurückgeben. Im Allgemeinen kann jedes aufrufbare Objekt als Funktion für die Zwecke dieses Moduls behandelt werden.

Das Modul functools definiert die folgenden Funktionen

@functools.cache(user_function)

Einfacher, leichter, unbegrenzter Funktionscache. Manchmal auch als "Memoize" bezeichnet.

Gibt dasselbe zurück wie lru_cache(maxsize=None) und erstellt eine dünne Hülle um eine Dictionary-Suche nach den Funktionsargumenten. Da keine alten Werte verworfen werden müssen, ist dies kleiner und schneller als lru_cache() mit Größenbeschränkung.

Zum Beispiel

@cache
def factorial(n):
    return n * factorial(n-1) if n else 1

>>> factorial(10)      # no previously cached result, makes 11 recursive calls
3628800
>>> factorial(5)       # just looks up cached value result
120
>>> factorial(12)      # makes two new recursive calls, the other 10 are cached
479001600

Der Cache ist threadsicher, sodass die umschlossene Funktion in mehreren Threads verwendet werden kann. Dies bedeutet, dass die zugrundeliegende Datenstruktur während gleichzeitiger Aktualisierungen kohärent bleibt.

Es ist möglich, dass die umschlossene Funktion mehr als einmal aufgerufen wird, wenn ein anderer Thread einen zusätzlichen Aufruf tätigt, bevor der ursprüngliche Aufruf abgeschlossen und gecacht wurde.

Hinzugefügt in Version 3.9.

@functools.cached_property(func)

Wandelt eine Methode einer Klasse in eine Eigenschaft um, deren Wert einmal berechnet und dann als normale Attribut für die Lebensdauer der Instanz gespeichert wird. Ähnlich wie property(), mit der zusätzlichen Caching-Funktion. Nützlich für teure berechnete Eigenschaften von Instanzen, die ansonsten praktisch unveränderlich sind.

Beispiel

class DataSet:

    def __init__(self, sequence_of_numbers):
        self._data = tuple(sequence_of_numbers)

    @cached_property
    def stdev(self):
        return statistics.stdev(self._data)

Die Funktionsweise von cached_property() unterscheidet sich etwas von der von property(). Eine normale Eigenschaft blockiert Attributzuweisungen, es sei denn, ein Setter ist definiert. Im Gegensatz dazu erlaubt ein cached_property Zuweisungen.

Der cached_property-Decorator wird nur bei Lookups ausgeführt und nur, wenn ein Attribut mit demselben Namen nicht existiert. Wenn er ausgeführt wird, schreibt der cached_property in das Attribut mit demselben Namen. Nachfolgende Attributlesungen und -zuweisungen haben Vorrang vor der cached_property-Methode und verhalten sich wie normale Attribute.

Der gecachte Wert kann durch Löschen des Attributs gelöscht werden. Dies ermöglicht es der cached_property-Methode, erneut ausgeführt zu werden.

Der cached_property verhindert keine möglichen Wettlaufsituationen bei der Verwendung in Multithreading. Die Getter-Funktion könnte mehr als einmal für dieselbe Instanz ausgeführt werden, wobei der letzte Lauf den gecachten Wert setzt. Wenn die gecachte Eigenschaft idempotent ist oder die mehrfache Ausführung für eine Instanz ansonsten unbedenklich ist, ist dies in Ordnung. Wenn Synchronisation erforderlich ist, implementieren Sie die notwendige Sperrung innerhalb des dekorierten Getter-Funktion oder um den Zugriff auf die gecachte Eigenschaft herum.

Beachten Sie, dass dieser Dekorator die Funktionsweise von PEP 412-Schlüsselgemeinsamen Dictionaries stört. Das bedeutet, dass Instanz-Dictionaries mehr Speicherplatz als üblich belegen können.

Außerdem erfordert dieser Dekorator, dass das Attribut __dict__ jeder Instanz eine mutable Zuordnung ist. Das bedeutet, dass er bei einigen Typen nicht funktioniert, z. B. bei Metaklassen (da die Attribute __dict__ von Instanzen von Klassen schreibgeschützte Proxies für den Klassen-Namespace sind) und solchen, die __slots__ angeben, ohne __dict__ als eines der definierten Slots aufzunehmen (da solche Klassen überhaupt kein Attribut __dict__ bereitstellen).

Wenn eine mutable Zuordnung nicht verfügbar ist oder wenn speichereffiziente Schlüsselteilung gewünscht wird, kann ein ähnlicher Effekt wie bei cached_property() auch durch Stapeln von property() über lru_cache() erzielt werden. Siehe Wie kann ich Methodenaufrufe cachen? für weitere Details, wie sich dies von cached_property() unterscheidet.

Hinzugefügt in Version 3.8.

Geändert in Version 3.12: Vor Python 3.12 enthielt cached_property eine undokumentierte Sperre, um sicherzustellen, dass bei der Verwendung in Multithreading die Getter-Funktion garantiert nur einmal pro Instanz ausgeführt wird. Die Sperre war jedoch pro Eigenschaft, nicht pro Instanz, was zu einer unannehmbar hohen Sperrkonfliktreduzierung führen konnte. In Python 3.12+ ist diese Sperrung entfernt.

functools.cmp_to_key(func)

Wandelt eine Vergleichsfunktion alter Prägung in eine Schlüsselfunktion um. Wird mit Werkzeugen verwendet, die Schlüsselfunktionen akzeptieren (wie sorted(), min(), max(), heapq.nlargest(), heapq.nsmallest(), itertools.groupby()). Diese Funktion wird hauptsächlich als Übergangswerkzeug für Programme verwendet, die von Python 2 konvertiert werden, das die Verwendung von Vergleichsfunktionen unterstützte.

Eine Vergleichsfunktion ist jedes aufrufbare Objekt, das zwei Argumente akzeptiert, sie vergleicht und eine negative Zahl für kleiner-als, Null für Gleichheit oder eine positive Zahl für größer-als zurückgibt. Eine Schlüsselfunktion ist ein aufrufbare Objekt, das ein Argument akzeptiert und einen anderen Wert zurückgibt, der als Sortierschlüssel verwendet werden soll.

Beispiel

sorted(iterable, key=cmp_to_key(locale.strcoll))  # locale-aware sort order

Für Sortierbeispiele und eine kurze Sortieranleitung siehe Sortiertechniken.

Hinzugefügt in Version 3.2.

@functools.lru_cache(user_function)
@functools.lru_cache(maxsize=128, typed=False)

Dekorator zum Umschließen einer Funktion mit einem memoizerenden aufrufbaren Objekt, das bis zu den maxsize zuletzt aufgerufenen Aufrufe speichert. Dies kann Zeit sparen, wenn eine teure oder I/O-gebundene Funktion periodisch mit denselben Argumenten aufgerufen wird.

Der Cache ist threadsicher, sodass die umschlossene Funktion in mehreren Threads verwendet werden kann. Dies bedeutet, dass die zugrundeliegende Datenstruktur während gleichzeitiger Aktualisierungen kohärent bleibt.

Es ist möglich, dass die umschlossene Funktion mehr als einmal aufgerufen wird, wenn ein anderer Thread einen zusätzlichen Aufruf tätigt, bevor der ursprüngliche Aufruf abgeschlossen und gecacht wurde.

Da ein Dictionary zum Cachen von Ergebnissen verwendet wird, müssen die Positions- und Schlüsselwortargumente der Funktion hashbar sein.

Unterschiedliche Argumentmuster können als unterschiedliche Aufrufe mit separaten Cache-Einträgen betrachtet werden. Zum Beispiel sind f(a=1, b=2) und f(b=2, a=1) in der Reihenfolge ihrer Schlüsselwortargumente unterschiedlich und können zwei separate Cache-Einträge haben.

Wenn user_function angegeben ist, muss es sich um ein aufrufbare Objekt handeln. Dies ermöglicht die direkte Anwendung des lru_cache-Dekorators auf eine Benutzerfunktion, wobei maxsize auf seinem Standardwert von 128 belassen wird.

@lru_cache
def count_vowels(sentence):
    return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')

Wenn maxsize auf None gesetzt ist, ist die LRU-Funktion deaktiviert und der Cache kann unbegrenzt wachsen.

Wenn typed auf True gesetzt ist, werden Funktionsargumente unterschiedlicher Typen separat gecacht. Wenn typed false ist, betrachtet die Implementierung sie normalerweise als äquivalente Aufrufe und speichert nur ein einziges Ergebnis im Cache. (Einige Typen wie str und int können auch dann separat gecacht werden, wenn typed false ist.)

Beachten Sie, dass die Typenspezifität nur für die unmittelbaren Argumente der Funktion gilt, nicht für deren Inhalt. Die skalaren Argumente Decimal(42) und Fraction(42) werden als unterschiedliche Aufrufe mit unterschiedlichen Ergebnissen behandelt. Im Gegensatz dazu werden die Tupelargumente ('answer', Decimal(42)) und ('answer', Fraction(42)) als äquivalent behandelt.

Die umschlossene Funktion ist mit einer Funktion cache_parameters() instrumentiert, die ein neues dict zurückgibt, das die Werte für maxsize und typed anzeigt. Dies dient nur zu Informationszwecken. Das Verändern der Werte hat keine Auswirkungen.

Um die Effektivität des Caches zu messen und den maxsize-Parameter zu optimieren, ist die umschlossene Funktion mit einer Funktion cache_info() instrumentiert, die ein benanntes Tupel zurückgibt, das hits, misses, maxsize und currsize anzeigt.

Der Dekorator bietet außerdem eine Funktion cache_clear() zum Leeren oder Ungültigmachen des Caches.

Die ursprüngliche zugrunde liegende Funktion ist über das Attribut __wrapped__ zugänglich. Dies ist nützlich für die Introspektion, zum Umgehen des Caches oder zum erneuten Umschließen der Funktion mit einem anderen Cache.

Der Cache speichert Referenzen auf die Argumente und Rückgabewerte, bis sie aus dem Cache fallen oder bis der Cache geleert wird.

Wenn eine Methode gecacht wird, wird das Instanzargument self in den Cache aufgenommen. Siehe Wie kann ich Methodenaufrufe cachen?

Ein LRU-Cache (Least Recently Used) funktioniert am besten, wenn die zuletzt aufgerufenen Aufrufe die besten Vorhersagen für kommende Aufrufe sind (z. B. die beliebtesten Artikel auf einem News-Server ändern sich tendenziell täglich). Die Größenbeschränkung des Caches stellt sicher, dass sich der Cache bei langlebigen Prozessen wie Webservern nicht unbegrenzt vergrößert.

Im Allgemeinen sollte der LRU-Cache nur verwendet werden, wenn zuvor berechnete Werte wiederverwendet werden sollen. Daher macht es keinen Sinn, Funktionen mit Nebeneffekten, Funktionen, die bei jedem Aufruf unterschiedliche mutable Objekte erstellen müssen (wie Generatoren und asynchrone Funktionen), oder unreine Funktionen wie time() oder random() zu cachen.

Beispiel für einen LRU-Cache für statische Webinhalte

@lru_cache(maxsize=32)
def get_pep(num):
    'Retrieve text of a Python Enhancement Proposal'
    resource = f'https://peps.pythonlang.de/pep-{num:04d}'
    try:
        with urllib.request.urlopen(resource) as s:
            return s.read()
    except urllib.error.HTTPError:
        return 'Not Found'

>>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
...     pep = get_pep(n)
...     print(n, len(pep))

>>> get_pep.cache_info()
CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)

Beispiel für die effiziente Berechnung von Fibonacci-Zahlen unter Verwendung eines Caches zur Implementierung einer dynamischen Programmierungstechnik

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

>>> [fib(n) for n in range(16)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

>>> fib.cache_info()
CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)

Hinzugefügt in Version 3.2.

Geändert in Version 3.3: Die Option typed hinzugefügt.

Geändert in Version 3.8: Die Option user_function hinzugefügt.

Geändert in Version 3.9: Die Funktion cache_parameters() hinzugefügt.

@functools.total_ordering

Gegeben eine Klasse, die eine oder mehrere Methoden für reiche Vergleiche definiert, liefert dieser Klassen-Dekorator die restlichen. Dies vereinfacht den Aufwand, der mit der Definition aller möglichen reichhaltigen Vergleichsoperationen verbunden ist.

Die Klasse muss eine der Methoden __lt__(), __le__(), __gt__() oder __ge__() definieren. Zusätzlich sollte die Klasse eine Methode __eq__() bereitstellen.

Zum Beispiel

@total_ordering
class Student:
    def _is_valid_operand(self, other):
        return (hasattr(other, "lastname") and
                hasattr(other, "firstname"))
    def __eq__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        if not self._is_valid_operand(other):
            return NotImplemented
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

Hinweis

Während dieser Dekorator es einfach macht, gut funktionierende, vollständig geordnete Typen zu erstellen, ist dies auf Kosten einer langsameren Ausführung und komplexerer Stack-Traces für die abgeleiteten Vergleichsmethoden. Wenn Performance-Benchmarks zeigen, dass dies ein Engpass für eine bestimmte Anwendung ist, ist die Implementierung aller sechs Methoden für reiche Vergleiche wahrscheinlich eine einfache Geschwindigkeitssteigerung.

Hinweis

Dieser Dekorator versucht nicht, Methoden zu überschreiben, die in der Klasse oder ihren Oberklassen definiert wurden. Das bedeutet, wenn eine Oberklasse einen Vergleichsoperator definiert, wird total_ordering ihn nicht erneut implementieren, auch wenn die ursprüngliche Methode abstrakt ist.

Hinzugefügt in Version 3.2.

Geändert in Version 3.4: Das Zurückgeben von NotImplemented aus der zugrundeliegenden Vergleichsfunktion für nicht erkannte Typen wird jetzt unterstützt.

functools.Placeholder

Ein Singleton-Objekt, das als Sentinel verwendet wird, um einen Platz für Positionsargumente bei Aufrufen von partial() und partialmethod() zu reservieren.

Hinzugefügt in Version 3.14.

functools.partial(func, /, *args, **keywords)

Gibt ein neues partial object zurück, das sich bei Aufruf wie func verhält, aufgerufen mit den Positionsargumenten args und den Schlüsselwortargumenten keywords. Wenn bei dem Aufruf weitere Argumente übergeben werden, werden diese an args angehängt. Wenn zusätzliche Schlüsselwortargumente übergeben werden, erweitern und überschreiben sie keywords. Ungefähr äquivalent zu

def partial(func, /, *args, **keywords):
    def newfunc(*more_args, **more_keywords):
        return func(*args, *more_args, **(keywords | more_keywords))
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

Die Funktion partial() wird für die partielle Funktionsanwendung verwendet, die einen Teil der Argumente und/oder Schlüsselwörter einer Funktion "einfriert" und ein neues Objekt mit einer vereinfachten Signatur zurückgibt. Zum Beispiel kann partial() verwendet werden, um ein aufrufbares Objekt zu erstellen, das sich wie die Funktion int() verhält, bei der das Argument base standardmäßig auf 2 gesetzt ist.

>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

Wenn Placeholder-Sentinels in args vorhanden sind, werden diese zuerst gefüllt, wenn partial() aufgerufen wird. Dies ermöglicht das Vorbelegen beliebiger Positionsargumente mit einem Aufruf von partial(); ohne Placeholder können nur die gewählten vorderen Positionsargumente vorbelegt werden.

Wenn Placeholder-Sentinels vorhanden sind, müssen sie alle zur Aufrufzeit gefüllt werden.

>>> say_to_world = partial(print, Placeholder, Placeholder, "world!")
>>> say_to_world('Hello', 'dear')
Hello dear world!

Der Aufruf von say_to_world('Hello') löst einen TypeError aus, da nur ein Positionsargument übergeben wird, aber zwei Platzhalter gefüllt werden müssen.

Wenn partial() auf ein bestehendes partial()-Objekt angewendet wird, werden Placeholder-Sentinels des Eingabeobjekts mit neuen Positionsargumenten gefüllt. Ein Platzhalter kann beibehalten werden, indem ein neuer Placeholder-Sentinel an die Stelle eines früheren Placeholder eingefügt wird.

>>> from functools import partial, Placeholder as _
>>> remove = partial(str.replace, _, _, '')
>>> message = 'Hello, dear dear world!'
>>> remove(message, ' dear')
'Hello, world!'
>>> remove_dear = partial(remove, _, ' dear')
>>> remove_dear(message)
'Hello, world!'
>>> remove_first_dear = partial(remove_dear, _, 1)
>>> remove_first_dear(message)
'Hello, dear world!'

Placeholder kann nicht als Schlüsselwortargument an partial() übergeben werden.

Geändert in Version 3.14: Unterstützung für Placeholder in Positionsargumenten hinzugefügt.

class functools.partialmethod(func, /, *args, **keywords)

Gibt einen neuen partialmethod-Deskriptor zurück, der sich wie partial verhält, außer dass er für die Verwendung als Methodendefinition konzipiert ist und nicht direkt aufrufbar ist.

func muss ein Deskriptor oder ein aufrufbares Objekt sein (Objekte, die beides sind, wie normale Funktionen, werden als Deskriptoren behandelt).

Wenn func ein Deskriptor ist (z. B. eine normale Python-Funktion, classmethod(), staticmethod(), abstractmethod() oder eine andere Instanz von partialmethod), werden Aufrufe von __get__ an den zugrunde liegenden Deskriptor delegiert, und ein geeignetes partial object wird als Ergebnis zurückgegeben.

Wenn func ein nicht-Deskriptor aufrufbares Objekt ist, wird dynamisch eine geeignete gebundene Methode erstellt. Dies verhält sich wie eine normale Python-Funktion, wenn es als Methode verwendet wird: Das Argument self wird als erstes Positionsargument eingefügt, noch vor den args und keywords, die dem Konstruktor partialmethod übergeben wurden.

Beispiel

>>> class Cell:
...     def __init__(self):
...         self._alive = False
...     @property
...     def alive(self):
...         return self._alive
...     def set_state(self, state):
...         self._alive = bool(state)
...     set_alive = partialmethod(set_state, True)
...     set_dead = partialmethod(set_state, False)
...
>>> c = Cell()
>>> c.alive
False
>>> c.set_alive()
>>> c.alive
True

Hinzugefügt in Version 3.4.

functools.reduce(function, iterable, /[, initial])

Wendet function von zwei Argumenten kumulativ auf die Elemente von iterable an, von links nach rechts, um das Iterable zu einem einzigen Wert zu reduzieren. Zum Beispiel berechnet reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) die Berechnung ((((1+2)+3)+4)+5). Das linke Argument, x, ist der akkumulierte Wert und das rechte Argument, y, ist der Aktualisierungswert aus dem iterable. Wenn das optionale initial vorhanden ist, wird es vor den Elementen des Iterables in die Berechnung eingefügt und dient als Standardwert, wenn das Iterable leer ist. Wenn initial nicht angegeben ist und iterable nur ein Element enthält, wird das erste Element zurückgegeben.

Etwa äquivalent zu

initial_missing = object()

def reduce(function, iterable, /, initial=initial_missing):
    it = iter(iterable)
    if initial is initial_missing:
        value = next(it)
    else:
        value = initial
    for element in it:
        value = function(value, element)
    return value

Siehe itertools.accumulate() für einen Iterator, der alle Zwischenwerte ausgibt.

Geändert in Version 3.14: initial wird jetzt als Schlüsselwortargument unterstützt.

@functools.singledispatch

Wandelt eine Funktion in eine Single-Dispatch-Funktion um, die eine Generische Funktion ist.

Um eine generische Funktion zu definieren, dekorieren Sie sie mit dem Dekorator @singledispatch. Beachten Sie bei der Definition einer Funktion mit @singledispatch, dass die Verteilung auf dem Typ des ersten Arguments basiert.

>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Let me just say,", end=" ")
...     print(arg)

Um überladene Implementierungen zur Funktion hinzuzufügen, verwenden Sie das Attribut register() der generischen Funktion, das als Dekorator verwendet werden kann. Für Funktionen, die mit Typen annotiert sind, leitet der Dekorator den Typ des ersten Arguments automatisch ab.

>>> @fun.register
... def _(arg: int, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> @fun.register
... def _(arg: list, verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

typing.Union kann ebenfalls verwendet werden.

>>> @fun.register
... def _(arg: int | float, verbose=False):
...     if verbose:
...         print("Strength in numbers, eh?", end=" ")
...     print(arg)
...
>>> from typing import Union
>>> @fun.register
... def _(arg: Union[list, set], verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)
...

Für Code, der keine Typannotationen verwendet, kann der entsprechende Typ als Argument explizit an den Dekorator selbst übergeben werden.

>>> @fun.register(complex)
... def _(arg, verbose=False):
...     if verbose:
...         print("Better than complicated.", end=" ")
...     print(arg.real, arg.imag)
...

Für Code, der auf einem Sammlungstyp (z. B. list) basiert, aber die Elemente der Sammlung typisieren möchte (z. B. list[int]), sollte der Dispatch-Typ explizit an den Dekorator selbst übergeben werden, während die Typisierung in der Funktionsdefinition erfolgt.

>>> @fun.register(list)
... def _(arg: list[int], verbose=False):
...     if verbose:
...         print("Enumerate this:")
...     for i, elem in enumerate(arg):
...         print(i, elem)

Hinweis

Zur Laufzeit wird die Funktion auf einer Instanz einer Liste basierend auf dem Typ, der sich innerhalb der Liste befindet, verteilt, d. h. [1,2,3] wird auf die gleiche Weise verteilt wie ["foo", "bar", "baz"]. Die in diesem Beispiel angegebene Annotation dient nur statischen Typprüfern und hat keine Laufzeitauswirkungen.

Um die Registrierung von Lambdas und bereits vorhandenen Funktionen zu ermöglichen, kann das Attribut register() auch in funktionaler Form verwendet werden.

>>> def nothing(arg, verbose=False):
...     print("Nothing.")
...
>>> fun.register(type(None), nothing)

Das Attribut register() gibt die undekorierte Funktion zurück. Dies ermöglicht die Stapelung von Dekoratoren, Pickling und die Erstellung von Unit-Tests für jede Variante unabhängig.

>>> @fun.register(float)
... @fun.register(Decimal)
... def fun_num(arg, verbose=False):
...     if verbose:
...         print("Half of your number:", end=" ")
...     print(arg / 2)
...
>>> fun_num is fun
False

Bei Aufruf verteilt die generische Funktion auf dem Typ des ersten Arguments.

>>> fun("Hello, world.")
Hello, world.
>>> fun("test.", verbose=True)
Let me just say, test.
>>> fun(42, verbose=True)
Strength in numbers, eh? 42
>>> fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
Enumerate this:
0 spam
1 spam
2 eggs
3 spam
>>> fun(None)
Nothing.
>>> fun(1.23)
0.615

Wenn keine registrierte Implementierung für einen bestimmten Typ vorhanden ist, wird seine Method auflösungs reihe (Method Resolution Order) verwendet, um eine allgemeinere Implementierung zu finden. Die ursprüngliche Funktion, die mit @singledispatch dekoriert wurde, wird für den Basistyp object registriert, was bedeutet, dass sie verwendet wird, wenn keine bessere Implementierung gefunden wird.

Wenn eine Implementierung bei einer abstrakten Basisklasse registriert wird, werden virtuelle Unterklassen der Basisklasse an diese Implementierung verteilt.

>>> from collections.abc import Mapping
>>> @fun.register
... def _(arg: Mapping, verbose=False):
...     if verbose:
...         print("Keys & Values")
...     for key, value in arg.items():
...         print(key, "=>", value)
...
>>> fun({"a": "b"})
a => b

Um zu überprüfen, welche Implementierung die generische Funktion für einen gegebenen Typ wählt, verwenden Sie das Attribut dispatch().

>>> fun.dispatch(float)
<function fun_num at 0x1035a2840>
>>> fun.dispatch(dict)    # note: default implementation
<function fun at 0x103fe0000>

Um auf alle registrierten Implementierungen zuzugreifen, verwenden Sie das schreibgeschützte Attribut registry.

>>> fun.registry.keys()
dict_keys([<class 'NoneType'>, <class 'int'>, <class 'object'>,
          <class 'decimal.Decimal'>, <class 'list'>,
          <class 'float'>])
>>> fun.registry[float]
<function fun_num at 0x1035a2840>
>>> fun.registry[object]
<function fun at 0x103fe0000>

Hinzugefügt in Version 3.4.

Geändert in Version 3.7: Das Attribut register() unterstützt jetzt die Verwendung von Typannotationen.

Geändert in Version 3.11: Das Attribut register() unterstützt jetzt typing.Union als Typannotation.

class functools.singledispatchmethod(func)

Wandelt eine Methode in eine Single-Dispatch-Funktion um, die eine Generische Funktion ist.

Um eine generische Methode zu definieren, dekorieren Sie sie mit dem Dekorator @singledispatchmethod. Beachten Sie bei der Definition einer Funktion mit @singledispatchmethod, dass die Verteilung auf dem Typ des ersten Arguments basiert, das weder self noch cls ist.

class Negator:
    @singledispatchmethod
    def neg(self, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    def _(self, arg: int):
        return -arg

    @neg.register
    def _(self, arg: bool):
        return not arg

@singledispatchmethod unterstützt das Verschachteln mit anderen Dekoratoren wie @classmethod. Beachten Sie, dass, um dispatcher.register zu ermöglichen, singledispatchmethod der äußerste Dekorator sein muss. Hier ist die Klasse Negator mit den an die Klasse gebundenen Methoden neg und nicht an eine Instanz der Klasse.

class Negator:
    @singledispatchmethod
    @classmethod
    def neg(cls, arg):
        raise NotImplementedError("Cannot negate a")

    @neg.register
    @classmethod
    def _(cls, arg: int):
        return -arg

    @neg.register
    @classmethod
    def _(cls, arg: bool):
        return not arg

Das gleiche Muster kann für andere ähnliche Dekoratoren verwendet werden: @staticmethod, @~abc.abstractmethod und andere.

Hinzugefügt in Version 3.8.

functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

Aktualisiert eine wrapper-Funktion, damit sie wie die wrapped-Funktion aussieht. Die optionalen Argumente sind Tupel, die angeben, welche Attribute der ursprünglichen Funktion direkt den entsprechenden Attributen der Wrapper-Funktion zugewiesen werden und welche Attribute der Wrapper-Funktion mit den entsprechenden Attributen der ursprünglichen Funktion aktualisiert werden. Die Standardwerte für diese Argumente sind die Modulkonstanten WRAPPER_ASSIGNMENTS (die den Attributen __module__, __name__, __qualname__, __annotations__, __type_params__ und __doc__, der Dokumentationsstring) der Wrapper-Funktion zugewiesen werden) und WRAPPER_UPDATES (die das __dict__ der Wrapper-Funktion, d. h. das Instanz-Dictionary, aktualisieren).

Um den Zugriff auf die ursprüngliche Funktion für Introspektion und andere Zwecke zu ermöglichen (z. B. um einen Caching-Decorator wie lru_cache() zu umgehen), fügt diese Funktion dem Wrapper automatisch ein Attribut __wrapped__ hinzu, das auf die gewrappte Funktion verweist.

Der Hauptzweck dieser Funktion liegt in Decorator-Funktionen, die die dekorierte Funktion wrappen und den Wrapper zurückgeben. Wenn die Wrapper-Funktion nicht aktualisiert wird, spiegeln die Metadaten der zurückgegebenen Funktion die Wrapper-Definition und nicht die ursprüngliche Funktionsdefinition wider, was typischerweise wenig hilfreich ist.

update_wrapper() kann mit anderen aufrufbaren Objekten als Funktionen verwendet werden. Attribute, die in assigned oder updated aufgeführt sind und im gewrappten Objekt fehlen, werden ignoriert (d. h. diese Funktion versucht nicht, sie auf der Wrapper-Funktion zu setzen). AttributeError wird immer noch ausgelöst, wenn der Wrapper-Funktion selbst Attribute fehlen, die in updated aufgeführt sind.

Geändert in Version 3.2: Das Attribut __wrapped__ wird jetzt automatisch hinzugefügt. Das Attribut __annotations__ wird jetzt standardmäßig kopiert. Fehlende Attribute lösen keinen AttributeError mehr aus.

Geändert in Version 3.4: Das Attribut __wrapped__ verweist nun immer auf die gewrappte Funktion, auch wenn diese Funktion ein Attribut __wrapped__ definiert hat. (siehe bpo-17482)

Geändert in Version 3.12: Das Attribut __type_params__ wird jetzt standardmäßig kopiert.

@functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

Dies ist eine Hilfsfunktion zur Verwendung von update_wrapper() als Funktions-Decorator bei der Definition einer Wrapper-Funktion. Sie ist äquivalent zu partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated). Zum Beispiel:

>>> from functools import wraps
>>> def my_decorator(f):
...     @wraps(f)
...     def wrapper(*args, **kwds):
...         print('Calling decorated function')
...         return f(*args, **kwds)
...     return wrapper
...
>>> @my_decorator
... def example():
...     """Docstring"""
...     print('Called example function')
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'

Ohne die Verwendung dieser Decorator-Factory wäre der Name der Beispiel-Funktion 'wrapper' gewesen, und der Docstring des ursprünglichen example() wäre verloren gegangen.

partial Objekte

partial Objekte sind aufrufbare Objekte, die von partial() erstellt werden. Sie haben drei schreibgeschützte Attribute:

partial.func

Ein aufrufbares Objekt oder eine Funktion. Aufrufe des partial-Objekts werden an func mit neuen Argumenten und Schlüsselwörtern weitergeleitet.

partial.args

Die positionellen Argumente von links, die den positionellen Argumenten vorangestellt werden, die an einen Aufruf eines partial-Objekts übergeben werden.

partial.keywords

Die Schlüsselwortargumente, die beim Aufruf des partial-Objekts bereitgestellt werden.

partial Objekte sind wie Funktions-Objekte insofern, als sie aufrufbar, schwach referenzierbar und Attribute haben können. Es gibt einige wichtige Unterschiede. Zum Beispiel werden die Attribute __name__ und __doc__ nicht automatisch erstellt.