Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.python > #61979 > unrolled thread
| Started by | Ben Finney <ben+python@benfinney.id.au> |
|---|---|
| First post | 2013-12-16 12:51 +1100 |
| Last post | 2013-12-19 00:39 +1300 |
| Articles | 12 — 6 participants |
Back to article view | Back to comp.lang.python
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
| From | Ben Finney <ben+python@benfinney.id.au> |
|---|---|
| Date | 2013-12-16 12:51 +1100 |
| Subject | Type 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]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2013-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]
| From | dieter <dieter@handshake.de> |
|---|---|
| Date | 2013-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]
| From | Steven D'Aprano <steve@pearwood.info> |
|---|---|
| Date | 2013-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]
| From | Gregory Ewing <greg.ewing@canterbury.ac.nz> |
|---|---|
| Date | 2013-12-17 23:35 +1300 |
| Subject | Re: 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]
| From | Ethan Furman <ethan@stoneleaf.us> |
|---|---|
| Date | 2013-12-17 06:50 -0800 |
| Subject | Re: 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]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2013-12-17 15:08 +0000 |
| Subject | Re: 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]
| From | Gregory Ewing <greg.ewing@canterbury.ac.nz> |
|---|---|
| Date | 2013-12-18 11:15 +1300 |
| Subject | Re: 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]
| From | Steven D'Aprano <steve+comp.lang.python@pearwood.info> |
|---|---|
| Date | 2013-12-17 23:51 +0000 |
| Subject | Re: 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]
| From | Ethan Furman <ethan@stoneleaf.us> |
|---|---|
| Date | 2013-12-17 17:10 -0800 |
| Subject | Re: 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]
| From | Gregory Ewing <greg.ewing@canterbury.ac.nz> |
|---|---|
| Date | 2013-12-19 00:45 +1300 |
| Subject | Re: 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]
| From | Gregory Ewing <greg.ewing@canterbury.ac.nz> |
|---|---|
| Date | 2013-12-19 00:39 +1300 |
| Subject | Re: 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