I run codes in Jupyter console(Python's version is 2.7.14
),
type(1)
Out[71]: int
type.__call__(1)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-72-0ed097e2c413> in <module>()
----> 1 type.__call__(1)
TypeError: descriptor '__call__' requires a 'type' object but received a 'int'
I've guessed type(...)
is a equivalent of type.__call__(...)
but it seems no. So I wonder:
type(1)
or type(name, base, dict)
?type.__call__(...)
will be invoked?Thanks.
We can also use the __class__ property of the object to find the type or class of the object. __class__ is an attribute on the object that refers to the class from which the object was created. The above code creates an instance human_obj of the Human class.
To call a class method, put the class as the first argument. Class methods can be can be called from instances and from the class itself. All of these use the same method. The method can use the classes variables and methods.
When you call an instance method (e.g. func ) from an instance object (e.g. inst ), Python automatically passes that instance object as the first argument, in addition to any other arguments that were passed in by the user.
Short version: There are two reasonable things type.__call__
could resolve to: a bound method representing the method used for calling type
itself, or an unbound method representing the method used for calling instances of type
. You're expecting the first result, but what you actually get is the second.
Long version:
some_callable(thing)
is normally equivalent to some_callable.__call__(thing)
. For example, print(1)
is equivalent to print.__call__(1)
(as long as you have from __future__ import print_function
turned on):
>>> print(1)
1
>>> print.__call__(1)
1
type
is a callable, and type(thing)
would be equivalent to type.__call__(thing)
, except that attribute lookup runs into a complication.
During the type.__call__
attribute lookup, Python searches type
and its superclasses (just object
) for a __dict__
entry with key '__call__'
. Python also searches type
's type and type
's type's superclasses for such a __dict__
entry. (You can see the code responsible for invoking these searches in type_getattro
, the C function that handles attribute lookup for types.) Since type
is its own type, both of these searches find type.__dict__['__call__']
.
One of these searches takes priority. The way the choice is made, and the reason the choice even matters, is the descriptor protocol. If the first search wins, then the descriptor protocol is applied as normal for finding a class attribute (descriptor.__get__(None, cls)
); if the second search wins, the descriptor protocol is applied as normal for finding an instance attribute (descriptor.__get__(instance, cls)
). The second search needs to win for type.__call__(thing)
to behave like type(thing)
, but it would only win if type.__dict__['__call__']
was a data descriptor, and it's not.
The first search wins. The descriptor protocol is applied as normal for finding a class attribute, and the result of looking up type.__call__
is an unbound method. It represents the general __call__
method of instances of type
, rather than the __call__
instance method of type
-as-an-instance-of-type. It would need to be called as
type.__call__(type, 1)
to be equivalent to type(1)
.
After all that, you might be wondering how type(thing)
works without running into all those complications. In terms of language semantics, Python only performs the second search when looking up type
's __call__
method to call it, so the first search can't win because it doesn't even happen. In terms of actual implementation, CPython doesn't actually look up the __call__
method at all; it looks up type
's type, goes to the C-level slot on type
's type corresponding to __call__
, and calls the function it finds. For a __call__
method implemented in Python, the C slot would contain a function that looks up and calls the Python method, but for a __call__
implemented in C, the C slot contains the __call__
implementation directly.
Because type
is a class, you need its __new__
method:
>>> type.__new__(type, 1)
<type 'int'>
Usually, cls(*args, **kwargs)
is equivalent to cls.__new__(cls, *args, **kwargs)
followed by calling .__init__(*args, **kwargs)
on the returned object.
Here's one possible implementation of this behavior of type()
:
class MyType(object):
def __new__(cls, obj):
return obj.__class__
def __init__(self, hahaha):
pass
See blhsing's answer for question 2.
The reason, as @user2357112 said, is that type
acts as both a built-in function (which also doubles as a metaclass but that's not important here) and a class.
type(1)
, in accordance with Built-in Functions — Python 2.7.15 documentation, acts as a regular function that returns the type of an object or creates a new custom type object.
In [13]: type?
Docstring:
type(object) -> the object's type
type(name, bases, dict) -> a new type
Type: type
typeobject.c:type_call()
which is specified as PyType_Type.tp_call
.type.__call__
, on the other hand, is an implementation of the call for objects of the class type
-- i.e. types -- which, as you know, creates an instance of that type. I.e. an unbound method:
In [14]: type.__call__?
Type: wrapper_descriptor
String form: <slot wrapper '__call__' of 'type' objects>
Namespace: Python builtin
Docstring: x.__call__(...) <==> x(...)
typeobject.c:slot_tp_call()
which is returned on __call__
attribute lookup, via typeobject.c:slotdefs[]
whose contents type.__dict__
is filled with during initialization.The reason for the difference in execution paths is that attribute lookup is not done in the first case:
In [16]: dis.dis(compile("type(1)","<stdin>","eval"))
1 0 LOAD_NAME 0 (type)
3 LOAD_CONST 0 (1)
6 CALL_FUNCTION 1
9 RETURN_VALUE
In [17]: dis.dis(compile("type.__call__(1)","<stdin>","eval"))
1 0 LOAD_NAME 0 (type)
3 LOAD_ATTR 1 (__call__)
6 LOAD_CONST 0 (1)
9 CALL_FUNCTION 1
12 RETURN_VALUE
And when calling an object like a function, tp_call
takes priority.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With