Warum verwendet Python "magische Methoden"?

99

Ich habe in letzter Zeit mit Python herumgespielt, und eine Sache, die ich etwas seltsam finde, ist die weit verbreitete Verwendung von 'magischen Methoden', z. B. um seine Länge verfügbar zu machen, ein Objekt implementiert eine Methode def __len__(self), und dann wird es aufgerufen, wenn Sie schreiben len(obj).

Ich habe mich nur gefragt, warum Objekte eine len(self)Methode nicht einfach definieren und sie direkt als Mitglied des Objekts aufrufen lassen, z obj.len(). Ich bin mir sicher, dass es gute Gründe dafür geben muss, dass Python es so macht, aber als Neuling habe ich noch nicht herausgefunden, was sie sind.

Greg Beech
quelle
4
Ich denke, der allgemeine Grund ist a) historisch und b) so etwas wie len()oder reversed()gilt für viele Arten von Objekten, aber eine Methode wie append()nur für Sequenzen usw.
Grant Paul

Antworten:

64

AFAIK lenist in dieser Hinsicht etwas Besonderes und hat historische Wurzeln.

Hier ist ein Zitat aus den FAQ :

Warum verwendet Python Methoden für einige Funktionen (z. B. list.index ()), aber Funktionen für andere (z. B. len (list))?

Der Hauptgrund ist die Geschichte. Funktionen wurden für Operationen verwendet, die für eine Gruppe von Typen generisch waren und die auch für Objekte funktionieren sollten, die überhaupt keine Methoden hatten (z. B. Tupel). Es ist auch praktisch, eine Funktion zu haben, die leicht auf eine amorphe Sammlung von Objekten angewendet werden kann, wenn Sie die Funktionsmerkmale von Python verwenden (map (), apply () et al.).

Tatsächlich ist die Implementierung von len (), max (), min () als integrierte Funktion weniger Code als die Implementierung als Methoden für jeden Typ. Man kann über Einzelfälle streiten, aber es ist ein Teil von Python, und es ist zu spät, um jetzt solche grundlegenden Änderungen vorzunehmen. Die Funktionen müssen erhalten bleiben, um einen massiven Codebruch zu vermeiden.

Die anderen "magischen Methoden" ( in der Python-Folklore eigentlich als spezielle Methode bezeichnet ) sind sehr sinnvoll, und ähnliche Funktionen gibt es auch in anderen Sprachen. Sie werden hauptsächlich für Code verwendet, der implizit aufgerufen wird, wenn eine spezielle Syntax verwendet wird.

Beispielsweise:

  • überladene Operatoren (existieren in C ++ und anderen)
  • Konstruktor / Destruktor
  • Hooks für den Zugriff auf Attribute
  • Werkzeuge für die Metaprogrammierung

und so weiter...

Eli Bendersky
quelle
2
Python und das Prinzip des geringsten Erstaunens sind eine gute Lektüre für einige der Vorteile, die Python auf diese Weise bietet (obwohl ich zugebe, dass Englisch Arbeit braucht). Der grundlegende Punkt: Es ermöglicht der Standardbibliothek, eine Menge Code zu implementieren, der sehr, sehr wiederverwendbar, aber dennoch überschreibbar wird.
jpmc26
20

Aus dem Zen von Python:

Verweigern Sie angesichts von Zweideutigkeiten die Versuchung zu raten.
Es sollte einen - und vorzugsweise nur einen - offensichtlichen Weg geben, dies zu tun.

Dies ist einer der Gründe - mit benutzerdefinierten Methoden, Entwickler frei sein würde , eine andere Methode Namen zu wählen, wie getLength(), length(), getlength()oder was auch immer. Python erzwingt eine strikte Benennung, damit die allgemeine Funktion len()verwendet werden kann.

Alle Operationen , die für viele Arten von Objekten gemeinsam sind , in magische Methoden setzen, wie __nonzero__, __len__oder __repr__. Sie sind jedoch meistens optional.

Das Überladen von Operatoren erfolgt auch mit magischen Methoden (z. B. __le__), daher ist es sinnvoll, sie auch für andere gängige Operationen zu verwenden.

AndiDog
quelle
Dies ist ein überzeugendes Argument. Noch befriedigender, dass "Guido nicht wirklich an OO geglaubt hat" .... (wie ich anderswo behauptet habe).
Andy Hayden
15

