0

I am using Python 3.6 where you can nicely zip individual generators of one kind to get a multidimensional generator of the same kind. Take the following example, where get_random_sequence is a generator that yields an infinite random number sequence to simulate one individual asset at a stock market.

import random
from typing import Iterator

def get_random_sequence(start_value: float) -> Iterator[float]:
    value = start_value
    yield value

    while True:
        r = random.uniform(-.1, .1) * value
        value = value + r
        yield value

g = get_random_sequence(1.)
a = [next(g) for _ in range(5)]
print(a)
# [1.0, 1.015821868415922, 1.051470250712725, 0.9827564500218019, 0.9001851912863]

This generator can be easily extended with Python's zip function and yield from to generate successive asset values at a simulated market with an arbitrary number of assets.

def get_market(no_assets: int = 10) -> Iterator[Sequence[float]]:
    rg = tuple(get_random_sequence(random.uniform(10., 60.)) for _ in range(no_assets))
    yield from zip(*rg)

gm = get_market(2)
b = [next(gm) for _ in range(5)]
print(b)
# [(55.20719435959121, 54.15552382961163), (51.64409510285255, 53.6327489348457), (50.16517363363749, 52.92881727359184), (48.8692976247231, 52.7090801870517), (52.49414777987645, 49.733746261206036)]

What I like about this approach is the use of yield from to avoid a while True: loop in which a tuple of n assets would have to be constructed explicitly.

My question is: Is there a way to apply yield from in a similar manner when the zipped generators receive values over send()?

Consider the following generator that yields the ratio of successive values in an infinite sequence.

from typing import Optional, Generator

def ratio_generator() -> Generator[float, Optional[float], None]:
    value_last = yield
    value = yield
    while True:
        ratio = 0. if value_last == 0. else value / value_last
        value_last = value
        value = yield ratio

gr = ratio_generator()
next(gr)    # move to the first yield
g = get_random_sequence(1.)
a = []
for _v in g:
    _r = gr.send(_v)
    if _r is None:
        # two values are required for a ratio
        continue
    a.append(_r)
    if len(a) >= 5:
        break
print(a)
# [1.009041186223442, 0.9318419861800313, 1.0607677437816718, 0.9237896996817375, 0.9759635921282439]

The best way to "zip" this generator I could come up with, unfortunately, does not involve yield from at all... but instead the ugly while True: solution mentioned above.

def ratio_generator_multiple(no_values: int) -> Generator[Sequence[float], Optional[Sequence[float]], None]:
    gs = tuple(ratio_generator() for _ in range(no_values))
    for each_g in gs:
        next(each_g)

    values = yield
    ratios = tuple(g.send(v) for g, v in zip(gs, values))

    while True: # :(
        values = yield None if None in ratios else ratios
        ratios = tuple(g.send(v) for g, v in zip(gs, values))

rgm = ratio_generator_multiple(2)
next(rgm)    # move to the first yield
gm = get_market(2)
b = []
for _v in gm:
    _r = rgm.send(_v)
    if _r is None:
        # two values are required for a ratio
        continue
    b.append(_r)
    if len(b) >= 5:
        break
print(b)
# [(1.0684036496767984, 1.0531433541856687), (1.0279604693226763, 1.0649271401851732), (1.0469406709985847, 0.9350856571355237), (0.9818403001921499, 1.0344633443394962), (1.0380945284830183, 0.9081599684720663)]

Is there a way to do something like values = yield from zip(*(g.send(v) for g, v in zip(generators, values))) so I can still use yield from on the zipped generators without a while True:? (The given example doesn't work, because it doesn't refresh the values on the right-hand side with the values on the left-hand side.)

I realize that this is more of an aesthetic problem. It would still be nice to know though...

wehnsdaefflae
  • 826
  • 2
  • 12
  • 27
  • 1
    @C.Nivs: It's syntactically valid. It might not mean what the questioner wants it to mean, though. – user2357112 Apr 07 '20 at 20:08
  • @C.Nivs thanks for your comment! i just wanted to give an example of what I want to achieve. but that boils the problem down to the fact that it seems like you cannot use `yield from` together with `send`, independent from whether generators are `zip`ped or not... would you agree? – wehnsdaefflae Apr 07 '20 at 20:11
  • 1
    You can use `yield from` with `send` - in fact, one of the reasons for introducing `yield from` was `send` support. However, you can only do this if you're `yield from`-ing over a generator, not just any iterator. `zip` isn't a generator. – user2357112 Apr 07 '20 at 20:15
  • @user2357112supportsMonica thanks! but doesn't `yield from` redirect control flow to the generator it... well... yields from? where would I `send` sth. to this generator with each iteration? could you maybe give me an example? – wehnsdaefflae Apr 07 '20 at 20:20
  • 1
    https://ideone.com/3z9QHM – user2357112 Apr 07 '20 at 20:25
  • @user2357112supportsMonica nice! thanks a lot! i support monica as well! if you give your comment as an answer I'll accept it when after a few days nothing else comes up. – wehnsdaefflae Apr 07 '20 at 20:27
  • I don't consider it an answer, since it doesn't really address the whole zip issue - it's just a demonstration of how `send` and `yield from` interact. – user2357112 Apr 07 '20 at 20:28
  • 1
    Writing a `zip` compatible with `send` will probably involve reimplementing `yield from` manually, including the tricky parts of `send`/`throw`/`close` forwarding and exception handling. – user2357112 Apr 07 '20 at 20:30

0 Answers0