Re: python object model

Mark Lutz (mlutz@KaPRE.COM)
Mon, 29 Aug 1994 12:36:11 +0700

Thanks for the interesting post. Looks like a very Good Thing.

One question, just to clarify: in your model, if some method or data-member
'X' exists at the instance (or some lower subclass), then the __getattr__()
method will never get called for access to 'X', because AttributeError is
not raised; or have I misunderstood?

It seems like catching access to all class members might be potentially useful,
especially for things like access restrictions, and tracing. For example:

class Super:
private = ['x', 'y', 'z'] # static class data
external = ['l', 'm', 'n']

def __getattr__(self, name):
if self.__hasslot__('traceme'): # debugging stuff
print 'access to' name

if name in Super.private: # access restricions
raise AttributeError

if name in Super.external: # wrapper classes
return get_a_remote_thing(name)

return self.__getslot__(name) # find member; see below

def public(self,...):
Super.x(self, ...) # use private member here

class Sub(Super):
def method(self..): ...

x = Sub()
x.x() # nope!
x.public() # works

If Guido does optimize access to these special methods as he mentioned, then
it wouldn't cost much to check for a __getattr__ on every member access (1
null pointer test). But I've never used this sort of thing; anyone else
think this would be useful? Of course, you could wrap a protected class in
an empty dummy class, and route approved accesses to the associated real
class, but it's inconvenient.

The __getslot__ method (and its relatives) seem to need further definition.
We need to be able to get a member without cycling back to __getattr__, but
it's not clear what the semantics should be. Guido writes about the
distinction between members at an instance and a class. We can probably
have it both ways:

# assume members are at the class:

class Super:
def __getattr__(self, name):
if ok-to-access:
use Super.__dict__[name] # class dictionary
Super.__dict__[name] = value

# assume members are in the instance:

class Super:
def __getattr__(self, name):
if ok-to-access:
use self.__dict__[name] # instance dictionary
self.__dict__[name] = value

# look for members anywhere, at instance and above:

class Super:
def __getattr__(self, name):
if ok-to-access:
use getattr(self, name) # make inheritance find it
setattr(self, name, value) # becomes an instance var

This last method, using normal inheritance search, is probably the
most useful, but getattr() must not cycle. Of course, if we restrict
__getattr__ to AttributeError events as you suggest, this isn't an issue:
we only get to __getattr__ if the member does not exist below, so there's
no reason to use __getslot__ to find it in the instance (or the class..)
anyhow; under this model, __getslot__ seems only useful for cases where
a member access is mapped to a completely different member (access to 'X'
returns member 'Y', as in your 'TraceWrapper').

Again, forgive me if I missed something in your examples; but it seems like
limiting member access catching to non-existant members, misses some possibly
useful functionality (?)

One final point: it's not completely clear (to me) that we're buying alot
here, _except_ in cases where the set of members of a class are not known
statically (and even then, we can use get()/set() accessors manually). In
the FAQ, for example, Guido describes how Python dictionaries can be wrapped
in classes, to open them up to OOP. Client/server programming can be
simlarly wrapped, using existing facilities, to make external objects
look like Python classes; to take a (grossly over-simplified) example:

Server.py:
import ServerInterface # a C extension module, with api wrappers

class BasicServer:
def __init__(self, info..):
self.hook = api_connect(info..) # attach to real server
def op1(self):
api_op1(self.hook) # call C server interface
def op2(self):
api_op2(self.hook) # call C server interface
def op3(self):
api_op3(self.hook, self.virt()) # add a virtual call back

Client.py:
from Server import BasicServer

t = BasicServer(info...)
t.op1()
t.op2()

class SpecializedServer(BasicServer):
def __init__(self, info..):
BasicServer.__init__(info..) # connect external service
def op1(self):
# over-ride # change api call
def op4(self):
# extend # add another operation
def virt(self):
# customize

t = SpecializedServer(info..)
t.op1(); t.op2(); t.op3(); t.op4()

You can do the same for Python lists, tuples, strings, etc: just define
a class with methods that wrap already existing functionality, and an
initializer that creates and stores an instance of the built-in data type.
Guido's dictionary wrapper shows how.

Of course, you clearly can't write such class wrappers when the api is
not known; but does __getattr__ make things much easier in this case?
Not to criticize your approach at all; it just seemed to me that the only
_additional_ functionality this stuff provided, was to allow accesses
to existing members to be trapped, and the model (as I understand it..)
does not, for member fetch's (but it does for set's) (again, ?)

Mark Lutz