Khởi tạo lớp trong Python
Tìm hiểu về cách khởi tạo lớp trong Python
Giả sử bạn có một lớp
Foo:class Foo(object):
def __init__(self, x, y=0):
self.x = x
self.y = y
Điều gì xảy ra khi bạn khởi tạo nó (tạo một
instance của lớp đó)?f = Foo(1, y=2)
Câu hỏi đặt ra ở đây là khi thực hiện cuộc gọi
Foo() - thì chức năng hoặc phương thức nào đang được gọi ở đó? Hầu hết những người mới bắt đầu và thậm chí là nhiều lập trình viên python có kinh nghiệm sẽ trả lời ngay lập tức là __init__ sẽ được gọi.
Tuy nhiên, đây không phải là một câu trả lời đúng!
__init__ không trả về một đối tượng, tuy nhiên, nếu gọi Foo(1, y=2) sẽ trả lại một đối tượng. Ngoài ra __init__ mong đợi một tham số self nhưng không có tham số đó khi gọi Foo(1, y=2). Có điều gì đó phức tạp hơn đã xảy ra ở đây?.
Chúng ta sẽ tìm hiểu xem nhưng gì đã xảy ra khi khởi tạo một lớp trong Python.
Trình tự khởi tạo
Khởi tạo một lớp trong Python bao gồm một vài giai đoạn, bản thân nó chính là
Pythonic, hiểu các giai đoạn này sẽ giúp cho ta hiểu thêm về Python. Foo là một lớp và chúng chính là đối tượng!.
Ngoài ra, các lớp, các hàm, phương thức và instance cũng là các đối tượng. Bất cứ khi nào bạn đặt dấu ngoặc đơn sau tên của chúng thì đồng nghĩa với việc bạn gọi phương thức
__call__ của chúng.
Như vậy
Foo(1, y=2) tương đương với Foo.__call__(1, y=2).>>> Foo.__class__
#<class 'type'>
Ta có thể thấy,
Foo là một đối tượng của kiểu type và gọi __call__ sẽ trả về một đối tượng của lớp Foo. Tếp theo, chúng ta sẽ xem phương thức __call__ cho type sẽ như thế nào. Chúng ta có thể tham khảo đoạn mã ban đầu của nó TypeObject
Hoặc đoạn mã đã đơn giản hóa
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;
}
Hoặc tóm gọn hơn trong Python thì điều này tương đương với:
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__ cấp phát bộ nhớ cho đối tượng, xây dựng nó như một đối tượng rỗng và sau đó __init__ được gọi để khởi tạo nó.
Cuối cùng:
Foo(*args, **kwargs)tương đương vớiFoo.__call__(*args, **kwargs).- Vì Foo là một instance của
type,Foo.__call__(*args, **kwargs)sẽ gọitype.__call__(Foo, *args, **kwargs). type.__call__(Foo, *args, **kwargs)sẽ gọitype.__new__(Foo, *args, **kwargs)mà trả vềobj.objsau đó được khởi tạo bằng cách gọiobj.__init__(*args, **kwargs).objĐược trả về.
Tùy chỉnh
Bây giờ chúng ta sẽ xem xét đến phương thức
__new__. Về có bản, nó là phương thức chịu trách nhiệm tạo đối tượng thực tế hay nói cách khác nó phân bổ không gian cho đối tượng và trả về nó.
Điều thú vị ở đây là khi bạn hiểu những gì
__new__ có thể làm, bạn có thể sử dụng nó để tùy chỉnh việc tạo đối tượng theo những cách thú vị. Cần lưu ý rằng, mặc dù __new__ là một phương thức tĩnh, nhưng bạn không cần khai báo nó với @staticmethod - nó được trình thông dịch Python xử lý đặc biệt.
Một ví dụ điển hình về sức mạnh của
__new__ trong việc sử dụng nó để triển khai lớp Singleton sau: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
sau đó:
>>> s1 = Singleton()
>>> s2 = Singleton()
>>> s1 is s2
True
Lưu ý rằng trong việc triển khai lớp singleton này,
__init__ sẽ được gọi mỗi làn Singleton() được gọi, vì vậy hãy cẩn thận.
Một ví dụ khác:
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
Kết quả:
>>> b1 = Borg()
b2 = Borg()
b1 is b2
False
>>> b1.x = 8
b2.x
8
Một lưu ý cuối cùng - các ví dụ trên cho thấy sức mạnh của
__new__, nhưng bạn có thể sử dụng nó, không có nghĩa là bạn nên dùng nó:__new__là một trong những tính năng dễ bị lạm dụng nhất trong Python. Nó tối nghĩa, bị đánh đố với những cạm bẫy và hầu hết mọi trường hợp sử dụng mà tôi tìm thấy cho nó đều được phục vụ tốt hơn bởi nhiều công cụ khác của Python. Tuy nhiên, khi bạn cần__new__, nó vô cùng mạnh mẽ và vô giá để hiểu.- Arion Sprague, Python's Hidden New
Kết luận
Rất hiếm khi bạn cần giải quyết một vấn đề trong python mà cần sử dụng đến
__new__. Tuy nhiên có những vấn đề mà __new__ có thể giải quyết được ngay. Vì vậy hãy cẩn trọng khi sử dụng!Tag:
Python nâng cao
Khởi tạo lớp trong Python
Reviewed by Finn
on
June 08, 2019
Rating:
Reviewed by Finn
on
June 08, 2019
Rating:


No comments: