universe-proton / universe-topology

A universal computer knowledge topology for all the programmers worldwide.
Apache License 2.0
50 stars 0 forks source link

Why Python 3 can call unbound method with an instance object of another type as the first argument? #3

Open justdoit0823 opened 7 years ago

justdoit0823 commented 7 years ago

A fews days ago, there occured an error when calling unbound method with an instance object of another type in Python 2 like the following.

In [1]: class Foo:
   ...:     def bar(self):
   ...:         print(self)
   ...:

In [2]: Foo.bar
Out[2]: <unbound method Foo.bar>

In [3]: Foo.bar(1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-8a5b47d332a0> in <module>()
----> 1 Foo.bar(1)

TypeError: unbound method bar() must be called with Foo instance as first argument (got int instance instead)

But it's ok in in Python 3 environment.

In [1]: class Foo:
   ...:     def bar(self):
   ...:         print(self)
   ...:

In [2]: Foo.bar(1)
1

Why this semantic is different between Python 2 and Python3?

Let's look into the CPython implementions.

Instance method call implemention in CPython 2

static PyObject *
instancemethod_call(PyObject *func, PyObject *arg, PyObject *kw)
{
    PyObject *self = PyMethod_GET_SELF(func);
    PyObject *klass = PyMethod_GET_CLASS(func);
    PyObject *result;

    func = PyMethod_GET_FUNCTION(func);
    if (self == NULL) {
        /* Unbound methods must be called with an instance of
           the class (or a derived class) as first argument */
        int ok;
        if (PyTuple_Size(arg) >= 1)
            self = PyTuple_GET_ITEM(arg, 0);
        if (self == NULL)
            ok = 0;
        else {
            ok = PyObject_IsInstance(self, klass);
            if (ok < 0)
                return NULL;
        }
        if (!ok) {
            char clsbuf[256];
            char instbuf[256];
            getclassname(klass, clsbuf, sizeof(clsbuf));
            getinstclassname(self, instbuf, sizeof(instbuf));
            PyErr_Format(PyExc_TypeError,
                         "unbound method %s%s must be called with "
                         "%s instance as first argument "
                         "(got %s%s instead)",
                         PyEval_GetFuncName(func),
                         PyEval_GetFuncDesc(func),
                         clsbuf,
                         instbuf,
                         self == NULL ? "" : " instance");
            return NULL;
        }
        Py_INCREF(arg);
    }
    else {
        Py_ssize_t argcount = PyTuple_Size(arg);
        PyObject *newarg = PyTuple_New(argcount + 1);
        int i;
        if (newarg == NULL)
            return NULL;
        Py_INCREF(self);
        PyTuple_SET_ITEM(newarg, 0, self);
        for (i = 0; i < argcount; i++) {
            PyObject *v = PyTuple_GET_ITEM(arg, i);
            Py_XINCREF(v);
            PyTuple_SET_ITEM(newarg, i+1, v);
        }
        arg = newarg;
    }
    result = PyObject_Call((PyObject *)func, arg, kw);
    Py_DECREF(arg);
    return result;
}

As you can see, the interpreter will check whether the first argument is an instance of function's class, so the integer 1 can't pass.

But the thing differs in CPython 3 implemention.

static PyObject *
instancemethod_call(PyObject *self, PyObject *arg, PyObject *kw)
{
    return PyObject_Call(PyMethod_GET_FUNCTION(self), arg, kw);
}

The Python 3 interpreter simply and roughly call the unbound method with all the arguments.

WTF, it's so different and magical.

Should the duck type be anywhere in Python 3?