Python verwendet das Wort "magische Methoden" , da diese Methoden wirklich Magie für Ihr Programm ausführen. Einer der größten Vorteile der Verwendung der magischen Methoden von Python besteht darin, dass sie eine einfache Möglichkeit bieten, Objekte dazu zu bringen, sich wie integrierte Typen zu verhalten. Das heißt, Sie können hässliche, kontraintuitive und nicht standardmäßige Methoden zur Ausführung grundlegender Operatoren vermeiden.

Betrachten Sie ein folgendes Beispiel:

dict1 = {1 : "ABC"}
dict2 = {2 : "EFG"}

dict1 + dict2
Traceback (most recent call last):
  File "python", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict' and 'dict'

Dies gibt einen Fehler aus, da der Wörterbuchtyp das Hinzufügen nicht unterstützt. Erweitern wir nun die Wörterbuchklasse und fügen die magische Methode "__add__" hinzu :

class AddableDict(dict):

    def __add__(self, otherObj):
        self.update(otherObj)
        return AddableDict(self)


dict1 = AddableDict({1 : "ABC"})
dict2 = AddableDict({2 : "EFG"})

print (dict1 + dict2)

Jetzt gibt es folgende Ausgabe.

{1: 'ABC', 2: 'EFG'}

Durch Hinzufügen dieser Methode ist plötzlich Magie passiert und der Fehler, den Sie früher hatten, ist verschwunden.

Ich hoffe, es macht dir die Dinge klar. Weitere Informationen finden Sie unter:

Ein Leitfaden zu Pythons magischen Methoden (Rafe Kettler, 2012)

Mangu Singh Rajpurohit
quelle
9

Einige dieser Funktionen können mehr als eine einzelne Methode implementieren (ohne abstrakte Methoden in einer Oberklasse). Zum Beispiel bool()verhält es sich so:

def bool(obj):
    if hasattr(obj, '__nonzero__'):
        return bool(obj.__nonzero__())
    elif hasattr(obj, '__len__'):
        if obj.__len__():
            return True
        else:
            return False
    return True

Sie können auch 100% sicher sein, dass bool()immer True oder False zurückgegeben wird. Wenn Sie sich auf eine Methode verlassen würden, könnten Sie nicht ganz sicher sein, was Sie zurückbekommen würden.

Einige andere Funktionen mit relativ komplizierten Implementierungen (die komplizierter sind als die zugrunde liegenden magischen Methoden) sind iter()und cmp()und alle Attributmethoden ( getattr, setattrund delattr). Dinge wie intauch Zugriff auf magische Methoden, wenn Sie Zwang ausüben (Sie können implementieren __int__), aber doppelte Pflicht als Typen. len(obj)ist eigentlich der einzige Fall, in dem ich nicht glaube, dass es jemals anders ist als obj.__len__().

Ian Bicking
quelle
2
Anstelle von hasattr()würde ich try:/ verwenden except AttributeError:und anstelle von if obj.__len__(): return True else: return Falsewürde ich nur sagen, return obj.__len__() > 0aber das sind nur stilistische Dinge.
Chris Lutz
In Python 2.6 (auf das übrigens bool(x)Bezug genommen wird x.__nonzero__()) würde Ihre Methode nicht funktionieren. Bool-Instanzen haben eine Methode __nonzero__(), und Ihr Code würde sich weiterhin selbst aufrufen, sobald obj ein Bool war. Vielleicht bool(obj.__bool__())sollten Sie genauso behandelt werden wie Sie __len__? (Oder funktioniert dieser Code tatsächlich für Python 3?)
Ponkadoodle
Die kreisförmige Natur von bool () war absichtlich etwas absurd, um die besonders kreisförmige Natur der Definition widerzuspiegeln. Es gibt ein Argument, dass es einfach als primitiv betrachtet werden sollte.
Ian Bicking
Der einzige Unterschied (derzeit) zwischen len(x)und x.__len__()besteht darin, dass ersterer OverflowError für Längen auslöst, die größer sind sys.maxsize, während letzterer im Allgemeinen nicht für in Python implementierte Typen gilt. Dies ist jedoch eher ein Fehler als eine Funktion (z. B. kann das Bereichsobjekt von Python 3.2 meist beliebig große Bereiche verarbeiten, die Verwendung lenmit ihnen kann jedoch fehlschlagen. Auch dies __len__schlägt fehl, da sie eher in C als in Python implementiert sind)
ncoghlan
4

