method-to-instance binding, callable generator decorator

Jean-Michel Pichavant jeanmichel at sequans.com
Wed Jan 26 12:55:53 EST 2011


Jack Bates wrote:
> Am struggling to understand Python method-to-instance binding
>
> Anyone know why this example throws a TypeError?
>
>   
>> #!/usr/bin/env python
>>
>> import functools
>>
>> # Take a generator function (i.e. a callable which returns a generator) and
>> # return a callable which calls .send()
>> class coroutine:
>>   def __init__(self, function):
>>     self.function = function
>>
>>     functools.update_wrapper(self, function)
>>
>>   def __call__(self, *args, **kwds):
>>     try:
>>       return self.generator.send(args)
>>
>>     except AttributeError:
>>       self.generator = self.function(*args, **kwds)
>>
>>       return self.generator.next()
>>
>> # Each time we're called, advance to next yield
>> @coroutine
>> def test():
>>   yield 'call me once'
>>   yield 'call me twice'
>>
>> # Works like a charm : )
>> assert 'call me once' == test()
>> assert 'call me twice' == test()
>>
>> class Test:
>>
>>   # Each time we're called, advance to next yield
>>   @coroutine
>>   def test(self):
>>     yield 'call me once'
>>     yield 'call me twice'
>>
>> test = Test()
>>
>> # TypeError, WTF?
>> assert 'call me once' == test.test()
>> assert 'call me twice' == test.test()
>>     
>
> https://gist.github.com/797019
>
> Am trying to write a decorator such that each time I call a function, it
> advances to the next "yield" - I plan to use functions like this as
> fixtures in tests
>
> Does a decorator like this already exist in the Python standard library?
>   

At the time you set the self.function attribute, its value is an unbound method, and thus must be called with the instance as first attribute.
Since "self.generator = self.function(*args, **kwds)" doesn't pass the self arguement as 1st parameter, you have to do it yourself.

replace your last 2 lines by 

assert 'call me once' == test.test(test)
assert 'call me twice' == test.test(test)

One alternative is to decorate, once the instance is created, ie. the method is bound to the instance and does not require to pass the instance as 1st argument:

class Test2:

    def test2(self):
        yield 'call me once'
        yield 'call me twice'

test2 = Test2()
test2.test2 = coroutine(test2.test2)

assert 'call me once' == test2.test2()
assert 'call me twice' == test2.test2()


I'm not sure it's a standard way to proceed though, it looks rather strange. I'm not familiar with decorators, but my guess is that one decorator cannot (except through the above tricks) decorate functions AND unbound methods.

In order to make your original coroutine decorator work with unbound methods, and only unbound methods, change
self.generator = self.function(*args, **kwds)
into
self.generator = self.function(self, *args, **kwds)

Hope it helps,

JM




More information about the Python-list mailing list