Python __call__ spezielle Methode praktisches Beispiel


159

Ich weiß, dass die __call__Methode in einer Klasse ausgelöst wird, wenn die Instanz einer Klasse aufgerufen wird. Ich habe jedoch keine Ahnung, wann ich diese spezielle Methode verwenden kann, da man einfach eine neue Methode erstellen und dieselbe Operation ausführen kann, die in __call__method ausgeführt wird, und anstatt die Instanz aufzurufen, können Sie die Methode aufrufen.

Ich würde es wirklich begrüßen, wenn mir jemand eine praktische Anwendung dieser speziellen Methode geben würde.



8
Die Funktionalität von _call_ entspricht genau dem überladenen Operator von () in C ++ . Wenn Sie einfach eine neue Methode außerhalb der Klasse erstellen, können Sie möglicherweise nicht auf die internen Daten in einer Klasse zugreifen.
Andy

2
Die häufigste Verwendung von __call__ist in der Übersicht verborgen. So instanziieren Sie eine Klasse: Ist x = Foo()wirklich x = type(Foo).__call__(Foo), wo __call__durch die Metaklasse von definiert wird Foo.
Chepner

Antworten:


88

Das Django-Formularmodul verwendet eine __call__Methode, um eine konsistente API für die Formularvalidierung zu implementieren. Sie können Ihren eigenen Validator für ein Formular in Django als Funktion schreiben.

def custom_validator(value):
    #your validation logic

Django verfügt über einige integrierte Standardvalidatoren wie E-Mail-Validatoren, URL-Validatoren usw., die weitgehend unter das Dach der RegEx-Validatoren fallen. Um diese sauber zu implementieren, greift Django auf aufrufbare Klassen (anstelle von Funktionen) zurück. Es implementiert die Standard-Regex-Validierungslogik in einem RegexValidator und erweitert diese Klassen dann für andere Validierungen.

class RegexValidator(object):
    def __call__(self, value):
        # validation logic

class URLValidator(RegexValidator):
    def __call__(self, value):
        super(URLValidator, self).__call__(value)
        #additional logic

class EmailValidator(RegexValidator):
    # some logic

Jetzt können sowohl Ihre benutzerdefinierte Funktion als auch der integrierte EmailValidator mit derselben Syntax aufgerufen werden.

for v in [custom_validator, EmailValidator()]:
    v(value) # <-----

Wie Sie sehen können, ähnelt diese Implementierung in Django dem, was andere in ihren Antworten unten erklärt haben. Kann dies auf andere Weise umgesetzt werden? Sie könnten, aber meiner Meinung nach wird es für ein großes Framework wie Django nicht so lesbar oder so leicht erweiterbar sein.


5
Bei richtiger Verwendung kann der Code also besser lesbar werden. Ich gehe davon aus, dass der Code auch sehr unlesbar wird, wenn er an der falschen Stelle verwendet wird.
Mohi666

15
Dies ist ein Beispiel dafür, wie es verwendet werden kann, aber meiner Meinung nach kein gutes. In diesem Fall hat eine aufrufbare Instanz keinen Vorteil. Es wäre besser, eine Schnittstelle / abstrakte Klasse mit einer Methode wie .validate () zu haben. es ist das gleiche nur expliziter. Der wahre Wert von __call__ besteht darin, eine Instanz an einem Ort verwenden zu können, an dem ein Callable erwartet wird. Ich benutze __call__ zum Beispiel am häufigsten, wenn ich Dekorateure erstelle.
Daniel

120

In diesem Beispiel wird die Memoisierung verwendet , wobei Werte im Grunde genommen in einer Tabelle (in diesem Fall im Wörterbuch) gespeichert werden, damit Sie sie später nachschlagen können, anstatt sie neu zu berechnen.

Hier verwenden wir eine einfache Klasse mit einer __call__Methode zur Berechnung von Fakultäten (über ein aufrufbares Objekt ) anstelle einer Fakultätsfunktion, die eine statische Variable enthält (da dies in Python nicht möglich ist).

