44

Quoting from Python Essential Reference, by David Beazley, page 20:

Normally, functions operate on a single set of input arguments. However, a function can also be written to operate as a task that processes a sequence of inputs sent to it. This type of function is known as a coroutine and is created by using the yield statement as an expression (yield) as shown in this example:

def print_matches(matchtext):
    print "Looking for", matchtext
    while True:
        line = (yield)       # Get a line of text
        if matchtext in line:
            print line

To use this function, you first call it, advance it to the first (yield), and then start sending data to it using send(). For example:

>>> matcher = print_matches("python")
>>> matcher.next() # Advance to the first (yield)
Looking for python
>>> matcher.send("Hello World")
>>> matcher.send("python is cool")
python is cool
>>> matcher.send("yow!")
>>> matcher.close() # Done with the matcher function call

Based on that information, I wrote this code:

#!/usr/bin/python3.2
import sys

def match_text(pattern):
    line = (yield)
    if pattern in line:
        print(line)

x = match_text('apple')
x.next()

for line in input('>>>> '):
    if x.send(line):
        print(line)

x.close()

But I get an error message like:

Traceback (most recent call last):
  File "xxx", line 9, in <module>
    matcher.next() # Advance to the first (yield)
AttributeError: 'generator' object has no attribute 'next'

Why doesn't this code (or the code from the book, for that matter) work in Python 3.2? It seems that what is supposed to be a coroutine, is treated as a generator instead - why? What is going on here?

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • 1
    Years later I have found this question again and seen the suggestions (but no close votes) for a duplicate. I agree that the other question is a better duplicate because it gets right to the point, and because the terminology confusion is only incidental to the actual problem. I went ahead and marked it as duplicate. – Karl Knechtel Jul 19 '22 at 16:53

3 Answers3

77

You're getting thrown off by the error message; type-wise, Python doesn't make a distinction - you can .send to anything that uses yield, even if it doesn't do anything with the sent value internally.

In 3.x, there is no longer a .next method attached to these; instead, use the built-in free function next:

next(matcher)
Steven C. Howell
  • 16,902
  • 15
  • 72
  • 97
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • 2
    Specifically, the protocol was changes so that Python expects a `.__next__` method, rather than `.next`, to implement the logic. (Iteration can also be implemented by making the `.__iter__` method be a generator function.) The free function `next` should be used regardless: by convention, every method name starting and ending with `__` is intended for a special, behind-the-scenes purpose, and should not be called directly from outside the class. This also works in 2.6 and 2.7, if for some reason you are forced to use the Python equivalent of Windows XP or 7, respectively. – Karl Knechtel May 30 '22 at 20:36
14

For python version 3.2 the syntax for the next() in-built function should be matcher.__next__() or next(matcher).

Adil B
  • 14,635
  • 11
  • 60
  • 78
6

In the case you find yourself patching somebody's code, it seems that the built-in python3 next() function calls the iterator's next() function, so you may be able to find/replace somebody's python2 .next( with the python3-tolerable .__next__( as I just did to make portions of the primefac module work in python3 (among other trivial changes).

Here's the reference:

next(iterator[, default])

Retrieve the next item from the iterator by calling its next() method. If default is given, it is returned if the iterator is exhausted, otherwise StopIteration is raised.

sage
  • 4,863
  • 2
  • 44
  • 47