Created
August 23, 2022 22:45
-
-
Save jleedev/1532e30455d1739095a86e2c99a4a625 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import collections | |
import itertools | |
# Before you can call send, you have to call next to drive it to the first | |
# yield point. What if it never yields? Then the first call to next will raise | |
# StopIteration on it. | |
def parse_message(): | |
print('parse_message') | |
ret = [] | |
while True: | |
val = (yield) | |
if val == '\0': | |
print('/parse_message') | |
return ret | |
else: | |
print(f'{val} -> {ord(val)}') | |
ret.append(ord(val)) | |
def drive(parser): | |
gen = parser() | |
print() | |
print(gen) | |
next(gen) | |
gen.send('A') | |
gen.send('B') | |
gen.send('C') | |
try: | |
gen.send('\0') | |
except StopIteration as e: | |
print(e.value) | |
# [65, 66, 67] | |
drive(parse_message) | |
def my_parser(): | |
return (yield from parse_message()) | |
# [65, 66, 67] | |
drive(my_parser) | |
def complex_parser(): | |
start = (yield) | |
if start == 'A': | |
# delegate to parse_message | |
return (yield from parse_message()) | |
# [66, 67] | |
drive(complex_parser) | |
# Whoops, need to thread the value into the subgenerator | |
def attempt_1(): | |
start = (yield) | |
if start == 'A': | |
# delegate to parse_message | |
gen = parse_message() | |
next(gen) | |
gen.send(start) | |
return (yield from gen) | |
try: | |
drive(attempt_1) | |
except TypeError: | |
# TypeError: ord() expected string of length 1, but NoneType found | |
pass | |
# That's because (yield from gen) is calling next(gen) to start the generator. | |
# We need to consume that somehow. | |
# To implement yield from, you first call next, yield that value, then | |
# alternately send() the value of yield, and yield the value of send(). | |
# To avoid calling next, you need to have on hand the first value to yield. | |
def unyield(gen, x): | |
just_started = gen.gi_frame.f_lasti == -1 | |
try: | |
if just_started: | |
next(gen) | |
while True: | |
x = yield gen.send(x) | |
except StopIteration as e: | |
return e.value | |
def attempt_2(): | |
start = (yield) | |
if start == 'A': | |
# delegate to parse_message | |
return (yield from unyield(parse_message(), start)) | |
drive(attempt_2) | |
def attempt_3(): | |
start = (yield) | |
if start == 'A': | |
# delegate to parse_message | |
gen = parse_message() | |
next(gen) | |
return (yield from unyield(gen, start)) | |
drive(attempt_3) | |
# Note that both of these work. | |
# - For convenience, we can decide whether we want to call next(gen) at the | |
# start | |
# - Traditional yield from starts with a yield | |
# - Ours starts with a send | |
# Otherwise this is the same | |
# | |
# This can probably be generalized to unyield multiple times, perhaps with a | |
# deque. Basically, you want to | |
# - Pretend to the inner generator that its (yield) is producing values from | |
# the outside | |
# - Be able to use use this idiomatically in a yield from expression | |
# | |
# Our first call to yield doesn't yield a value. Would probably complicate | |
# things to thread that through as well. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment