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

--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to