Angenommen, Sie haben eine Klasse Foo :
class Foo(object): def __init__(self, x, y=0): self.x = x self.y = y
Was passiert, wenn Sie das Objekt erstellen?
f = Foo(1, y=2)
Welche Methode wird bei diesem Aufruf von Foo zuerst aufgerufen? Die meisten Neulinge und vielleicht einige erfahrene Pythonisten werden sofort antworten: " __init__ method". Wenn Sie sich jedoch die obigen Ausschnitte genau ansehen, wird schnell klar, dass eine solche Antwort falsch ist.
__init__ gibt kein Ergebnis zurück und Foo (1, y = 2) gibt dagegen eine Instanz der Klasse zurück . Außerdem nimmt __init__ self als ersten Parameter, was beim Aufruf von Foo (1, y = 2) nicht vorkommt . Das Erstellen einer Instanz ist etwas komplizierter, worüber wir in diesem Artikel sprechen werden.
Objekterstellungsverfahren
Die Python-Instanziierung besteht aus mehreren Phasen. Das Verstehen jedes Schritts bringt uns dem Verständnis der Sprache als Ganzes ein Stück näher. Foo ist eine Klasse, aber in Python sind Klassen auch Objekte! Klassen, Funktionen, Methoden und Instanzen sind alle Objekte. Wenn Sie nach ihrem Namen eckige Klammern setzen, rufen Sie die Methode __call__ auf . Also ist Foo (1, y = 2) das Äquivalent von Foo .__ call __ (1, y = 2) . Darüber hinaus wird die Methode __call__ in der Klasse des Foo- Objekts deklariert. Was ist die Klasse des Foo- Objekts?
>>> Foo.__class__ <class 'type'>
Die Foo- Klasse ist also eine Instanz der Typklasse , und der Aufruf der Methode __call__ der letzten gibt die Foo- Klasse zurück. Schauen wir uns nun die __call__- Methode der Typklasse an. Nachfolgend sind die Implementierungen in C in CPython und in PyPy aufgeführt. Wenn Sie es leid sind, sie anzusehen, scrollen Sie ein wenig weiter, um eine vereinfachte Version zu finden:
Cpython
Link zur Quelle .
static PyObject * type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *obj; if (type->tp_new == NULL) { PyErr_Format(PyExc_TypeError, "cannot create '%.100s' instances", type->tp_name); return NULL; } obj = type->tp_new(type, args, kwds); obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL); if (obj == NULL) return NULL; if (type == &PyType_Type && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 && (kwds == NULL || (PyDict_Check(kwds) && PyDict_Size(kwds) == 0))) return obj; if (!PyType_IsSubtype(Py_TYPE(obj), type)) return obj; type = Py_TYPE(obj); if (type->tp_init != NULL) { int res = type->tp_init(obj, args, kwds); if (res < 0) { assert(PyErr_Occurred()); Py_DECREF(obj); obj = NULL; } else { assert(!PyErr_Occurred()); } } return obj; }
Pypy
Link zur Quelle .
def descr_call(self, space, __args__): promote(self)
Wenn Sie alle Arten von Fehlerprüfungen vergessen, entsprechen die obigen Codes ungefähr den folgenden Angaben:
def __call__(obj_type, *args, **kwargs): obj = obj_type.__new__(*args, **kwargs) if obj is not None and issubclass(obj, obj_type): obj.__init__(*args, **kwargs) return obj
__new__ reserviert Speicher für ein "leeres" Objekt und ruft __init__ auf, um es zu initialisieren.
Um zusammenzufassen:
- Foo (* args, ** kwargs) entspricht Foo .__ call __ (* args, ** kwargs) .
- Da das Foo- Objekt eine Instanz der Typklasse ist , entspricht der Aufruf von Foo .__ call __ (* args, ** kwargs) dem Aufruf von .__ call __ (Foo, * args, ** kwargs) .
- type .__ call __ (Foo, * args, ** kwargs) ruft die Methode type .__ new __ (Foo, * args, ** kwargs) auf , die obj zurückgibt .
- obj wird durch den Aufruf von obj .__ init __ (* args, ** kwargs) initialisiert .
- Das Ergebnis des gesamten Prozesses ist ein initialisiertes Objekt .
Anpassung
Wenden wir uns nun __neu__ zu . Diese Methode reserviert Speicher für das Objekt und gibt ihn zurück. Sie können diesen Prozess auf viele verschiedene Arten anpassen. Es ist zu beachten, dass __new__ zwar eine statische Methode ist, Sie sie jedoch nicht mit @staticmethod deklarieren müssen : Der Interpreter behandelt __new__ als Sonderfall.
Ein häufiges Beispiel für __new__- Überschreibungen ist das Erstellen von Singleton:
class Singleton(object): _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance
Dann:
>>> s1 = Singleton() ... s2 = Singleton() ... s1 is s2 True
Beachten Sie, dass __init__ jedes Mal aufgerufen wird, wenn Singleton () aufgerufen wird . Seien Sie also vorsichtig .
Ein weiteres Beispiel für das Überschreiben von __new__ ist die Implementierung des Borg-Musters („Borg“) :
class Borg(object): _dict = None def __new__(cls, *args, **kwargs): obj = super().__new__(cls, *args, **kwargs) if cls._dict is None: cls._dict = obj.__dict__ else: obj.__dict__ = cls._dict return obj
Dann:
>>> b1 = Borg() ... b2 = Borg() ... b1 is b2 False >>> b1.x = 8 ... b2.x 8
Beachten Sie , dass die obigen Beispiele zwar die Möglichkeiten des Überschreibens von __new__ veranschaulichen , dies jedoch nicht bedeutet, dass sie verwendet werden müssen:
__new__ ist eines der häufigsten Opfer von Missbrauch. Was durch Überschreiben dieser Methode erreicht werden kann, wird meistens besser mit anderen Mitteln erreicht. Wenn es jedoch wirklich notwendig ist, ist __new__ ein äußerst nützliches und leistungsstarkes Werkzeug.
- Arion Sprag, das gut vergessene alte in Python
In Python stößt man selten auf ein Problem, bei dem die beste Lösung darin bestand, __new__ zu verwenden. Aber wenn Sie einen Hammer haben, fängt jedes Problem an, wie ein Nagel auszusehen. Verwenden Sie daher immer das leistungsstärkste Werkzeug, um das am besten geeignete zu verwenden .