Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #61979 > unrolled thread

Type of an object: ‘obj.__class__’ versus ‘type(obj)’

Started byBen Finney <ben+python@benfinney.id.au>
First post2013-12-16 12:51 +1100
Last post2013-12-19 00:39 +1300
Articles 12 — 6 participants

Back to article view | Back to comp.lang.python


Contents

  Type of an object: ‘obj.__class__’ versus ‘type(obj)’ Ben Finney <ben+python@benfinney.id.au> - 2013-12-16 12:51 +1100
    Re: Type of an object: ‘obj.__class__’ versus ‘type(obj)’ Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-12-16 22:17 +0000
      Re: Type of an object: ‘obj.__class__’ versus ‘type(obj)’ dieter <dieter@handshake.de> - 2013-12-17 08:14 +0100
        Re: Type of an object: ‘obj.__class__’ versus ‘type(obj)’ Steven D'Aprano <steve@pearwood.info> - 2013-12-17 07:42 +0000
          Re: Type of an object: Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2013-12-17 23:35 +1300
            Re: Type of an object: Ethan Furman <ethan@stoneleaf.us> - 2013-12-17 06:50 -0800
            Re: Type of an object: Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-12-17 15:08 +0000
              Re: Type of an object: Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2013-12-18 11:15 +1300
                Re: Type of an object: Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-12-17 23:51 +0000
                  Re: Type of an object: Ethan Furman <ethan@stoneleaf.us> - 2013-12-17 17:10 -0800
                    Re: Type of an object: Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2013-12-19 00:45 +1300
                  Re: Type of an object: Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2013-12-19 00:39 +1300

#61979 — Type of an object: ‘obj.__class__’ versus ‘type(obj)’

FromBen Finney <ben+python@benfinney.id.au>
Date2013-12-16 12:51 +1100
SubjectType of an object: ‘obj.__class__’ versus ‘type(obj)’
Message-ID<mailman.4162.1387158693.18130.python-list@python.org>
Howdy all,

What is the Pythonic way to determine the type of an object? Are there
multiple valid ways, and when should each be used?

We have ‘obj.__class__’, an attribute bound to the object's class. Or is
it? When is that true, and when should we not rely on it?

We have ‘type(obj)’, calling the constructor for the ‘type’ type in
order to get a reference to the type of ‘obj’. Or is it? When is that
true, and when should we not rely on it?

Are there other ways to get at the type of a Python object? What reasons
are there to choose or avoid them?

