On 5 Mar, 14:38, "Arnaud Delobelle" <[EMAIL PROTECTED]> wrote: > On 5 Mar, 07:31, "Raymond Hettinger" <[EMAIL PROTECTED]> wrote: > > > I had an idea but no time to think it through. > > Perhaps the under-under name mangling trick > > can be replaced (in Py3.0) with a suitably designed decorator. > > Your challenge is to write the decorator. > > Any trick in the book (metaclasses, descriptors, etc) is fair game. > > I had some time this lunchtimes and I needed to calm my nerves so I > took up your challenge :) > Here is my poor effort. I'm sure lots of things are wrong with it but > I'm not sure I'll look at it again.
Well in fact I couldn't help but try to improve it a bit. Objects now don't need a callerclass attribute, instead all necessary info is stored in a global __callerclass__. Bits that didn't work now do. So here is my final attempt, promised. The awkward bits are: * how to find out where a method is called from * how to resume method resolution once it has been established a local method has to be bypassed, as I don't know how to interfere directly with mro. Feedback of any form is welcome (though I prefer when it's polite :) -------------------- from types import MethodType, FunctionType class IdDict(object): def __init__(self): self.objects = {} def __getitem__(self, obj): return self.objects.get(id(obj), None) def __setitem__(self, obj, callerclass): self.objects[id(obj)] = callerclass def __delitem__(self, obj): del self.objects[id(obj)] # This stores the information about from what class an object is calling a method # It is decoupled from the object, better than previous version # Maybe that makes it easier to use with threads? __callerclass__ = IdDict() # The purpose of this class is to update __callerclass__ just before and after a method is called class BoundMethod(object): def __init__(self, meth, callobj, callerclass): self.values = meth, callobj, callerclass def __call__(self, *args, **kwargs): meth, callobj, callerclass = self.values if callobj is None and args: callobj = args[0] try: __callerclass__[callobj] = callerclass return meth(*args, **kwargs) finally: del __callerclass__[callobj] # A 'normal' method decorator is needed as well class method(object): def __init__(self, f): self.f = f def __get__(self, obj, objtype=None): return BoundMethod(MethodType(self.f, obj, objtype), obj, self.defclass) class LocalMethodError(AttributeError): pass # The suggested localmethod decorator class localmethod(method): def __get__(self, obj, objtype=None): callobj = obj or objtype defclass = self.defclass if __callerclass__[callobj] is defclass: return MethodType(self.f, obj, objtype) else: # The caller method is from a different class, so look for the next candidate. mro = iter((obj and type(obj) or objtype).mro()) for c in mro: # Skip all classes up to the localmethod's class if c == defclass: break name = self.name for base in mro: if name in base.__dict__: try: return base.__dict__[name].__get__(obj, objtype) except LocalMethodError: continue raise LocalMethodError, "localmethod '%s' is not accessible outside object '%s'" % (self.name, self.defclass.__name__) class Type(type): def __new__(self, name, bases, attrs): # decorate all function attributes with 'method' for attr, val in attrs.items(): if type(val) == FunctionType: attrs[attr] = method(val) return type.__new__(self, name, bases, attrs) def __init__(self, name, bases, attrs): for attr, val in attrs.iteritems(): # Inform methods of what class they are created in if isinstance(val, method): val.defclass = self # Inform localmethod of their name (in case they have to be bypassed) if isinstance(val, localmethod): val.name = attr class Object(object): __metaclass__ = Type # Here is your example code class A(Object): @localmethod def m(self): print 'A.m' def am(self): self.m() class B(A): @localmethod def m(self): print 'B.m' def bm(self): self.m() m = B() m.am() # prints 'A.m' m.bm() # prints 'B.m' # Added: B.am(m) # prints 'A.m' B.bm(m) # prints 'B.m' m.m() # LocalMethodError (which descends from AttributeError) # Untested beyond this particular example! -------------------- -- Arnaud -- http://mail.python.org/mailman/listinfo/python-list