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

Jacob Holm wrote:
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:
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

Jacob Holm wrote:
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:
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