Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 309 – Partielle Funktionsanwendung

Autor:
Peter Harris <scav at blueyonder.co.uk>
Status:
Final
Typ:
Standards Track
Erstellt:
08-Feb-2003
Python-Version:
2.5
Post-History:
10-Feb-2003, 27-Feb-2003, 22-Feb-2004, 28-Apr-2006

Inhaltsverzeichnis

Hinweis

Nach der Annahme dieses PEPs offenbarte weitere Diskussionen auf python-dev und comp.lang.python einen Wunsch nach mehreren Werkzeugen, die auf Funktions-Objekten operieren, aber nicht mit funktionaler Programmierung zusammenhängen. Anstatt ein neues Modul für diese Werkzeuge zu erstellen, wurde vereinbart [1], das Modul „functional“ in „functools“ umzubenennen, um seinen neu erweiterten Fokus widerzuspiegeln.

Referenzen in diesem PEP auf ein „functional“-Modul wurden aus historischen Gründen beibehalten.

Zusammenfassung

Dieser Vorschlag betrifft eine Funktion oder eine aufrufbare Klasse, die es ermöglicht, ein neues aufrufbares Objekt aus einem aufrufbaren Objekt und einer partiellen Argumentliste (einschließlich Positions- und Schlüsselwortargumenten) zu konstruieren.

Ich schlage ein Standardbibliotheksmodul namens „functional“ vor, das nützliche höherrangige Funktionen enthält, einschließlich der Implementierung von partial().

Eine Implementierung wurde auf SourceForge eingereicht [2].

Akzeptanz

Patch #941881 wurde 2005 für Py2.5 akzeptiert und angewendet. Er entspricht im Wesentlichen dem hier dargestellten Entwurf: ein partial()-Typkonstruktor, der linke Positionsargumente und beliebige Schlüsselwörter bindet. Das partial-Objekt hat drei schreibgeschützte Attribute: func, args und keywords. Aufrufe des partial-Objekts können Schlüsselwörter angeben, die die im Objekt selbst vorhandenen überschreiben.

Es gibt eine separate und fortlaufende Diskussion darüber, ob die partial-Implementierung um eine __get__-Methode erweitert werden soll, um das Verhalten einer äquivalenten Funktion genauer nachzubilden.

Motivation

In der funktionalen Programmierung ist Currying eine Methode, Mehrargumentfunktionen in Bezug auf Einstellenargumentfunktionen zu implementieren. Eine Funktion mit N Argumenten ist eigentlich eine Funktion mit 1 Argument, die eine andere Funktion zurückgibt, die (N-1) Argumente erwartet. Funktionsanwendung in Sprachen wie Haskell und ML funktioniert so, dass ein Funktionsaufruf

f x y z

tatsächlich bedeutet

(((f x) y) z)

Dies wäre nur ein obskures theoretisches Problem, außer dass es sich in der tatsächlichen Programmierung als sehr nützlich erweist. Eine Funktion durch partielle Anwendung von Argumenten auf eine andere Funktion auszudrücken, kann sowohl elegant als auch mächtig sein und wird in funktionalen Sprachen intensiv genutzt.

In einigen funktionalen Sprachen (z. B. Miranda) können Sie einen Ausdruck wie (+1) verwenden, um das Äquivalent von Pythons (lambda x: x + 1) zu meinen.

Im Allgemeinen sind Sprachen wie diese stark typisiert, sodass der Compiler immer die erwartete Anzahl von Argumenten kennt und das Richtige tun kann, wenn er mit einem Funktor und weniger Argumenten als erwartet konfrontiert wird.

Python implementiert keine Mehrargumentfunktionen durch Currying. Wenn Sie also eine Funktion mit partiell angewendeten Argumenten wünschen, würden Sie wahrscheinlich eine Lambda-Funktion wie oben verwenden oder eine benannte Funktion für jede Instanz definieren.

