Yield-from: Nonexistent throw() and close()?

I'm happy to raise exceptions in the face of nonexistent send() methods, but I'm not so sure about throw() and close(). The purpose of close() is to make sure the generator is cleaned up before discarding it. If it's delegating to something that doesn't have a close(), then presumably that thing doesn't need to do any cleaning up, in which case we should just ignore it and close() the generator as usual. In the case of throw(), if we raise an AttributeError when the iterator doesn't have throw(), we're still going to end up raising an exception in the delegating generator, just not the one that was thrown in. One use case I can think of is for killing a generator- based thread -- you could define a KillThread exception for this and throw it into the thread you want to kill. The main loop of your scheduler would then catch KillThread exceptions and silently remove the thread from the system. But if the KillThread gets turned into something else, it won't get caught and everything will fall over for no good reason. So at least in that case I think it makes more sense to ignore the lack of a throw() method and raise the thrown-in exception in the delegating generator. Does this reasoning make sense? -- Greg

Greg Ewing wrote:
I'm happy to raise exceptions in the face of nonexistent send() methods, but I'm not so sure about throw() and close(). I'm not. I'd hate for this:
def foo(): for i in xrange(5): yield i to behave different from this: def foo(): yield from xrange(5) I think a missing send should be converted to a next, just as the PEP proposed.
The purpose of close() is to make sure the generator is cleaned up before discarding it. If it's delegating to something that doesn't have a close(), then presumably that thing doesn't need to do any cleaning up, in which case we should just ignore it and close() the generator as usual.
In the case of throw(), if we raise an AttributeError when the iterator doesn't have throw(), we're still going to end up raising an exception in the delegating generator, just not the one that was thrown in.
One use case I can think of is for killing a generator- based thread -- you could define a KillThread exception for this and throw it into the thread you want to kill. The main loop of your scheduler would then catch KillThread exceptions and silently remove the thread from the system.
But if the KillThread gets turned into something else, it won't get caught and everything will fall over for no good reason. So at least in that case I think it makes more sense to ignore the lack of a throw() method and raise the thrown-in exception in the delegating generator.
Does this reasoning make sense?
Yup. Works for me. Regards Jacob

Jacob Holm wrote:
Greg Ewing wrote:
I'm happy to raise exceptions in the face of nonexistent send() methods, but I'm not so sure about throw() and close(). I'm not. I'd hate for this:
def foo(): for i in xrange(5): yield i
to behave different from this:
def foo(): yield from xrange(5) These two forms already behave differently when generators are used (rather than xrange), why should they not also behave differently when non-generators are used?
"In the face of ambiguity, refuse the temptation to guess." I think that an exception makes more sense, otherwise, we are guessing as to what the programmer intended by using send in your example.

Hi Bruce Bruce Frederiksen wrote:
Jacob Holm wrote:
I'd hate for this:
def foo(): for i in xrange(5): yield i
to behave different from this:
def foo(): yield from xrange(5) These two forms already behave differently when generators are used (rather than xrange), why should they not also behave differently when non-generators are used? Not sure in what way you think they behave differently? foo is a generator in both cases, and as such has a send method. I am thinking of #2 as a simple rewrite/refactoring using the nifty new feature. Why should foo().send('bar') ignore the value in #1 and raise an exception in #2?
"In the face of ambiguity, refuse the temptation to guess."
I think that an exception makes more sense, otherwise, we are guessing as to what the programmer intended by using send in your example.
I disagree. The principle of least surprise tells me that #1 and #2 should be the same. Regards Jacob

Jacob Holm wrote:
Hi Bruce
Bruce Frederiksen wrote:
Jacob Holm wrote:
I'd hate for this:
def foo(): for i in xrange(5): yield i
to behave different from this:
def foo(): yield from xrange(5) These two forms already behave differently when generators are used (rather than xrange), why should they not also behave differently when non-generators are used? Not sure in what way you think they behave differently? foo is a generator in both cases, and as such has a send method. True, but I was referring to the subgenerator/subiterable (xrange in your example); not to foo itself. If xrange were a generator, #2 behaves intentionally differently than #1. I am thinking of #2 as a simple rewrite/refactoring using the nifty new feature. Why should foo().send('bar') ignore the value in #1 and raise an exception in #2? Thinking of #2 as a simple rewrite of #1 is not how this PEP defines yield from.
Unfortunately, AFAICT the *reason* for the difference is that the for statement was defined well before PEP 342 was adopted. When PEP 342 was adopted, changing the for statement to honor the new generator methods would break legacy code. But the "yield from" is being defined after PEP 342, so it may safely include these new capabilities. Your example does not show why you would want to use send with foo. As it is currently defined, there is no reason to do so. If the yield in #1 is meant to be a yield *expression* who's values are acted on by foo somehow, then "yield from" won't work here, no matter how it's defined, because the yielded values are inaccessible to foo. The same is true if xrange were a generator. -bruce frederiksen

Jacob Holm wrote:
Hi Bruce Bruce Frederiksen wrote:
Jacob Holm wrote:
I'd hate for this:
def foo(): for i in xrange(5): yield i
to behave different from this:
def foo(): yield from xrange(5)
These two forms already behave differently when generators are used
Since yield-from doesn't exist yet, there's no 'already behave'. The issue is whether they *should* behave differently or not. The issue can perhaps be brought into better focus by considering this: def dontsendtome(): y = yield 42 if y is not None: raise AttributeError("dontsendtome has no method 'send'") Now by the "inlining" interpretation (which I think is very good and want to keep if at all possible), def icantbesenttoeither(): yield from dontsendtome() should also raise an AttributeError if you send() it something that isn't None. Now, from the outside, there's little observable difference between a generator such as dontsendtome() and some other iterator that doesn't have a send() method -- both raise an AttributeError if you try to send() something other than None. So it's reasonable to expect them to behave similarly when used in a 'yield from'. (There is one observable difference, namely that send(None) to an iterator with no send() raises an AttributeError, whereas it's impossible to write a generator which can tell the difference. But I see this as an artifact due to the lack of a send(iter, value) protocol function, which really ought to exist and translate send(None) into next() for all iterators). -- Greg
participants (3)
-
Bruce Frederiksen
-
Greg Ewing
-
Jacob Holm