Unterschied zwischen foo.bar () und bar (foo)?

8

Überlegen Sie:

%Vor%

Das Obenstehende stammt vom Udacity Python-Kurs. Ich habe festgestellt, dass ich show_info für Beispiel billy_cyrus mit einem der folgenden Verfahren aufrufen kann:

%Vor%

Ich bin neugierig, warum. Gibt es einen Unterschied zwischen den beiden Methoden? Wenn ja, wann würde man gegen die andere verwendet werden? Ich verwende Python 3.6, wenn das wichtig ist.

    
Chris Decker 13.10.2017, 19:49
quelle

2 Antworten

12

In Bezug auf den Aufruf der Methode gibt es meistens keinen Unterschied. In Bezug darauf, wie die zugrunde liegende Maschinerie funktioniert, gibt es einen Unterschied.

Da show_info eine Methode ist, ist es ein descriptor in der Klasse. Das bedeutet, wenn Sie über eine Instanz darauf zugreifen, in der sie nicht von einem anderen Schatten gespiegelt wird Attribut , der Operator . ruft __get__ für den Deskriptor, um eine gebundene Methode für diese Instanz zu erstellen. Eine gebundene Methode ist im Grunde eine Closure, die den Parameter self für Sie vor allen anderen von Ihnen übergebenen Argumenten übergibt. Sie können die Bindung wie folgt sehen:

%Vor%

Jedes Mal, wenn Sie den Operator . für eine Klassenmethode verwenden, wird ein anderer Abschluss erstellt.

Wenn Sie dagegen über das Klassenobjekt auf die Methode zugreifen, wird sie nicht gebunden. Die Methode ist ein Deskriptor, der nur ein reguläres Attribut der Klasse ist:

%Vor%

Sie können das genaue Verhalten des Bindens einer Methode vor dem Aufruf simulieren, indem Sie ihr __get__ self: aufrufen:

%Vor%

Auch dies wird in 99,99% der Fälle für Sie keinen Unterschied machen, da die Funktionen bound_meth() und Parent.bound_meth(billy_cyrus) letztendlich dasselbe zugrundeliegende Funktionsobjekt mit denselben Parametern aufrufen.

Wo es darauf ankommt

Es gibt ein paar Orte, an denen es wichtig ist, wie Sie eine Klassenmethode aufrufen. Ein häufiger Anwendungsfall ist, wenn Sie eine Methode überschreiben, aber die in der übergeordneten Klasse angegebene Definition verwenden möchten. Angenommen, ich habe eine Klasse, die ich "unveränderbar" gemacht habe, indem ich __setattr__ . Ich kann immer noch Attribute für die Instanz festlegen, wie in der folgenden Methode __init__ :

%Vor%

Wenn ich versucht habe, einen normalen Aufruf von __setattr__ in __init__ zu machen, indem ich self.a = a mache, würde ein ValueError jedes Mal ausgelöst. Aber mit object.__setattr__ kann ich diese Einschränkung umgehen. Alternativ könnte ich super().__setattr__('a', a) für den gleichen Effekt oder self.__dict__['a'] = a für einen sehr ähnlichen Effekt verwenden.

@Silvio Mayolos Antwort hat ein anderes gutes Beispiel, in dem Sie die Klassenmethode absichtlich als eine Funktion verwenden möchten, die auf viele Objekte angewendet werden kann.

Ein anderer wichtiger Punkt (wenn auch nicht in Bezug auf Aufrufmethoden) ist, wenn Sie andere gebräuchliche Deskriptoren wie property . Im Gegensatz zu Methoden sind Eigenschaften Datendeskriptoren . Dies bedeutet, dass sie eine __set__ Methode (und optional __delete__ ) zusätzlich zu __get__ definieren. Eine Eigenschaft erstellt ein virtuelles Attribut, dessen Getter und Setter beliebig komplexe Funktionen sind und nicht nur einfache Zuweisungen. Um eine Eigenschaft richtig zu verwenden, müssen Sie dies über die Instanz tun. Zum Beispiel:

%Vor%

Jetzt können Sie etwas wie

tun %Vor%

Wenn Sie versuchen, über die Klasse auf die Eigenschaft zuzugreifen, können Sie das zugrunde liegende Deskriptorobjekt abrufen, da es ein ungebundenes Attribut ist:

%Vor%

Nebenbei bemerkt, das Ausblenden von Attributen mit demselben Namen wie eine Eigenschaft in __dict__ ist ein netter Trick, der funktioniert, weil Datendeskriptoren in einer Klasse __dict__ Einträge in der Instanz __dict__ trumpfen, obwohl Instanz __dict__ Einträge übertrumpfen Nicht-Daten-Deskriptoren in einer Klasse.

Wo es seltsam werden kann

Sie können eine Klassenmethode mit einer Instanzmethode in Python überschreiben. Das würde bedeuten, dass type(foo).bar(foo) und foo.bar() überhaupt nicht dieselbe zugrunde liegende Funktion aufrufen. Dies ist für magische Methoden irrelevant, da sie immer den vorherigen Aufruf verwenden, aber es kann einen großen Unterschied für normale Methodenaufrufe machen.

Es gibt mehrere Möglichkeiten, eine Methode für eine Instanz zu überschreiben . Dasjenige, das ich am intuitivsten finde, ist, das Instanzattribut auf eine gebundene Methode zu setzen. Hier ist ein Beispiel für eine modifizierte billy_cyrus unter der Annahme der Definition von Parent in der ursprünglichen Frage:

%Vor%

In diesem Fall hätte der Aufruf der Methode für die Instanz gegenüber der Klasse vollständig unterschiedliche Ergebnisse. Dies funktioniert nur, weil Methoden übrigens Nicht-Daten Deskriptoren sind. Wenn sie Datendeskriptoren wären (mit einer __set__ -Methode), würde die Zuweisung billy_cyrus.show_info = alt_show_info.__get__(billy_cyrus, Parent) nichts überschreiben, sondern stattdessen nur zu __set__ umleiten und sie manuell in b setzen billy_cyrus 's __dict__ würde es einfach ignorieren, wie es bei einer Eigenschaft passiert.

Zusätzliche Ressourcen

Hier sind ein paar Ressourcen zu Deskriptoren:

Mad Physicist 13.10.2017, 20:11
quelle
6

Es gibt keinen semantischen Unterschied zwischen den beiden. Es ist nur eine Frage des Stils. Normalerweise würden Sie billy_cyrus.show_info() bei normaler Verwendung verwenden, aber die Tatsache, dass der zweite Ansatz zulässig ist, erlaubt Ihnen, Parent.show_info zu verwenden, um die Methode selbst als ein erstklassiges Objekt zu erhalten. Wenn das nicht erlaubt wäre, dann wäre es nicht möglich (oder zumindest ziemlich schwierig), so etwas zu tun.

%Vor%     
Silvio Mayolo 13.10.2017 19:54
quelle

Tags und Links