Instansiasi python

Katakanlah Anda memiliki kelas Foo :


class Foo(object): def __init__(self, x, y=0): self.x = x self.y = y 

Apa yang terjadi ketika Anda membuat objeknya?


 f = Foo(1, y=2) 

Metode mana yang dipanggil pertama kali dengan panggilan ini ke Foo ? Kebanyakan pendatang baru, dan mungkin beberapa pythonis berpengalaman, akan segera menjawab: "metode __init__ ." Tetapi jika Anda perhatikan dengan saksama cuplikan di atas, akan segera menjadi jelas bahwa jawaban seperti itu salah.


__init__ tidak mengembalikan hasil apa pun, dan Foo (1, y = 2) , sebaliknya, mengembalikan turunan kelas . Selain itu, __init__ menganggap diri sebagai parameter pertama, yang tidak terjadi ketika memanggil Foo (1, y = 2) . Membuat instance sedikit lebih rumit, yang akan kita bicarakan di artikel ini.


Prosedur Pembuatan Objek


Instansiasi python terdiri dari beberapa tahap. Memahami setiap langkah membuat kita sedikit lebih dekat untuk memahami bahasa secara keseluruhan. Foo adalah kelas, tetapi dalam Python, kelas juga objek! Kelas, fungsi, metode dan instance adalah semua objek, dan setiap kali Anda menempatkan tanda kurung di belakang namanya, Anda memanggil metode __call__ mereka. Jadi Foo (1, y = 2) adalah setara dengan Foo. Panggilan __ (1, y = 2) . Selain itu, metode __call__ dideklarasikan di kelas objek Foo . Apa kelas objek Foo ?


 >>> Foo.__class__ <class 'type'> 

Jadi kelas Foo adalah turunan dari kelas tipe dan memanggil metode __call__ yang terakhir mengembalikan kelas Foo . Sekarang mari kita lihat apa metode __call__ dari kelas tipe . Di bawah ini adalah implementasinya dalam C di CPython dan di PyPy. Jika Anda bosan menontonnya, gulir sedikit lebih jauh untuk menemukan versi yang disederhanakan:


Cpython


Tautan ke sumber .


 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


Tautan ke sumber .


 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 



Jika Anda lupa tentang semua jenis pemeriksaan kesalahan, kode di atas kira-kira setara dengan ini:


 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__ mengalokasikan memori untuk objek "kosong" dan memanggil __init__ untuk menginisialisasi.


Untuk meringkas:


  1. Foo (* args, ** kwargs) setara dengan Foo .__ panggil __ (* args, ** kwargs) .
  2. Karena objek Foo adalah turunan dari kelas tipe , memanggil Foo .__ panggilan __ (* args, ** kwargs) setara dengan mengetikkan .__ panggilan __ (Foo, * args, ** kwargs) .
  3. ketik .__ panggilan __ (Foo, * args, ** kwargs) memanggil metode .__ baru __ (Foo, * args, ** kwargs) , yang mengembalikan obj .
  4. obj diinisialisasi dengan memanggil obj .__ init __ (* args, ** kwargs) .
  5. Hasil dari seluruh proses adalah objek yang diinisialisasi.

Kustomisasi


Sekarang mari kita mengalihkan perhatian kita ke __new__ . Metode ini mengalokasikan memori untuk objek dan mengembalikannya. Anda bebas untuk menyesuaikan proses ini dengan berbagai cara. Perlu dicatat bahwa meskipun __new__ adalah metode statis, Anda tidak perlu mendeklarasikannya menggunakan @staticmethod : penerjemah memperlakukan __new__ sebagai kasus khusus.


Contoh umum dari penggantian __new__ baru adalah menciptakan 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 

Lalu:


 >>> s1 = Singleton() ... s2 = Singleton() ... s1 is s2 True 

Perhatikan bahwa __init__ akan dipanggil setiap kali Singleton () dipanggil, jadi harus berhati-hati.


Contoh lain dari penggantian __new__ baru adalah penerapan pola Borg (β€œ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 

Lalu:


 >>> b1 = Borg() ... b2 = Borg() ... b1 is b2 False >>> b1.x = 8 ... b2.x 8 

Ingatlah bahwa meskipun contoh di atas menunjukkan kemungkinan mengganti __new__ baru , ini tidak berarti bahwa itu harus digunakan:


__new__ adalah salah satu korban pelecehan yang paling sering. Apa yang dapat dilakukan dengan mengganti metode ini adalah yang paling sering dicapai dengan cara lain. Namun, ketika itu benar-benar diperlukan, __baru__ adalah alat yang sangat berguna dan kuat.


- Arion Sprag, Tua yang Terlupakan dengan Python


Satu jarang menemukan masalah dalam Python, di mana solusi terbaik adalah menggunakan __new__ . Tetapi ketika Anda memiliki palu, setiap masalah mulai terlihat seperti paku, jadi selalu lebih suka menggunakan alat yang paling kuat untuk menggunakan yang paling cocok .

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


All Articles