pythonのクラスインスタンス化の理解
9935 ワード
英文原文来自Understanding Python Class Instantiation 从PythonWeekly邮件中看到
让我们以一个Foo
类开始:
class Foo(object):
def __init__(self, x, y=0):
self.x = x
self.y = y
クラスの新しいインスタンスをインスタンス化すると何が起こりますか?f = Foo(1, y=2)
Foo
の呼び出しにはいったいどんな関数や方法が呼び出されたのだろうか.多くの初心者や多くの経験のあるPython開発者は、__init__
メソッドを呼び出したとすぐに答えます.1秒をよく考えてみると、これは正しい答えではないことに気づきます.__init__
はオブジェクトを返さなかったが、呼び出しFoo(1, y=2)
はオブジェクトを返した.さらに、__init__
はself
パラメータを予想するが、Foo(1, y=2)
を呼び出すと、ここにはこのパラメータはない.ここにはもっと複雑な仕事があります.この文章では、Pythonでクラスをインスタンス化したときに何が起こったのかを探ってみましょう.
構築順序
Pythonでオブジェクトをインスタンス化するにはいくつかの段階が含まれていますが、そのすばらしさはPythonic(pythonの禅)であることです.これらの手順を理解することで、Python全体について少し理解することができます.Foo
はクラスですが、Pythonのクラスも対象です!クラス、関数、メソッド、およびインスタンスはオブジェクトであり、名前の後ろにカッコを付けると__call__
メソッドが呼び出されます.したがって、Foo(1, y=2)
はFoo.__call__(1, y=2)
に等価である.__call__
メソッドは、Foo
のクラスに定義される.Foo
のクラスは何ですか?>>> Foo.__class__
したがって、Foo
はタイプtype
のオブジェクトであり、__call__
を呼び出してFoo
クラスのオブジェクトを返す.type
の__call__
の方法を見てみましょう.この方法はかなり複雑ですが、できるだけ簡略化してみましょう.以下にCPython CとPyPy Pythonの実装を貼り付けました.ソースコードから答えを探すのは面白いと思いますが、次の簡略化版を直接見ることもできます.
CPython
ソースコード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
ソースコード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
エラーチェックを無視すると、通常のクラスのインスタンス化とほぼ同じです.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__
メソッドは、オブジェクトにメモリ領域を割り当て、「空」オブジェクトとして構築され、__init__
メソッドが呼び出されて初期化されます.総じて、次のようになります. Foo(*args, **kwargs)
はFoo.__call__(*args, **kwargs)
に等価である. Foo
がtype
の例である以上、Foo.__call__(*args, **kwargs)
が実際に呼び出すのはtype.__call__(Foo, *args, **kwargs)
である. type.__call__(Foo, *args, **kwargs)
は、type.__new__(Foo, *args, **kwargs)
を呼び出し、オブジェクトを返します. obj
は、その後、呼び出しobj.__init__(*args, **kwargs)
によって初期化される. obj
が戻される.
カスタム
今、__new__
の方法に注意を向けます.本質的には、実際のオブジェクトの作成を担当する方法です.__new__
法の下位実装の詳細を具体的に探究することはできない.ポイントは、オブジェクトにスペースを割り当て、オブジェクトを返すことです.興味深いことに、__new__
が何をしたかを意識すると、面白いインスタンスの作成方法をカスタマイズすることができます.__new__
は静的な方法ですが、@staticmethod
で宣言する必要はありません.Python解釈器の特例です.__new__
メソッドの素晴らしい力を示す例は、単一のクラスを実現するために使用されることです.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
次に使用します.>>> s1 = Singleton()
... s2 = Singleton()
... s1 is s2
True
この単一のクラスの実装では、__init__
メソッドは、Singleton()
を呼び出すたびに呼び出されるので、処理に注意してください.もう1つの類似例は、Borg design patternを実装することである.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
次に、>>> b1 = Borg()
... b2 = Borg()
... b1 is b2
False
>>> b1.x = 8
... b2.x
8
最後の注意-上記の例は__new__
の力を示していますが、それを使用できることを示しているだけで、あなたがそうすべきではありません.__new__
はPythonの中で最も乱用されやすい特性である.難解で欠陥が多く、ほとんどの使用例でより良い解決策が発見されました(他のPythonツールを使用しています).しかし、__new__
が必要な場合、それは信じられないほど強く、理解に値します.Arion Sprague, Python’s Hidden New
pythonでは、問題の最適な解決策は__new__
の場合が珍しい.面倒なことに、ハンマーを持っていると、どんな問題も釘のように見えます.そうすると、__new__
で解決できる問題が突然たくさん発生する可能性があります.しかし、新しいツールを使用するのではなく、より良い設計に傾くべきです.__new__
は必ずしも良いわけではありません.
リファレンス The Python Language Reference/Data Model Eli Bendersky/Python Object Creation Sequence
補足Foo
が__call__
メソッドを定義している場合、Foo(*args, **kwargs)
はFoo.__call__(*args, **kwargs)
に等しくありません.>>> class Foo:
... def __call__(self):
... print('running __call__')
...
>>> Foo()
<__main__.foo object="" at="">
>>> Foo.__call__()
Traceback (most recent call last):
File "", line 1, in
TypeError: __call__() missing 1 required positional argument: 'self'
In this case, __call__ is used to call instances of the class :
>>> Foo()()
running __call__
>>>
作者:treelake
リンク:http://www.jianshu.com/p/f63ad9d550f1
出典:簡書
著作権は作者の所有である.商業転載は著者に連絡して許可を得てください.非商業転載は出典を明記してください.