views:

281

answers:

3

In python, it is illegal to create new attribute for an object instance like this

>>> a = object()
>>> a.hhh = 1

throws

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute 'hhh'

However, for a function object, it is OK.

>>> def f():
...   return 1
...
>>> f.hhh = 1

What is the rationale behind this difference?

+11  A: 

The reason function objects support arbitrary attributes is that, before we added that feature, several frameworks (e.g. parser generator ones) were abusing function docstrings (and other attribute of function objects) to stash away per-function information that was crucial to them -- the need for such association of arbitrary named attributes to function objects being proven by example, supporting them directly in the language rather than punting and letting (e.g.) docstrings be abused, was pretty obvious.

To support arbitrary instance attributes a type must supply every one of its instances with a __dict__ -- that's no big deal for functions (which are never tiny objects anyway), but it might well be for other objects intended to be tiny. By making the object type as light as we could, and also supplying __slots__ to allow avoiding per-instance __dict__ in subtypes of object, we supported small, specialized "value" types to the best of our ability.

Alex Martelli
Thanks Alex, for this inside view of *object*. I hope the gain in space was worth the effort. I still find it counter-intuitive that (a) the base class of the object system does not support one of the basic features of OO - instance attributes; and (b) this feature all of a sudden springs into existence with any non-sensical derived class *class Foo(object): pass*, which I find myself using more than often just to get instance attributes.
ThomasH
@Thomas, are you equally surprised that Object in Java "does not support one of the basic features of OO" in exactly the same way? Its direct instances don't have instance attributes either. C# is the same, I believe. Since (and this is REALLY a basic feature of OO's: Liskov's Principle!) no subclass can take away something that's in the superclass, to satisfy your intuition would actually cost infinite memory: a dict should then have a __dict___... and THAT dict should of course have its OWN __dict__... and it can NEVER, NEVER stop. Your intuition's misleading you *A LOT*!-)
Alex Martelli
@Alex Ok, I hear you. But maybe this would all be possible if the \_\_dict\_\_'s of lean objects were lazy?! I actually could envision myself wanting to annotate the \_\_dict\_\_ of a \_\_dict\_\_ of an *object* instance with a little attribute, and it would be perfectly enough for me if this \_\_dict\_\_ would only spring into existence when I first assign to it. How is that?
ThomasH
@ThomasH: it's possible (at the cost of about 4 bytes/object as every object will need space for a dict pointer, NULL if it doesn't yet have a dict; not cheap, bloating Python's memory footprint very measurably, but better than the 64 bytes an empty dict costs) though quite tricky. I suggest experimenting with a specialized metaclass before drafting a PEP and patch for the C runtime, the metaclass will be much simpler and still reveal many of the difficulties (previously immutable objects now can change; what cascading effects will that have? etc). Good luck (you'll need some!-).
Alex Martelli
Ha, Alex, I don't think I'll be so bold as to file a PEP (I've seen some of them going down), but thanks for the insights anyway, appreciate that. Maybe I finally start reading Kickzales' et al. Art of the MetaObject Protocol...
ThomasH
@Thomas, it's an interesting book if you can grab ahold of a copy (it was a mess a few years ago when I got mine, though I did finally make it;-), but not that directly instructive wrt Python metaclasses even though they were originally inspired by it -- Simionato's essays, etc, may actually prove more useful and are easier to grab (for free, too;-) on the net;-).
Alex Martelli
@Alex Thanks for the pointer, I'll have a look. As for AMOP, I just got my copy shipped today. (Seems like MIT Press is putting some of their old-time favourits into print once in a while, I just received another title the other day which had been out of print for years).
ThomasH
+4  A: 

Alex Martelli posted an awesome answer to your question. For anyone who is looking for a good way to accomplish arbitrary attributes on an empty object, do this:

class myobject(object):
    pass

o = myobject()
o.anything = 123

Or more efficient (and better documented) if you know the attributes:

class myobject(object):
    __slots__ = ('anything', 'anythingelse')

o = myobject()
o.anything = 123
o.anythingelse = 456
gahooa
+1  A: 

The rationale is that an instance of object() is a degenerate special case. It "is" an object but it isn't designed to be useful by itself.

Think of object as a temporary hack, bridging old-style types and classes. In Python 3.0 it will fade into obscurity because it will no longer be used as part of

class Foo( object ):
    pass

f = Foo()
f.randomAttribute = 3.1415926
S.Lott
Main real use of object is in the idiom `sentinel=object()`. It WOULD be modestly handy to have an "objectwithadict" builtin at hand rather than having to make new classes for the purpose, soemetimes;-).
Alex Martelli
objectwithadict -- essentially `class ObjectWithADict(object): pass`?
S.Lott