Sie sind nicht wirklich "magische Namen". Es ist nur die Schnittstelle, die ein Objekt implementieren muss, um einen bestimmten Dienst bereitzustellen. In diesem Sinne sind sie nicht magischer als jede vordefinierte Schnittstellendefinition, die Sie neu implementieren müssen.

Stefano Borini
quelle
1

Während der Grund größtenteils historisch ist, gibt es in Pythons einige Besonderheiten len, die die Verwendung einer Funktion anstelle einer geeigneten Methode erforderlich machen.

Einige Operationen in Python sind als Methoden implementiert, zum Beispiel list.indexund dict.append, während andere als Callables und magische Methoden implementiert werden, zum Beispiel strund iterund reversed. Die beiden Gruppen unterscheiden sich so stark, dass der unterschiedliche Ansatz gerechtfertigt ist:

  1. Sie sind häufig.
  2. str, intUnd Freunde sind Typen. Es ist sinnvoller, den Konstruktor aufzurufen.
  3. Die Implementierung unterscheidet sich vom Funktionsaufruf. Kann beispielsweise iteraufrufen, __getitem__wenn __iter__es nicht verfügbar ist, und unterstützt zusätzliche Argumente, die nicht in einen Methodenaufruf passen. Aus dem gleichen Grund it.next()wurde next(it)in neueren Versionen von Python geändert - es ist sinnvoller.
  4. Einige davon sind nahe Verwandte von Betreibern. Es gibt eine Syntax für den Aufruf __iter__und __next__- es heißt die forSchleife. Aus Gründen der Konsistenz ist eine Funktion besser. Und es macht es für bestimmte Optimierungen besser.
  5. Einige der Funktionen sind den anderen einfach viel zu ähnlich - verhält sich reprwie strsie. Mit im str(x)Vergleich x.repr()wäre verwirrend.
  6. Einige von ihnen verwenden beispielsweise selten die eigentliche Implementierungsmethode isinstance.
  7. Einige von ihnen sind tatsächliche Betreiber, getattr(x, 'a')sind eine andere Art x.aund Weise und getattrteilen viele der oben genannten Eigenschaften.

Ich persönlich nenne die erste Gruppe methodenähnlich und die zweite Gruppe operatorähnlich. Es ist keine sehr gute Unterscheidung, aber ich hoffe, es hilft irgendwie.

Trotzdem lenpasst es nicht genau in die zweite Gruppe. Es ist näher an den Operationen im ersten, mit dem einzigen Unterschied, dass es weitaus häufiger ist als fast alle anderen. Aber das einzige, was es tut, ist anzurufen __len__, und es ist sehr nahe L.index. Es gibt jedoch einige Unterschiede. Zum Beispiel __len__könnte für die Implementierung anderer Funktionen aufgerufen werden, z. B. boolwenn die Methode aufgerufen wurde len, könnten Sie bool(x)mit einer benutzerdefinierten lenMethode brechen , die völlig andere Dinge tut.

Kurz gesagt, Sie haben eine Reihe sehr häufiger Funktionen, die Klassen implementieren können, auf die über einen Operator, über eine spezielle Funktion (die normalerweise mehr als die Implementierung als Operator ausführt), während der Objektkonstruktion und auf alle zugegriffen werden kann einige gemeinsame Merkmale teilen. Der Rest ist eine Methode. Und lenist eine Ausnahme von dieser Regel.

Rosh Oxymoron
quelle
0

Zu den beiden oben genannten Beiträgen gibt es nicht viel hinzuzufügen, aber alle "magischen" Funktionen sind überhaupt nicht wirklich magisch. Sie sind Teil des Moduls __ builtins__, das beim Start des Interpreters implizit / automatisch importiert wird. Dh:

from __builtins__ import *

geschieht jedes Mal, bevor Ihr Programm startet.

Ich dachte immer, es wäre korrekter, wenn Python dies nur für die interaktive Shell tun würde und Skripte benötigt, um die verschiedenen Teile aus den benötigten Buildins zu importieren. Auch wahrscheinlich wäre eine andere __ main__ Handhabung in Shells nett oder interaktiv. Schauen Sie sich auf jeden Fall alle Funktionen an und sehen Sie, wie es ohne sie ist:

dir (__builtins__)
...
del __builtins__
user318904
quelle