I haven't even finished reading this yet. This is good stuff! --- Kevin Jacobs <jacobs@penguin.theopalgroup.com> wrote:
Hello all,
I've been meta-reflecting a lot lately: reflecting on reflection.
My recent post on __slots__ not being picklable (and the resounding lack of response to it) inspired me to try my hand at channeling Guido and reverse- engineer some of the design decisions that went into the new-style class system. Unfortunately, the more I dug into the code, the more philosophical my questions became. So, I've written up some questions that help lay bare some of basic design questions that I've been asking myself and that you should be aware of.
While there are several subtle issues I could raise, I do want some feedback on some simple and fundamental ones first. Please don't disqualify yourself from commenting because you haven't read the code or used the new features yet. I've written my examples assuming only a basic and cursor understanding of the new Python 2.2 features.
[In this discussion I am only going to talk about native Python classes, not C-extension or native Python types (e.g., ints, lists, tuples, strings, cStringIO, etc.)]
1) Should class instances explicitly/directly know all of their attributes?
Before Python 2.2, all object instances contained a __dict__ attribute that mapped attribute names to their values. This made pickling and some other reflection tasks fairly easy.
e.g.:
class Foo: def __init__(self): self.a = 1 self.b = 2
class Bar(Foo): def __init__(self): Foo.__init__(self) self.c = 3
bar = Bar() print bar.__dict__ > {'a': 1, 'c': 3, 'b': 2}
I am aware that there are situations where this simple case does not hold (e.g., when implementing __setattr__ or __getattr__), but let's ignore those for now. Rather, I will concentrate on how this classical Python idiom interacts with the new slots mechanism. Here is the above example using slots:
e.g.:
class Foo(object): __slots__ = ['a','b'] def __init__(self): self.a = 1 self.b = 2
class Bar(Foo): __slots__ = ['c'] def __init__(self): Foo.__init__(self) self.c = 3
bar = Bar() print bar.__dict__ > AttributeError: 'Bar' object has no attribute '__dict__'
We can see that the class instance 'bar' has no __dict__ attribute. This is because the slots mechanism allocates space for attribute storage directly inside the object, and thus does not use (or need) a per-object instance dictionary to store attributes. Of course, it is possible to request that a per-instance dictionary by inheriting from a new-style class that does not list any slots. e.g. continuing from above:
class Baz(Bar): def __init__(self): Bar.__init__(self) self.d = 4 self.e = 5
baz = Baz() print baz.__dict__ > {'e': 5, 'd': 4}
We have now created a class that has __dict__, but it only contains the attributes not stored in slots! So, should class instances explicitly know their attributes? Or more precisely, should class instances always have a __dict__ attribute that contains their attributes? Don't worry, this does not mean that we cannot also have slots, though it does have some other implications. Keep reading...
2) Should attribute access follow the same resolution order rules as methods?
class Foo(object): __slots__ = ['a'] self.a def __init__(self): self.a = 1
class Bar(Foo): __slots__ = ('a',) def __init__(self): Foo.__init__(self) self.a = 2
bar = Bar() print bar.a > 2 print super(Bar,bar).a # this doesn't actually work > 2 or 1?
Don't worry -- this isn't a proposal and no, this doesn't actually work. However, the current implementation only narrowly escapes this trap:
print bar.__class__.a.__get__(bar) > 2 print bar.__class__.__base__.a.__get__(bar) > AttributeError: a
Ok, let me explain what just happened. Slots are implemented via the new descriptor interface. In short, descriptor objects are properties and support __get__ and __set__ methods. The slot descriptors are told the offset within an object instance the PyObject* lives and proxy operations for them. So getting and setting slots involves:
# print bar.a a_descr = bar.__class__.a print a_descr.__set__(bar)
# bar.a = 1 a_descr = bar.__class__.a a_descr.__set__(bar, 1)
So, above we get an attribute error when trying to access the 'a' slot from Bar since it was never initialized. However, with a little ugliness you can do the following:
# Get the descriptors for Foo.a and Bar.a a_foo_descr = bar.__class__.__base__.a a_bar_descr = bar.__class__.a a_foo_descr.__set__(bar,1) a_bar_descr.__set__(bar,2)
print bar.a > 2 print a_foo_descr.__get__(bar) > 1 print a_bar_descr.__get__(bar) > 2
In other words, the namespace for slots is not really flat, although there is no simple way to access these hidden attributes === message truncated ===
__________________________________________________ Do You Yahoo!? Yahoo! Sports - Coverage of the 2002 Olympic Games http://sports.yahoo.com