[Python-Dev] type(obj) vs. obj.__class__

Serhiy Storchaka storchaka at gmail.com
Sat Oct 17 18:29:34 EDT 2015


On 18.10.15 00:45, Eric Snow wrote:
> In a recent tracker issue about OrderedDict [1] we've had some
> discussion about the use of type(od) as a replacement for
> od.__class__.  It came up because the pure Python implementation of
> OrderedDict uses self.__class__ in 3 different methods (__repr__,
> __reduce__, and copy).  The patch in that issue changes the C
> implementation to use Py_TYPE(). [2]  So I wanted to get some feedback
> on the practical implications of such a change and if we need to
> clarify the difference more formally.
>
> In this specific case [3] there are 3 questions:
>
> * Should __repr__() for a stdlib class use type(self).__name__ or
> self.__class__.__name__?
> * Should __reduce__() return type(self) or self.__class__?
> * Should copy() use type(self) or self.__class__?
>
> The more general question of when we use type(obj) vs. obj.__class__
> applies to both the language and to all the stdlib as I expect
> consistency there would result in fewer surprises.  I realize that
> there are some places where using obj.__class__ makes more sense (e.g.
> for some proxy support).  There are other places where using type(obj)
> is the way to go (e.g. special method lookup).  However, the
> difference is muddled enough that usage is inconsistent in the stdlib.
> For example, C-implemented types use Py_TYPE() almost exclusively.
>
> So, would it make sense to establish some concrete guidelines about
> when to use type(obj) vs. obj.__class__?  If so, what would those be?
> It may also be helpful to enumerate use cases for "type(obj) is not
> obj.__class__".
>
> -eric
>
>
> [1] http://bugs.python.org/issue25410
> [2] I'm going to open a separate thread about the issue of
> compatibility and C accelerated types.
> [3] https://hg.python.org/cpython/file/default/Lib/collections/__init__.py#l238

Want to add that in common case type(obj) is the same as obj.__class__. 
When you set obj.__class__ (assignment is restricted by issue24912), 
type(obj) is changed as well. You can make obj.__class__ differ from 
type(obj) if set __class__ as class attribute at class creation time, or 
made __class__ a property, or like.

 >>> class A: pass
...
 >>> class B: __class__ = A
...
 >>> type(B())
<class '__main__.B'>
 >>> B().__class__
<class '__main__.A'>

The only places where obj.__class__ made different from type(obj) in the 
stdlib, besides tests, are mock object (hence related to tests), and 
proxy stream in xml.sax.saxutils (I'm not sure that the latter use is 
correct).


About pickling. Default implementation of __reduce_ex__ uses 
obj.__class__ in protocols 0 and 1, and type(obj) in protocols 2+.

 >>> B().__reduce_ex__(1)
(<function _reconstructor at 0xb705965c>, (<class '__main__.A'>, <class 
'object'>, None))
 >>> B().__reduce_ex__(2)
(<function __newobj__ at 0xb70596ec>, (<class '__main__.B'>,), None, 
None, None)

But pickler rejects classes with mismatched type(obj) and obj.__class__ 
in protocols 2+.

 >>> pickle.dumps(B(), 2)
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
_pickle.PicklingError: args[0] from __newobj__ args has the wrong class




More information about the Python-Dev mailing list