Bind descriptor to instance method attribute?

16 views
Skip to first unread message

B Maqueira

unread,
May 15, 2012, 5:59:07 AM5/15/12
to cam...@googlegroups.com

Dear all,

I have been stuck for about 2 days trying to implement some black magic in python and I don't seem to be able to get it going. So I was wondering if any of you may have done something similar in the past. This is what I am trying to do:

a) I have a class with methods which have an attribute .help:

class Test(object):
    ''' yay '''
    def __init__(self, name='world'):
         self.name = name

    def one(self):
         ''' method one '''
         return 1
    one.help = 'hello'

b) I need to be able to generate the "help" content dynamically by instance, so if I have
t1 = Test('Peter')
t2 = Test('Eva')

t1.one.help returns 'hello Peter'
t2.one.help return 'hello Eva'

If I try to do this by going into self.one.__func__ to change the .help content the changes happen at class level so they are not instance specific. Therefore I am trying to bind a descriptor on that .help so I can compute the instance.one.help on a per instance basis:

class InstanceMethodHelp(object):
   ''' A descriptor to provide 'per instance' help attributes '''
   def __get__(self, instance, owner):
       return instance.__doc__ + instance.name

I can easily do this on the class level, but how do I do this to the instance methods? I think it probably needs a couple of metaclasses

class HelpedMethod(types.MethodType):
    ''' a metaclass for instancemethods that have a custom descriptor on the help property '''
   help = InstanceMethodHelp()

But I am not allowed to subclass function, types.MethodType or types.FunctionType here...

Any hints are greatly appreciated!!!!

Braudel

---------------------------
Dr B Maqueira
b...@ferrarihaines.com
http://codelab.ferrarihaines.com

Ib Lundgren

unread,
May 15, 2012, 6:22:15 AM5/15/12
to cam...@googlegroups.com
Hey

I have not done anything similar and am a complete noob when it comes to meta classes. However there is a more simple approach that might work for you.

If rather than trying to fiddle with the method you use an object which implements __call__

class Test(object):

    class One(object):
         def __init__(self, name):
             self.help = 'hello ' + name

         def __call__(self):
             return 1


    def __init__(self, name='world'):
         self.one = self.One(name)

this should result in 

>>> t1 = Test('Peter') 
>>> t2 = Test('Eva')
>>> t1.one.help
'hello Peter'
>>> t1.one()
1

-- Ib



--
You received this message because you are subscribed to the Google Groups "Cambridge and East Anglian Python Users Group" group.
To post to this group, send email to cam...@googlegroups.com.
To unsubscribe from this group, send email to campug+un...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/campug?hl=en.

Stephen Paulger

unread,
May 15, 2012, 6:55:37 AM5/15/12
to cam...@googlegroups.com
Braudel,

If you run id() on the same method of two instances you'll see the same result, which shows that the methods are the same object, that is why assigning attributes affects all instances.

I would probably extend Ib's solution using properties.

class Test(object):

    class One(object):
        def __init__(self, name):
            self.name = name

        def __call__(self):
            return 1

        @property
        def help(self):
            return "Hello %s" % self.name

    def __init__(self, name='world'):
         self.one = self.One(name)

    @property
    def name(self):
        return self.one.name

    @name.setter
    def name(self, name):
        self.one.name = name


a = Test("Andrew")
print a.one()
print a.one.help
print a.name
a.name = "Arthur"
print a.one.help

Produces

1
Hello Andrew
Andrew
Hello Arthur


Steve

Tom Lynn

unread,
May 18, 2012, 6:18:56 PM5/18/12
to Cambridge and East Anglian Python Users Group
Sorry if it's too late to be useful, but here's another stab at this.


class ClassProperty(object):
'''A descriptor for the named classmethod.'''
def __init__(self, name):
self.name = name

def __get__(self, cls, owner):
return getattr(cls, self.name)()


class DynamicPropertiesMetaclass(type):
help = ClassProperty('_help')


def with_help(method, _cache={}):
'''Returns a proxy for method which has a dynamic "help"
property.'''
@property
def proxy_for_method(self):
try:
return _cache[(method, self)]
except KeyError:
class Proxy(object):
__metaclass__ = DynamicPropertiesMetaclass
__name__ = method.__name__
__doc__ = method.__doc__
__class__ = method.__class__
def __new__(cls, *args, **kwargs):
return method(self, *args, **kwargs)
@classmethod
def _help(cls, *args):
return '%s %s (%s)' % (method.help, self.name,
method.__doc__)
_cache[(method, self)] = Proxy
return Proxy
return proxy_for_method


# ----

class Test(object):
''' yay '''
def __init__(self, name='world'):
self.name = name

def one(self):
''' method one '''
return (1, self, self.name) # changed to test "self"
one.help = 'hello'


Test.one = with_help(Test.one) # or "@with_help" at method
declaration


t1 = Test('Peter')
t2 = Test('Eva')

print repr(t1.one.help)
print repr(t2.one.help)
print t1.one()
print t2.one()


Produces:

'hello Peter ( method one )'
'hello Eva ( method one )'
(1, <__main__.Test object at 0x7fd318fa6d10>, 'Peter')
(1, <__main__.Test object at 0x7fd318fa6d50>, 'Eva')


Tom
Reply all
Reply to author
Forward
0 new messages