Python-Instanziierung

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; /* Ugly exception: when the call was type(something), don't call tp_init on the result. */ if (type == &PyType_Type && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 && (kwds == NULL || (PyDict_Check(kwds) && PyDict_Size(kwds) == 0))) return obj; /* If the returned object is not an instance of type, it won't be initialized. */ 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) # invoke the __new__ of the type if not we_are_jitted(): # note that the annotator will figure out that self.w_new_function # can only be None if the newshortcut config option is not set w_newfunc = self.w_new_function else: # for the JIT it is better to take the slow path because normal lookup # is nicely optimized, but the self.w_new_function attribute is not # known to the JIT w_newfunc = None if w_newfunc is None: w_newtype, w_newdescr = self.lookup_where('__new__') if w_newdescr is None: # see test_crash_mro_without_object_1 raise oefmt(space.w_TypeError, "cannot create '%N' instances", self) w_newfunc = space.get(w_newdescr, self) if (space.config.objspace.std.newshortcut and not we_are_jitted() and isinstance(w_newtype, W_TypeObject)): self.w_new_function = w_newfunc w_newobject = space.call_obj_args(w_newfunc, self, __args__) call_init = space.isinstance_w(w_newobject, self) # maybe invoke the __init__ of the type if (call_init and not (space.is_w(self, space.w_type) and not __args__.keywords and len(__args__.arguments_w) == 1)): w_descr = space.lookup(w_newobject, '__init__') if w_descr is not None: # see test_crash_mro_without_object_2 w_result = space.get_and_call_args(w_descr, w_newobject, __args__) if not space.is_w(w_result, space.w_None): raise oefmt(space.w_TypeError, "__init__() should return None") return w_newobject 



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:


  1. Foo (* args, ** kwargs) entspricht Foo .__ call __ (* args, ** kwargs) .
  2. Da das Foo- Objekt eine Instanz der Typklasse ist , entspricht der Aufruf von Foo .__ call __ (* args, ** kwargs) dem Aufruf von .__ call __ (Foo, * args, ** kwargs) .
  3. type .__ call __ (Foo, * args, ** kwargs) ruft die Methode type .__ new __ (Foo, * args, ** kwargs) auf , die obj zurückgibt .
  4. obj wird durch den Aufruf von obj .__ init __ (* args, ** kwargs) initialisiert .
  5. 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 .

Source: https://habr.com/ru/post/de480022/


All Articles