mjc4y@brunelleschi.cs.virginia.edu: python object model

Guido.van.Rossum@cwi.nl
Wed, 24 Aug 1994 15:16:28 +0200

This could be the start of a very interesting discussion on the future
of objects in Python. I am forwarding it because the tail of the
message says I can decide if I want to... Later I'll comment on it
myself.

--Guido van Rossum, CWI, Amsterdam <Guido.van.Rossum@cwi.nl>
<URL:http://www.cwi.nl/cwi/people/Guido.van.Rossum.html>

------- Forwarded Message

Date: Tue, 23 Aug 1994 14:34:18 -0400
From: mjc4y@brunelleschi.cs.virginia.edu
To: deline@src.dec.com
cc: tnb2d@server.cs.Virginia.EDU, guido@cwi.nl
Subject: python object model

Guido,
Introduction
--------------------------------------------
this mail starts with an excerpt from a discussion that Tommy
Burnette, Rob DeLine and I have been having on the subject of
the extensibility of the Python object system. Tommy tells me
you (eventually) intend to lift the restrictions of the
builtin objects to allow subclassing. I think this discussion
has direct bearing on such an effort, and points to a
mechanism that might make Python *much* more powerful and
flexible without sacrificing the farm.

This discussion isn't a plea for more features, but more of a
"what if" between Python admirers. I thought it a sufficiently
interesting line of speculation that it was worth letting you
in on it. I'd be delighted to hear any thought or reactions
you have, but I understand you're a busy guy. :)

We start with a discussion of a feature I hacked into the
python interpreter....I write to Rob:

| conway@virginia.edu writes:
|
| : [[ Interesting footnote: I've successfully hacked Python's dictionary
| : slot access to allow the user to specify a SUIT-interest-like callba
ck
| : whenever a dictionary element is changed. Using this on any class
| : instance's self.__dict__, you can trap assignments like fred.x = 10
| : to call a function of your choosing. This function will be given
| : fred.__dict__ (the dict being changed), "x" (the key being
| : changed), and fred.x oldval and new val. Works great....
|
deline@src.dec.com writes:
|
| Hmmmmm....
|
| Isn't it the case that if a dictionary were implemented as a class
| with setting and getting as methods, then you could get the behavior
| you want just by subclassing?
| class Dictionary:
| def __getitem__(self, key):
| # some implementation
| def __setitem__(self, key, value):
| # some implementation
|
| class DictionaryWithNotification(Dictionary):
| def register(self, func):
| self.func = func
| def __setitem__(self, key, value):
| oldValue = self.__getitem__(self, key)
| self.func(self, key, oldValue, value)
| Dictionary.__setitem__(self, key, value)

My reply:

Objects in Python
- ----------------------------

Yes and No. Unfortuately, in Python, there are objects, and then
there are Objects. :) As you know, lists, tuples, dictionaries,
strings, integers, floats, etc. are all "objects" in the Python model,
and objects like lists have "methods" associated with them like "sort"
and "append". None of these "builtin" object types support subclassing
or subtyping in any way. There is a fair bit of machinery in Python to
support two builtin object types: classobjects and instanceobjects
(ok, and methodobjects). These are the only objects that
participate in the subclassing mechanism.

You can create a class Dictionary like you have outlined here, but
that doesn't help you for implementing the "notification" facility on
instance object slot access. Classes and instances store their slot
values in *regular* dictionaries, a decision that's been hardcoded in
C. From here, it's not clear how to proceed. There are any
number of options (hacking the original source, adding a new classtype
and kluging out a way to instantiate them...), but all have some
serious drawbacks. The rest of this mail hopes to outline a possible
solution, taken from languages like CLOS.

The problem here is that there are no "hooks" into the implementation
from Python. Perhaps this is intentional on Guido's part, so that he
can stay free to reimplement slot access and assignment in any way,
perhaps without using dicts at all at some future time. Whatever the
reason, it seems we have a clear case where "the implementation shows
through" in bad ways, even though it's supposed to be helping us with
the power of abstraction. [see Kiczales and his metaobject protocol
stuff]

Some days I wish computers weren't trying to be so damn helpful. :)

What we want is to be able to override the *definition of a
classobject* and instanceobject itself, and to do so in such a way
that you can use the default behavior of classes when you want
to. (Rob: CLOS folks will recognize this idea as MOP, yes?). I'm going to
think out loud here, dreaming up what a mechanism might
look like, and imagining what it might take to actually implement
it. If it's not too hard, maybe Guido can be convinced to extend the
language.

Classobjects Reconsidered
- -------------------------

So we need to redefine what it means to be a classobject. Whether the
implementation of this new class object happens in C or in Python is
really no big deal to me. Clearly having it in Python is better for
exploring new ideas and doesn't require a recompile of the
interpreter, but I understand the motivation for putting it in C for
speed reasons. Let's assume that it happens in C for now.

[[ side note: my terminology might be wrong here, but we're talking
about metaclasses here, yes? a metaclassobject has things like
getitem and setitem methods, and when instantiated, a metaclass
creates classes, not instances. It serves as a container for the
"protocol" to which all "instances" of a 'real' class must
adhere. Python currently supports a single instance of the ethereal
metaclassobject; it's called a classobject. Cutting to the chase,
what I'm about to propose is that Python allow *many* instances of
the metaclass object -- many different kinds of classes and
therefore many different kinds of instances. ]]

A proposed Syntax for metaclasses
- ----------------------------------------

Let's invent some syntax and introduce a new keyword:

metaclass

Here's an example of what it might look like in python:

metaclass SmartClass:
def __init__(self):
# assume that the attributes of an object are held in a
# field called __attrs__
self.__attrs__ = SmartDict()

def __getslot__(self, key):
# do something

def __setslot__(self, key, value):
# do something

def __callmethod__(self, meth, *args):
print "calling method", meth, "with", args
# really call the method
apply(meth, self + args)

etc...fleshing out this list will
be an interesting exercise. Does the init
take args, for example? :)

