== Recording attribute access and method calls Here's a little bit of magic, demonstrated in the Python interpreter: >>> from record import Record >>> r = Record() >>> r2 = r.a.b('abc').c >>> print r2 a.b('abc').c >>> print r.d.e(r2, t=20).f() d.e(Record().a.b('abc').c, t = 20).f() Rather than making function calls or giving you real attributes, the _Record_ class just records what you did (and in this case, lets it be dumped as a string). It's smart enough to know when some of the function arguments are also recorded bits and treat them specially. (Here it just sticks 'Record().' in front of them, since this is an example.) A version of this cropped up in PingingWeblogsInPython, where it's the underlying mechanism the xmlrpclib module uses to let people invoke XML/RPC procedures as if they were writing plain Python calls. Python allows objects to customize access to themselves (covered [[here|http://docs.python.org/ref/attribute-access.html]]), so the implementation is pretty simple: * every _Record_ object remembers the path to itself. * ((__getattr__)) returns a kid _Record_ object with the attribute name appended to the path. * ((__call__)) returns a new _Record_ object with the function call arguments added to the path. * ((__str__)) just returns the current path. This creates a new object for each step of the path, but because _Record_ objects never change their own path they can be safely saved and reused by outside code (eg, in the example we reuse _r_ as the starting point for two different paths; if we returned the same object all the time and just mutated its current path, this would explode spectacularly). Because Python doesn't distinguish between 'call attribute' and 'access attribute', all of the attributes you get back are potentially callable (eg, _r2_ could be called later). === Sidebar: the actual code Here's a simple implementation of the _Record_ class. > class Record(object): > def __init__(self, name=''): > self._n = name > def __str__(self): > return self._n > > def __getattr__(self, attr): > nn = attr > if self._n: > nn = '%s.%s' % (self._n, attr) > return self.__class__(nn) > > def _gr(self, x): > if isinstance(x, Record): > return "Record().%s" % str(x) > else: > return repr(x) > > def __call__(self, *a, **kw): > al = [self._gr(x) for x in a] > k = kw.keys(); k.sort() > al.extend(["%s = %s" % (x, self._gr(kw[x])) > for x in k]) > nn = "%s(%s)" % (self._n, ", ".join(al)) > return self.__class__(nn) (As usual, some names have been shortened to make the code not take up too much space.) In a real version, ((__getattr__)) probably should have a cache, so that getting '_.foo_' twice gives you the same object back.