Die Lambda-Syntax ist jedoch, gelinde gesagt, nicht jedermanns Geschmack. Darüber hinaus bietet Pythons flexible Parameterübergabe, sowohl positionsbezogen als auch mit Schlüsselwörtern, die Möglichkeit, die Idee der partiellen Anwendung zu verallgemeinern und Dinge zu tun, die Lambda nicht kann.

Beispielimplementierung

Hier ist eine Möglichkeit, ein aufrufbares Objekt mit partiell angewendeten Argumenten in Python zu erstellen. Die folgende Implementierung basiert auf Verbesserungen von Scott David Daniels

class partial(object):

    def __init__(*args, **kw):
        self = args[0]
        self.fn, self.args, self.kw = (args[1], args[2:], kw)

    def __call__(self, *args, **kw):
        if kw and self.kw:
            d = self.kw.copy()
            d.update(kw)
        else:
            d = kw or self.kw
        return self.fn(*(self.args + args), **d)

(Ein ähnliches Rezept wie dieses befindet sich seit einiger Zeit im Python Cookbook [3].)

Beachten Sie, dass beim Aufruf des Objekts, als ob es eine Funktion wäre, Positionsargumente an die im Konstruktor bereitgestellten angehängt werden und Schlüsselwortargumente die im Konstruktor bereitgestellten überschreiben und ergänzen.

Positionsargumente, Schlüsselwortargumente oder beides können bei der Erstellung des Objekts und bei seinem Aufruf angegeben werden.

Anwendungsbeispiele

Daher ist partial(operator.add, 1) so etwas wie (lambda x: 1 + x). Kein Beispiel, bei dem man die Vorteile sieht, versteht sich.

Beachten Sie auch, dass Sie eine Klasse auf die gleiche Weise wrappen können, da Klassen selbst aufrufbare Fabriken für Objekte sind. In einigen Fällen können Sie also Klassen spezialisieren, indem Sie die Argumente des Konstruktors partiell anwenden, anstatt eine Unterklasse zu definieren.

Zum Beispiel macht partial(Tkinter.Label, fg='blue') Tkinter Labels, die standardmäßig eine blaue Vordergrundfarbe haben.

Hier ist ein einfaches Beispiel, das partielle Anwendung verwendet, um dynamisch Callbacks für Tkinter-Widgets zu erstellen

from Tkinter import Tk, Canvas, Button
import sys
from functional import partial

win = Tk()
c = Canvas(win,width=200,height=50)
c.pack()

for colour in sys.argv[1:]:
    b = Button(win, text=colour,
               command=partial(c.config, bg=colour))
    b.pack(side='left')

win.mainloop()

Abgelehntes Syntax-Vorschlag

Ich habe ursprünglich die Syntax fn@(*args, **kw) vorgeschlagen, was dasselbe bedeutet wie partial(fn, *args, **kw).

Das @-Zeichen wird in einigen Assemblersprachen verwendet, um Registerindirektion zu implizieren, und die Verwendung hier ist ebenfalls eine Art Indirektion. f@(x) ist nicht f(x), sondern ein Ding, das zu f(x) wird, wenn man es aufruft.

Es wurde nicht gut aufgenommen, daher habe ich diesen Teil des Vorschlags zurückgezogen. Auf jeden Fall wurde @ für die neue Decorator-Syntax verwendet.

Feedback von comp.lang.python und python-dev

Unter den geäußerten Meinungen waren die folgenden (die ich zusammenfasse)

  • Lambda ist gut genug.
  • Die @-Syntax ist hässlich (einstimmig).
  • Es ist eigentlich ein Curry und kein Closure. Es gibt eine fast identische Implementierung einer Curry-Klasse im Python Cookbook von ActiveState.
  • Eine Curry-Klasse wäre tatsächlich eine nützliche Ergänzung zur Standardbibliothek.
  • Es handelt sich nicht um Funktionscurrying, sondern um partielle Anwendung. Daher wird nun vorgeschlagen, den Namen auf partial() zu setzen.
  • Es ist vielleicht nicht nützlich genug, um in den Built-ins zu sein.
  • Die Idee eines Moduls namens functional wurde gut aufgenommen, und es gibt andere Dinge, die dorthin gehören (z. B. Funktionskomposition).
  • Der Vollständigkeit halber wurde ein weiteres Objekt vorgeschlagen, das partielle Argumente nach denen anhängt, die im Funktionsaufruf bereitgestellt wurden (vielleicht rightcurry genannt).

