Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is type(1) the equivalent of type.__call__(1)?

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:

  1. What have been done by the python interpreter when I call type(1) or type(name, base, dict)?
  2. When the type.__call__(...) will be invoked?

Thanks.

like image 235
Yantao Xie Avatar asked Sep 19 '18 03:09

Yantao Xie


People also ask

What__ class__ Python?

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.

How do you call a class method in Python?

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.

Which parameter is the instance for Python?

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.


3 Answers

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.

like image 157
user2357112 supports Monica Avatar answered Oct 04 '22 01:10

user2357112 supports Monica


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.

like image 25
iBug Avatar answered Oct 04 '22 02:10

iBug


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
  • It's implemented by 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(...)
  • It's implemented by 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.

like image 31
ivan_pozdeev Avatar answered Oct 04 '22 01:10

ivan_pozdeev