class Factorial:
    def __init__(self):
        self.cache = {}
    def __call__(self, n):
        if n not in self.cache:
            if n == 0:
                self.cache[n] = 1
            else:
                self.cache[n] = n * self.__call__(n-1)
        return self.cache[n]

fact = Factorial()

Jetzt haben Sie ein factObjekt, das wie jede andere Funktion aufgerufen werden kann. Beispielsweise

for i in xrange(10):                                                             
    print("{}! = {}".format(i, fact(i)))

# output
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880

Und es ist auch zustandsbehaftet.


2
Ich hätte lieber ein factindizierbares Objekt, da Ihre __call__Funktion im Wesentlichen ein Index ist. Würde auch eine Liste anstelle eines Diktats verwenden, aber das bin nur ich.
Chris Lutz

4
@delnan - Fast alles kann auf verschiedene Arten gemacht werden. Welche besser lesbar ist, hängt vom Leser ab.
Chris Lutz

1
@ Chris Lutz: Sie können über solche Änderungen nachdenken. Für das Auswendiglernen im Allgemeinen funktioniert ein Wörterbuch gut, da Sie nicht garantieren können, in welcher Reihenfolge die Dinge Ihre Liste füllen. In diesem Fall könnte eine Liste funktionieren, aber sie wird nicht schneller oder einfacher sein.
S.Lott

8
@delnan: Dies soll nicht die kürzeste sein. Niemand gewinnt beim Code Golf. Es soll zeigen __call__, einfach sein und nichts weiter.
S.Lott

3
Aber es ruiniert das Beispiel, wenn die demonstrierte Technik nicht ideal für die Aufgaben ist, nicht wahr? (Und ich war nicht über die "Lass uns Zeilen für den Teufel speichern" -Kurz, ich sprach über die "Schreibe es auf diese ebenso klare Weise und speichere einen Boilerplate-Code" -Kurz. Seien Sie versichert, dass ich es nicht bin Als einer dieser Verrückten, die versuchen, den kürzestmöglichen Code zu schreiben, möchte ich lediglich Boilerplate-Code vermeiden, der dem Leser nichts hinzufügt.)

40

Ich finde es nützlich, weil ich damit APIs erstellen kann, die einfach zu verwenden sind (Sie haben ein aufrufbares Objekt, für das bestimmte Argumente erforderlich sind) und das einfach zu implementieren ist, weil Sie objektorientierte Methoden verwenden können.

Das Folgende ist Code, den ich gestern geschrieben habe und der eine Version der hashlib.fooMethoden erstellt, die ganze Dateien anstelle von Zeichenfolgen hashen:

# filehash.py
import hashlib


class Hasher(object):
    """
    A wrapper around the hashlib hash algorithms that allows an entire file to
    be hashed in a chunked manner.
    """
    def __init__(self, algorithm):
        self.algorithm = algorithm

    def __call__(self, file):
        hash = self.algorithm()
        with open(file, 'rb') as f:
            for chunk in iter(lambda: f.read(4096), ''):
                hash.update(chunk)
        return hash.hexdigest()


md5    = Hasher(hashlib.md5)
sha1   = Hasher(hashlib.sha1)
sha224 = Hasher(hashlib.sha224)
sha256 = Hasher(hashlib.sha256)
sha384 = Hasher(hashlib.sha384)
sha512 = Hasher(hashlib.sha512)

Diese Implementierung ermöglicht es mir, die Funktionen auf ähnliche Weise wie die hashlib.fooFunktionen zu verwenden:

from filehash import sha1
print sha1('somefile.txt')

Natürlich hätte ich es anders implementieren können, aber in diesem Fall schien es ein einfacher Ansatz zu sein.


7
Wiederum ruinieren Verschlüsse dieses Beispiel. pastebin.com/961vU0ay ist 80% der Linien und genauso klar.

8
Ich bin nicht davon überzeugt, dass es jemandem immer genauso klar sein würde (z. B. vielleicht jemandem, der nur Java verwendet hat). Verschachtelte Funktionen und die Suche / der Bereich von Variablen können verwirrend sein. Ich nehme an, mein Punkt war, dass __call__Sie ein Tool erhalten, mit dem Sie OO-Techniken verwenden können, um Probleme zu lösen.
Bradley.ayers

4
Ich denke, die Frage "Warum X über Y verwenden", wenn beide gleichwertige Funktionen bieten, ist schrecklich subjektiv. Für einige Menschen ist der OO-Ansatz leichter zu verstehen, für andere der Abschlussansatz. Es gibt kein überzeugendes Argument übereinander zu verwenden, es sei denn , Sie eine Situation gehabt , wo Sie hatte zu verwenden , um isinstanceähnliche oder etwas.
Bradley.ayers

2
@delnan Ihr Schließungsbeispiel besteht aus weniger Codezeilen, aber dass es genauso klar ist, ist schwieriger zu argumentieren.
Dennis

8
Ein Beispiel dafür, wo Sie lieber eine __call__Methode anstelle eines Abschlusses verwenden möchten, ist das Multiprozessor-Modul, das mithilfe von Beizen Informationen zwischen Prozessen weitergibt. Sie können einen Abschluss nicht auswählen, aber Sie können eine Instanz einer Klasse auswählen.
John Peter Thompson Garcés

21

__call__wird auch verwendet, um Dekorationsklassen in Python zu implementieren. In diesem Fall wird die Instanz der Klasse aufgerufen, wenn die Methode mit dem Dekorator aufgerufen wird.

class EnterExitParam(object):

    def __init__(self, p1):
        self.p1 = p1

    def __call__(self, f):
        def new_f():
            print("Entering", f.__name__)
            print("p1=", self.p1)
            f()
            print("Leaving", f.__name__)
        return new_f


@EnterExitParam("foo bar")
def hello():
    print("Hello")


if __name__ == "__main__":
    hello()

9

Ja, wenn Sie wissen, dass Sie mit Objekten arbeiten, ist es durchaus möglich (und in vielen Fällen ratsam), einen expliziten Methodenaufruf zu verwenden. Manchmal beschäftigen Sie sich jedoch mit Code, der aufrufbare Objekte erwartet - normalerweise Funktionen. Dank dieser Funktion __call__können Sie jedoch komplexere Objekte mit Instanzdaten und mehr Methoden zum Delegieren sich wiederholender Aufgaben usw. erstellen, die noch aufrufbar sind.

Manchmal verwenden Sie auch sowohl Objekte für komplexe Aufgaben (wo es sinnvoll ist, eine dedizierte Klasse zu schreiben) als auch Objekte für einfache Aufgaben (die bereits in Funktionen vorhanden sind oder einfacher als Funktionen geschrieben werden können). Um eine gemeinsame Schnittstelle zu haben, müssen Sie entweder winzige Klassen schreiben, die diese Funktionen mit der erwarteten Schnittstelle umschließen, oder Sie behalten die Funktionsfunktionen bei und machen die komplexeren Objekte aufrufbar. Nehmen wir als Beispiel Threads. Die ThreadObjekte aus dem Standard-Bibliotheksmodulthreading möchten, dass ein targetArgument als Argument aufgerufen werden kann (dh als Aktion, die im neuen Thread ausgeführt werden soll). Mit einem aufrufbaren Objekt sind Sie nicht auf Funktionen beschränkt, sondern können auch andere Objekte übergeben, z. B. einen relativ komplexen Worker, der Aufgaben von anderen Threads ausführt und diese nacheinander ausführt:

class Worker(object):
    def __init__(self, *args, **kwargs):
        self.queue = queue.Queue()
        self.args = args
        self.kwargs = kwargs

    def add_task(self, task):
        self.queue.put(task)

    def __call__(self):
        while True:
            next_action = self.queue.get()
            success = next_action(*self.args, **self.kwargs)
            if not success:
               self.add_task(next_action)

Dies ist nur ein Beispiel aus meinem Kopf, aber ich denke, es ist bereits komplex genug, um die Klasse zu rechtfertigen. Dies nur mit Funktionen zu tun ist schwierig, zumindest müssen zwei Funktionen zurückgegeben werden, und das wird langsam komplex. Man könnte__call__ in etwas anderes umbenennen und eine gebundene Methode übergeben, aber das macht den Code, der den Thread erstellt, etwas weniger offensichtlich und fügt keinen Wert hinzu.


3
Es ist wahrscheinlich nützlich, hier den Ausdruck "Ententypisierung" ( en.wikipedia.org/wiki/Duck_typing#In_Python ) zu verwenden. Auf diese Weise können Sie eine Funktion mit einem komplizierteren Klassenobjekt nachahmen .
Andrew Jaffe

2
Als verwandtes Beispiel habe ich gesehen __call__, dass Klasseninstanzen (anstelle von Funktionen) als WSGI-Anwendungen verwendet wurden. Hier ist ein Beispiel aus "The Definitive Guide to Pylons": Verwenden von Instanzen von Klassen
Josh Rosen

5

Klassenbasierte Dekorateure __call__verweisen auf die umschlossene Funktion. Z.B:

class Deco(object):
    def __init__(self,f):
        self.f = f
    def __call__(self, *args, **kwargs):
        print args
        print kwargs
        self.f(*args, **kwargs)

Eine gute Beschreibung der verschiedenen Optionen finden Sie hier bei Artima.com


Ich sehe jedoch selten Klassendekorateure, da sie für die Arbeit mit Methoden nicht offensichtlichen Code benötigen.

4

IMHO- __call__Methode und -Verschlüsse bieten uns eine natürliche Möglichkeit, STRATEGIE-Entwurfsmuster in Python zu erstellen. Wir definieren eine Familie von Algorithmen, kapseln jeden, kapseln sie austauschbar und können am Ende eine Reihe gemeinsamer Schritte ausführen und beispielsweise einen Hash für eine Datei berechnen.


4

Ich bin gerade auf eine Verwendung von __call__()im Konzert gestoßen, mit __getattr__()der ich schön finde. Sie können mehrere Ebenen einer JSON / HTTP / (jedoch_serialisierten) API in einem Objekt ausblenden.

Der __getattr__()Teil sorgt dafür, dass iterativ eine geänderte Instanz derselben Klasse zurückgegeben wird, wobei jeweils ein weiteres Attribut ausgefüllt wird. Nachdem alle Informationen erschöpft sind, __call__()übernimmt er die von Ihnen eingegebenen Argumente.

Mit diesem Modell können Sie beispielsweise einen Anruf wie tätigen api.v2.volumes.ssd.update(size=20), der in einer PUT-Anfrage an endet https://some.tld/api/v2/volumes/ssd/update.

Der jeweilige Code ist ein Blockspeichertreiber für ein bestimmtes Volume-Backend in OpenStack. Sie können ihn hier überprüfen: https://github.com/openstack/cinder/blob/master/cinder/volume/drivers/nexenta/jsonrpc.py

BEARBEITEN: Der Link wurde aktualisiert, um auf die Master-Revision zu verweisen.


Das ist schön. Ich habe einmal denselben Mechanismus zum Durchlaufen eines beliebigen XML-Baums mithilfe des Attributzugriffs verwendet.
Petri

1

Geben Sie a an __metaclass__und überschreiben Sie die __call__Methode. Lassen Sie die Methode der angegebenen Metaklassen __new__eine Instanz der Klasse zurückgeben. Viola, Sie haben eine "Funktion" mit Methoden.


1

Wir können die __call__Methode verwenden, um andere Klassenmethoden als statische Methoden zu verwenden.

    class _Callable:
        def __init__(self, anycallable):
            self.__call__ = anycallable

    class Model:

        def get_instance(conn, table_name):

            """ do something"""

        get_instance = _Callable(get_instance)

    provs_fac = Model.get_instance(connection, "users")             

0

Ein häufiges Beispiel ist das __call__In functools.partial, hier ist eine vereinfachte Version (mit Python> = 3.5):

class partial:
    """New function with partial application of the given arguments and keywords."""

    def __new__(cls, func, *args, **kwargs):
        if not callable(func):
            raise TypeError("the first argument must be callable")
        self = super().__new__(cls)

        self.func = func
        self.args = args
        self.kwargs = kwargs
        return self

    def __call__(self, *args, **kwargs):
        return self.func(*self.args, *args, **self.kwargs, **kwargs)

Verwendung:

def add(x, y):
    return x + y

inc = partial(add, y=1)
print(inc(41))  # 42

0

Der Funktionsaufrufoperator.

class Foo:
    def __call__(self, a, b, c):
        # do something

x = Foo()
x(1, 2, 3)

Die Methode __call__ kann verwendet werden, um dasselbe Objekt neu zu definieren / neu zu initialisieren. Es erleichtert auch die Verwendung von Instanzen / Objekten einer Klasse als Funktionen, indem Argumente an die Objekte übergeben werden.


Wann wäre es nützlich? Foo (1, 2, 3) scheint klarer zu sein.
Jaroslaw Nikitenko

0

Ich einen guten Platz finden aufrufbare Objekte zu verwenden, die , die definieren __call__(), ist , wenn die funktionalen Programmierfunktionen in Python, wie map(), filter(), reduce().

Die beste Zeit, um ein aufrufbares Objekt über eine einfache Funktion oder eine Lambda-Funktion zu verwenden, ist, wenn die Logik komplex ist und einen bestimmten Status beibehalten muss oder andere Informationen verwendet, die nicht an die __call__()Funktion übergeben wurden.

Hier ist ein Code, der Dateinamen basierend auf ihrer Dateinamenerweiterung mithilfe eines aufrufbaren Objekts und filtert filter().

Abrufbar:

import os

class FileAcceptor(object):
    def __init__(self, accepted_extensions):
        self.accepted_extensions = accepted_extensions

    def __call__(self, filename):
        base, ext = os.path.splitext(filename)
        return ext in self.accepted_extensions

class ImageFileAcceptor(FileAcceptor):
    def __init__(self):
        image_extensions = ('.jpg', '.jpeg', '.gif', '.bmp')
        super(ImageFileAcceptor, self).__init__(image_extensions)

Verwendung:

filenames = [
    'me.jpg',
    'me.txt',
    'friend1.jpg',
    'friend2.bmp',
    'you.jpeg',
    'you.xml']

acceptor = ImageFileAcceptor()
image_filenames = filter(acceptor, filenames)
print image_filenames

Ausgabe:

['me.jpg', 'friend1.jpg', 'friend2.bmp', 'you.jpeg']

0

Das ist zu spät, aber ich gebe ein Beispiel. Stellen Sie sich vor, Sie haben eine VectorKlasse und eine PointKlasse. Beide nehmen x, yals Positionsargumente. Stellen Sie sich vor, Sie möchten eine Funktion erstellen, die den Punkt verschiebt, der auf den Vektor gesetzt werden soll.

4 Lösungen

  • put_point_on_vec(point, vec)

  • Machen Sie es zu einer Methode für die Vektorklasse. z.B my_vec.put_point(point)

  • Machen Sie es zu einer Methode für die PointKlasse.my_point.put_on_vec(vec)
  • Vectorimplementiert __call__, so können Sie es wie verwendenmy_vec_instance(point)

Dies ist tatsächlich Teil einiger Beispiele, an denen ich für einen Leitfaden für Dunder-Methoden arbeite, die mit Maths erklärt wurden und die ich früher oder später veröffentlichen werde.

Ich habe die Logik verlassen, den Punkt selbst zu verschieben, weil es nicht um diese Frage geht

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.