Ich stimme zu, dass Lambda normalerweise gut genug ist, aber eben nicht immer. Und ich möchte die Möglichkeit zur nützlichen Introspektion und Unterklassifizierung haben.

Ich stimme nicht zu, dass @ besonders hässlich ist, aber vielleicht bin ich einfach seltsam. Wir haben Wörterbuch-, Listen- und Tupel-Literale, die durch spezielle Zeichen sauber unterschieden sind – ein Weg, partiell angewendete Funktionsliterale direkt auszudrücken, ist keine allzu große Anstrengung. Allerdings hat nicht eine einzige Person gesagt, dass sie es mag, also ist es für mich ein toter Papagei.

Ich stimme der Benennung der Klasse als partial anstelle von curry oder closure zu, daher habe ich den Vorschlag in diesem PEP entsprechend angepasst. Aber nicht durchgängig: Einige falsche Verweise auf „curry“ wurden beibehalten, da die Diskussion zu dieser Zeit dort stattfand.

Partielle Anwendung von Argumenten von rechts oder das Einfügen von Argumenten an beliebigen Positionen schafft eigene Probleme, aber bis zur Entdeckung einer guten Implementierung und nicht verwirrender Semantik denke ich nicht, dass es ausgeschlossen werden sollte.

Carl Banks hat eine Implementierung als echtes funktionales Closure gepostet

def curry(fn, *cargs, **ckwargs):
    def call_fn(*fargs, **fkwargs):
        d = ckwargs.copy()
        d.update(fkwargs)
        return fn(*(cargs + fargs), **d)
    return call_fn

das meiner Zusicherung nach effizienter ist.

Ich habe die Klasse auch in Pyrex kodiert, um abzuschätzen, wie die Leistung durch die Kodierung in C verbessert werden könnte

cdef class curry:

    cdef object fn, args, kw

    def __init__(self, fn, *args, **kw):
        self.fn=fn
        self.args=args
        self.kw = kw

    def __call__(self, *args, **kw):
        if self.kw:        # from Python Cookbook version
            d = self.kw.copy()
            d.update(kw)
        else:
            d=kw
        return self.fn(*(self.args + args), **d)

Der Leistungsgewinn in Pyrex beträgt weniger als 100 % gegenüber der Implementierung mit verschachtelten Funktionen, da sie für vollständige Allgemeinheit über Python API-Aufrufe operieren muss. Aus demselben Grund wird eine C-Implementierung wahrscheinlich nicht viel schneller sein, sodass die Argumente für eine integrierte, in C codierte Lösung nicht sehr stark sind.

Zusammenfassung

Ich bevorzuge, dass ein Mittel zur partiellen Anwendung von Funktionen und anderen aufrufbaren Objekten in der Standardbibliothek vorhanden ist.

Ein Standardbibliotheksmodul functional sollte eine Implementierung von partial enthalten, sowie alle anderen höherrangigen Funktionen, die die Community wünscht. Andere Funktionen, die dorthin gehören könnten, fallen jedoch außerhalb des Geltungsbereichs dieses PEPs.

Patches für die Implementierung, Dokumentation und Unit-Tests (SF Patches 931005, 931007 und 931010) wurden eingereicht, aber noch nicht eingecheckt.

Eine C-Implementierung von Hye-Shik Chang wurde ebenfalls eingereicht, obwohl sie voraussichtlich erst dann aufgenommen wird, wenn die Python-Implementierung sich als nützlich genug erwiesen hat, um optimiert zu werden.

Referenzen


Quelle: https://github.com/python/peps/blob/main/peps/pep-0309.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT