Re: python object model

scharf@EMBL-Heidelberg.DE
Tue, 30 Aug 1994 19:26:43 +0100

> 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?

Yes, this is true. Originally I made is such, that __getattr__ was called each
time an attribute was accessed. It *is* a performance problem: even if finding
the __getattr__ method costs no time, the call of __getattr__() is expensive.
If a method is called, python does first a __gettattr__ to get the methodobject
and then executes it. So, for each methodcall you will have two calls: the
__gettattr__ and then the method itself. That was the reason why I decided to
make it as it is now - call __gettattr__ only if the object doesn't have the
attribute.

Note, that in my model, __setattr__ is always called (if the class defines the
method).

>
> 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').

You are right! __getslot__ isn't necessary but I included it for symmetry
reasons - it wasn't in my implementation till I documented - I thought why is
there a __{set,has,del}slot__ but no __getslot__, so I included it. Anyway,
Guido proposed to manipulate self.__dict__ instead of these methods...

> 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 (?)

Yes, you might be right... but I don't miss this 'possibly usful 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).

Shure yoy can do it manually, but you cannot write a general Client/Server.
With my Client/Server framework, you can put any object in the server and a
client using a Proxy it will look the same (for normal calls) as if it is
local. You don't have to handwrite the calls to the server - it is done by the
framework! My sample implementation does not cover the case where the client
gives a mutable object to the server. In this case the server should get a
Proxy which calls the methods at the client side... That would be just like
distributed objects under NextStep.

> 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.

The disadvantage is, that you have to know all the methods - and then handcode
each method. That's a lot of work! You could automate it that way:

def CreateWrapper(methlist,className):
methods=""
for m in methlist:
methods=methods+ "\tdef %s(self,*args):\n" \
"\t\treturn apply(self._obj.%s,args)\n" %(m,m)
cldef="global %s\n" \
"class %s:\n" \
"\tdef __init__(self,obj):\n"\
"\t\tself._obj=obj\n" % (className,className)
exec(cldef+methods)

file=open('test','r')
CreateWrapper(file.__methods__,"File") # disatvantage: you need an instance
# to generate the wrapper
f=File(f)
print f.readline()
f.close()
print f.closed ## doesn't work, because we cannot warp attributes!!!

The problem is, that you cannot make it 100% compatilbe to the real thing,
because you have no handle on attributes (f.closed).

> 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?

Yes, with my proposal it is really easy to write a wrapper where even
attributes work as expected! In addition you don't have to know all the methods
and attributes of the wrapped object!

class File:
def __init__(self,name,acc='r'):
self.file=open(name,acc)
def __getattr__(self,name):
return getattr(self.file,name)
#
# let's add a simple method to read n lines
#
def readn(self,n):
result=[]
for i in range(n):
result.append(self.readline())
return result
file=File('test')
list=f.readn(10)
print f.closed
f.close()
print f.closed

Michael