-- 
 \         “Pinky, are you pondering what I'm pondering?” “I think so, |
  `\    Brain, but Zero Mostel times anything will still give you Zero |
_o__)                                  Mostel.” —_Pinky and The Brain_ |
Ben Finney

[toc] | [next] | [standalone]


#62107

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-12-16 22:17 +0000
Message-ID<52af7bfe$0$29976$c3e8da3$5496439d@news.astraweb.com>
In reply to#61979
On Mon, 16 Dec 2013 12:51:21 +1100, Ben Finney wrote:

> Howdy all,
> 
> What is the Pythonic way to determine the type of an object? Are there
> multiple valid ways, and when should each be used?

That is an excellent question, I only wish I had an excellent answer to 
give you. Obviously great minds think alike because I was going to ask 
the same question, prompted by this comment from Nick Coghlan on the 
python-dev list:

"...type(obj).__name__ (working with the concrete type, ignoring any
proxying) or obj.__class__.__name__ (which takes proxying into 
account)..."

So there is a difference between them, but I'm not entirely sure what it 
is.


> We have ‘obj.__class__’, an attribute bound to the object's class. Or is
> it? When is that true, and when should we not rely on it?

I think you can rely on it. I don't believe you can delete the __class__ 
attribute from an instance:

py> class X(object):
...     pass
...
py> x = X()
py> x.__class__
<class '__main__.X'>
py> del x.__class__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't delete __class__ attribute


I think it is fair to consider __class__ to be part of the "object API" 
shared by all objects. I suppose it's possible to create a metaclass 
which does not expose a __class__ attribute, but I would consider that 
broken by design.

Furthermore, you can dynamically set the __class__ of an instance in 
order to dynamically change its type and therefore behaviour (although 
there are restrictions on what you can change it to, and from). Using the 
same x instance as above:

py> class Y(object):
...     pass
...
py> x.__class__ = Y
py> type(x)
<class '__main__.Y'>


This is by design, and it allows a very useful form of dynamic behaviour:

http://code.activestate.com/recipes/68429-ring-buffer/


 
> We have ‘type(obj)’, calling the constructor for the ‘type’ type in
> order to get a reference to the type of ‘obj’. Or is it? When is that
> true, and when should we not rely on it?

Are there circumstances where type(obj) and obj.__class__ return 
something different? Based on Nick's comment above, I would have to guess 
the answer must be yes, but I don't know what those circumstances are.


Aside: I'm not sure that it is useful to think of type as the constructor 
in the one-argument form. If you recall, prior to Python 2.2 `type` was a 
regular function which took one argument and returned the argument's type:

[steve@ando ~]$ python1.5
Python 1.5.2 (#1, Aug 27 2012, 09:09:18)  [GCC 4.1.2 20080704 (Red Hat 
4.1.2-52)] on linux2
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> type
<built-in function type>


The types returned were very different from the types we know and love 
since the class/type unification of version 2.2:

>>> type(42)
<type 'int'>
>>> int
<built-in function int>
>>> type(42)('2')
Traceback (innermost last):
  File "<stdin>", line 1, in ?
TypeError: call of non-function (type type)


So while it is *technically* correct that type(...) calls the type 
constructor, the one-argument form type(obj) is intended to behave as a 
function, while the three-argument form type(name, bases, namespace) is 
intended to behave as a constructor of types. But I digress.


> Are there other ways to get at the type of a Python object? What reasons
> are there to choose or avoid them?


I am not aware of any other ways.



-- 
Steven

[toc] | [prev] | [next] | [standalone]


#62158

Fromdieter <dieter@handshake.de>
Date2013-12-17 08:14 +0100
Message-ID<mailman.4265.1387264469.18130.python-list@python.org>
In reply to#62107
Steven D'Aprano <steve+comp.lang.python@pearwood.info> writes:

> On Mon, 16 Dec 2013 12:51:21 +1100, Ben Finney wrote:
> ... "type(obj)" versus "obj.__class__"
> That is an excellent question, I only wish I had an excellent answer to 
> give you. Obviously great minds think alike because I was going to ask 
> the same question, prompted by this comment from Nick Coghlan on the 
> python-dev list:
>
> "...type(obj).__name__ (working with the concrete type, ignoring any
> proxying) or obj.__class__.__name__ (which takes proxying into 
> account)..."
>
> So there is a difference between them, but I'm not entirely sure what it 
> is.

I understand the difference: sometimes you work with proxies
(e.g. "weakref" proxies). A proxie should work mostly like the proxied
object - but in rare cases, you want to detect that what you have
is actually a proxie rather than the real object.
You can use "type(obj)" to check the real type ob "obj" (in some
sense, it is more direct - more reliable; giving you the real
type of "obj"). "obj.__class__" on the other hand uses standard
attribute access - and proxying may have customized attribute access
to access the proxied object's attributes rather than its own: then
"obj.__class__" would give you not the type of "obj" (the proxie)
but that of the proxied object.

[toc] | [prev] | [next] | [standalone]


#62159

FromSteven D'Aprano <steve@pearwood.info>
Date2013-12-17 07:42 +0000
Message-ID<52b0006a$0$29973$c3e8da3$5496439d@news.astraweb.com>
In reply to#62158
On Tue, 17 Dec 2013 08:14:09 +0100, dieter wrote:

> Steven D'Aprano <steve+comp.lang.python@pearwood.info> writes:
> 
>> On Mon, 16 Dec 2013 12:51:21 +1100, Ben Finney wrote: ... "type(obj)"
>> versus "obj.__class__" That is an excellent question, I only wish I had
>> an excellent answer to give you. Obviously great minds think alike
>> because I was going to ask the same question, prompted by this comment
>> from Nick Coghlan on the python-dev list:
>>
>> "...type(obj).__name__ (working with the concrete type, ignoring any
>> proxying) or obj.__class__.__name__ (which takes proxying into
>> account)..."
>>
>> So there is a difference between them, but I'm not entirely sure what
>> it is.
> 
> I understand the difference: sometimes you work with proxies (e.g.
> "weakref" proxies). A proxie should work mostly like the proxied object
> - but in rare cases, you want to detect that what you have is actually a
> proxie rather than the real object. You can use "type(obj)" to check the
> real type ob "obj" (in some sense, it is more direct - more reliable;
> giving you the real type of "obj"). "obj.__class__" on the other hand
> uses standard attribute access - and proxying may have customized
> attribute access to access the proxied object's attributes rather than
> its own: then "obj.__class__" would give you not the type of "obj" (the
> proxie) but that of the proxied object.


I think I need to see an actual working demonstration, because as far as 
I can see, type(obj) returns obj.__class__.

I'm not suggesting you are wrong, only that code speaks more loudly than 
words :-)


-- 
Steven

[toc] | [prev] | [next] | [standalone]


#62175 — Re: Type of an object:

FromGregory Ewing <greg.ewing@canterbury.ac.nz>
Date2013-12-17 23:35 +1300
SubjectRe: Type of an object:
Message-ID<bhann0F1rrsU1@mid.individual.net>
In reply to#62159
Steven D'Aprano wrote:
> I think I need to see an actual working demonstration, because as far as 
> I can see, type(obj) returns obj.__class__.

Nope:

 >>> class C(object):
...  def f(self):
...   return "Surprise!"
...  __class__ = property(f)
...
 >>> c = C()
 >>> type(c)
<class '__main__.C'>
 >>> c.__class__
'Surprise!'

It appears that type() bypasses the attribute access
mechanism and goes straight for the actual type.

-- 
Greg

[toc] | [prev] | [next] | [standalone]


#62193 — Re: Type of an object:

FromEthan Furman <ethan@stoneleaf.us>
Date2013-12-17 06:50 -0800
SubjectRe: Type of an object:
Message-ID<mailman.4285.1387291826.18130.python-list@python.org>
In reply to#62175
On 12/17/2013 02:35 AM, Gregory Ewing wrote:
> Steven D'Aprano wrote:
>> I think I need to see an actual working demonstration, because as far as I can see, type(obj) returns obj.__class__.
>
> Nope:
>
>>>> class C(object):
> ...  def f(self):
> ...   return "Surprise!"
> ...  __class__ = property(f)
> ...
>>>> c = C()
>>>> type(c)
> <class '__main__.C'>
>>>> c.__class__
> 'Surprise!'

I believe the proxying info comes into play with __class__.__name__:

--> class Test:
...   pass
...

--> t = Test()

--> type(t)
<class '__main__.Test'>

--> t.__class__
<class '__main__.Test'>

--> t.__class__.__name__
'Test'

--> t.__class__.__name__ = 'some_proxy'

--> t.__class__.__name__
'some_proxy'

--
~Ethan~

[toc] | [prev] | [next] | [standalone]


#62202 — Re: Type of an object:

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-12-17 15:08 +0000
SubjectRe: Type of an object:
Message-ID<52b06902$0$29976$c3e8da3$5496439d@news.astraweb.com>
In reply to#62175
On Tue, 17 Dec 2013 23:35:10 +1300, Gregory Ewing wrote:

> Steven D'Aprano wrote:
>> I think I need to see an actual working demonstration, because as far
>> as I can see, type(obj) returns obj.__class__.
> 
> Nope:
> 
>  >>> class C(object):
> ...  def f(self):
> ...   return "Surprise!"
> ...  __class__ = property(f)
> ...
>  >>> c = C()
>  >>> type(c)
> <class '__main__.C'>
>  >>> c.__class__
> 'Surprise!'


Well, that is a surprise, but I don't think that is intended behaviour. I 
think that's something which only works by accident. The intention is 
that __class__ returns the instance's type, not arbitrary values. If you 
try to set it to a non-class on the instance, it fails:


py> class D(object):
...     pass
...
py> d = D()
py> d.__class__ = property(lambda self: 'Surprise!')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __class__ must be set to a class, not 'property' object


Same when you try to set it on the class object itself:

py> D.__class__ = property(lambda self: 'Surprise!')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __class__ must be set to a class, not 'property' object


So my guess is that the fact that your code works at all is an accident.

I can demonstrate that changing the __class__ of an instance causes 
type(obj) to return a different value, and also to dynamically change the 
behaviour of the object:


py> class Spam(object):
...     def method(self):
...             return "spam"
...
py> class Ham(object):
...     def method(self):
...             return "ham"
...
py> ham = Ham()
py> ham.method()
'ham'
py> ham.__class__ = Spam
py> ham.method()
'spam'
py> type(ham)
<class '__main__.Spam'>


So this is a case where type(obj) returns obj.__class__ rather than it's 
internal type field. That's the point I was trying to make: type(obj) may 
return obj's internal type field, but if you set obj.__class__, type(obj) 
will then return the new class, not the internal one.


-- 
Steven

[toc] | [prev] | [next] | [standalone]


#62243 — Re: Type of an object:

FromGregory Ewing <greg.ewing@canterbury.ac.nz>
Date2013-12-18 11:15 +1300
SubjectRe: Type of an object:
Message-ID<bhc0n9Fan00U1@mid.individual.net>
In reply to#62202
Steven D'Aprano wrote:
> Well, that is a surprise, but I don't think that is intended behaviour. I 
> think that's something which only works by accident. The intention is 
> that __class__ returns the instance's type, not arbitrary values.

Well, a proxy object would obviously return a suitable
class-like object. I was just demonstrating that it's
possible to override what __class__ returns.

I don't think it's an accident, because the weakref
module uses this for its proxy objects.

 >>> import weakref
 >>> class C(object):
...  pass
...
 >>> c = C()
 >>> p = weakref.proxy(c)
 >>> p.__class__
<class '__main__.C'>
 >>> type(p)
<type 'weakproxy'>

> If you 
> try to set it to a non-class on the instance, it fails:

For proxying purposes you don't need to be able to set
it, but I don't see why you couldn't use a property
setter to override that behaviour as well if you really
wanted to.

-- 
Greg

[toc] | [prev] | [next] | [standalone]


#62246 — Re: Type of an object:

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-12-17 23:51 +0000
SubjectRe: Type of an object:
Message-ID<52b0e36b$0$6512$c3e8da3$5496439d@news.astraweb.com>
In reply to#62243
On Wed, 18 Dec 2013 11:15:03 +1300, Gregory Ewing wrote:

> Steven D'Aprano wrote:
>> Well, that is a surprise, but I don't think that is intended behaviour.
>> I think that's something which only works by accident. The intention is
>> that __class__ returns the instance's type, not arbitrary values.
> 
> Well, a proxy object would obviously return a suitable class-like
> object. I was just demonstrating that it's possible to override what
> __class__ returns.

You can certainly do it with a __getattribute__ method:

py> class K(object):
...     def __getattribute__(self, name):
...             if name == '__class__': return 42
...             return super().__getattribute__(name)
...
py> k = K()
py> k.__class__
42

but I think that counts as "shoot yourself in the foot" category.


> I don't think it's an accident, because the weakref module uses this for
> its proxy objects.

Just a minute, we seem to be talking about completely different things 
here. You demonstrated setting __class__ to a non-class object inside a 
class statement, emphasis on the *non-class* part. Here's a simpler 
version showing the same thing:

py> class Q(object):
...     __class__ = 42
...
py> q = Q()
py> q.__class__
42
py> type(q)
<class '__main__.Q'>


Here's an equivalent way:

py> Q = type("Q", (object,), {'__class__': 23})
py> Q().__class__
23

It's the *non-class* part I reckon is an accident, or a bug. Telling me 
that weakproxy sets __class__ to a *class* doesn't argue for or against 
me.

Ignoring virtual __getattribute__ attributes, I cannot see any other way 
to set the __class__ of an object to be a non-class. (I suppose you can 
do anything you like with a metaclass, but that falls firmly into 
"consenting adults" territory.) Unless you "break" access to the 
__class__ descriptor first, as in the Q class above, there doesn't seem 
to be any way to set it to a non-class or an incompatible class. You 
can't set it on the class:

py> class C(object):
...     pass
...
py> C.__class__ = 23
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __class__ must be set to a class, not 'int' object
py> C.__dict__['__class__'] = 23
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment


nor can you set it on the instance:

py> c = C()
py> c.__class__ = 23
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __class__ must be set to a class, not 'int' object


You can set it on the instance dict directly, but it doesn't do you any 
good because the descriptor overrides it:

py> c.__dict__['__class__'] = 23
py> c.__class__
<class '__main__.C'>


Somebody has gone to a *lot* of trouble to ensure that __class__ always 
returns an actual class, and I'm not sure the Q example above is a 
deliberate hole in that.


Your weakref example is not a counter-example:

>  >>> import weakref
>  >>> class C(object):
> ...  pass
> ...
>  >>> c = C()
>  >>> p = weakref.proxy(c)
>  >>> p.__class__
> <class '__main__.C'>
>  >>> type(p)
> <type 'weakproxy'>


since both weakproxy and C are classes. This is a good example though of 
when type(obj) and obj.__class__ can legitimately differ.

This leads to another question: we've now seen two examples where 
(presumably) the internal type field and __class__ differ. In the 
weakproxy case, type(obj) returns the internal type field. In the 
"regular" case, where you set obj.__class__ to a class, type(obj) returns 
the new (external) type. How the hell does it decide which one to return?



-- 
Steven

[toc] | [prev] | [next] | [standalone]


#62251 — Re: Type of an object:

FromEthan Furman <ethan@stoneleaf.us>
Date2013-12-17 17:10 -0800
SubjectRe: Type of an object:
Message-ID<mailman.4316.1387330505.18130.python-list@python.org>
In reply to#62246
On 12/17/2013 03:51 PM, Steven D'Aprano wrote:
>
> This leads to another question: we've now seen two examples where
> (presumably) the internal type field and __class__ differ. In the
> weakproxy case, type(obj) returns the internal type field. In the
> "regular" case, where you set obj.__class__ to a class, type(obj) returns
> the new (external) type. How the hell does it decide which one to return?

However it does it, it does it in C.  ;)

--
~Ethan~

[toc] | [prev] | [next] | [standalone]


#62294 — Re: Type of an object:

FromGregory Ewing <greg.ewing@canterbury.ac.nz>
Date2013-12-19 00:45 +1300
SubjectRe: Type of an object:
Message-ID<bhdg62Fjrb3U1@mid.individual.net>
In reply to#62251
Ethan Furman wrote:
>> This leads to another question: we've now seen two examples where
>> (presumably) the internal type field and __class__ differ. In the
>> weakproxy case, type(obj) returns the internal type field. In the
>> "regular" case, where you set obj.__class__ to a class, type(obj) returns
>> the new (external) type. How the hell does it decide which one to return?

When you set the __class__ of a regular object and it
succeeds, it actually changes the internal type, i.e.
obj->ob_type at the C level.

This is only allowed under certain conditions. Mostly
only for user-defined classes, and only if the layouts
of the old and new instance structs are compatible.

-- 
Greg

[toc] | [prev] | [next] | [standalone]


#62293 — Re: Type of an object:

FromGregory Ewing <greg.ewing@canterbury.ac.nz>
Date2013-12-19 00:39 +1300
SubjectRe: Type of an object:
Message-ID<bhdfrdFjo0gU1@mid.individual.net>
In reply to#62246
Steven D'Aprano wrote:
> It's the *non-class* part I reckon is an accident, or a bug. Telling me 
> that weakproxy sets __class__ to a *class* doesn't argue for or against 
> me.

I wouldn't describe what weakref.proxy is doing as
*setting* __class__ to anything. Rather, it's arranging
things so that when you *get* x.__class__, it returns
something other than type(x). And I expect it's achieving
that effect by exploiting one of the general mechanisms
available for overriding attribute access.

You seem to think it's an "accident" that there is
no protection in place to prevent __class__ access from
being overridden in such a way that it returns something
other than a class.

I suppose you could say that this "works by accident", but
I would say that it simply works. There may not be any
obvious practical *use* for it, but it's not worth
anyone's time to go out of their way to prevent it.

-- 
Greg

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web