This defines all the operations on a class instance. Now to create a
class of this kind we do this:

class SmartRect is SmartClass:
# SmartClass.__init__(self) gets called here.

def __init__(self, color):
self.color = color

def getcolor(self):
retrun self.color

Now when the SmartRect is instantiated,

myrect = SmartRect("red")

you get a "smartinstance" called myrect. Instead of a dict in its
belly, myrect has a SmartDict (see the SmartClass spec). when you call
getcolor

print "color is ----->", myrect.getcolor()

you get

calling method <method myrect.getcolor of myrect instance at 11b558> wi
th args None
color is -----> red

where the first line comes from __callmethod__ and the second line
comes directly from the getcolor method of SmartRect.

The C implementation of this seems to be pretty
straightforward. Metaclasses have well-defined methods (getslot,
setslot, callmethod, etc) and Class objects get pointers to these
methods when the class is created. On each of the relevant
opertations, these methods are called through those pointers (just
another table like exists for typeobjects).

With this, we have all the power we need for modifying the semantics
of class instances. What's nice here is that the mechanism is
completely general and yet has fine granularity: one class can
implement method call one way and another class can do it another way,
all within the same program.

Back to the Notification Class
- ----------------------------------
Now, implementing the mechanism we talked about at the beginning is
easy. To implement a class that calls a user callback whenever
instance data is accessed, you :
- make the class's metaclass define a method that sets up the
callback
- override the __setslot__ function to perform the assignment
and to then call the callback.

That's it. In case you think that all this trouble is the facilitate
this one favorite feature of mine, rest assured. This metaclass
facility is something that could be used to implement the long awaited
access objects, read only classes, for restricting the attrs that can
be attached to an instance, before and after methods, and other
wonderful things that we can't even think of yet.

Conclusion
- ---------------------
Whew.

Guido,
Sorry for being so long winded. I hope this discussion sparks some
interest and some discussion. Does this look like something you could
build into the language, given your (rumored) desire to fold the
builtin object types into a more general object system?

I have no idea if this stuff would be of general use to the
newgroup. I don't want to spark a "feature war" discussion, so I'll
leave it to you, Guido, if you want to forward this to the mailing
list.

Cheers,
matt

PS for Rob DeLine:
1. You can forward ths off to Gregor Kiczales if you think he'd be
interested.
2. What else is the metaclass mechanism often used for? Is there a
"standard list of cool things" that metaclasses are good for?

------- End of Forwarded Message