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