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)
  • に等価である.
  • Footypeの例である以上、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
    出典:簡書
    著作権は作者の所有である.商業転載は著者に連絡して許可を得てください.非商業転載は出典を明記してください.