[Tutor] How to extract numerator and denominator from fractions.Fraction(4, 32)?

Oscar Benjamin oscar.j.benjamin at gmail.com
Tue Aug 6 19:56:14 CEST 2013


On 6 August 2013 17:33, Alan Gauld <alan.gauld at btinternet.com> wrote:
> On 06/08/13 10:01, Walter Prins wrote:
>
>>         py> def cr():  # Co-Routine.
>>         ...     x = yield()
>>         ...     while True:
>>         ...             x = yield(x + 1)
>>         ...
>
>
>> "send" and yield as akin to threaded execution, where cr() is like a
>> thread that blocks at the yield call after creation, and then when you
>> call on magic.send() the "thread" wakes up and continues and eventually
>> returns another value
>
>
> It's clever but I'm not keen on it overloading yield to do it.
> yield as a word conveys (to me at least) the idea of returning a value but
> not quite completely ending. This usage sounds like a different concept and
> I'd have preferred a more explicit name - although I can't think what!!

I thought of Python's yield in the sense that 'to yield' means 'to
concede'. So funcA yields execution to to the parent frame. The new
'yield from' sort of messes that interpretation up though since it
should really be 'yield to' to make sense in this way.

> Also
> I'm not keen on the argument/parameter mechanism
> here either. Arguments are sent but not explicitly declared in the receiver,
> that all feels rather un-pythonic to me.

I'm not really sure what you mean here.

> But I've only
> skimmed it so far, I need to do some hands-on playing I think.
>
> I am getting concerned that python is developing a lot of these
> non-intuitive type features, almost because it can(*). A lot of it no doubt
> scratches somebody's itch but its at the expense of making what was a very
> easy to use language ever more complex. I'm not sure if i would choose
> Python for my Learn to Program tutorial if I was starting it  these days -
> although I'm not sure what else is any better...

No one needs to use .send() if they don't want to. It's also so
uncommonly used that you don't need to mention it in any introductory
tutorial.

There's also .throw():

def cr():
    try:
        yield
    except Exception as e:
        print('received: %s' % e)
    yield

g = cr()
next(g)  # Advance to yield
g.throw(ValueError('42'))


Some time ago I wanted to be able to compute statistics over some data
in a single-pass and I wanted to think of an easy way to invert a
function that consumes an iterator so that I could push values in. The
basic idea is to do something like:

mean = Mean()
var = Var()
max = Max()

for x in data:
    mean.update(data)
    var.update(data)
    max.update(data)

print('mean:', mean.compute())
print('var:', var.compute())
print('max:', max.compute())


Then I realised that any function that computes a fold over an
iterator can be inverted using yield so that this

def isum(iterable, start=0):
    total = start
    for x in iterable:
        total += x
    return total

becomes this

def gsum(start=0):
    total = start
    yield lambda: total
    while True:
        total += yield

gen = gsum()
evaluate = next(gen)
next(gen)  # Move to second yield
push = gen.send

push(1)
push(2)
push(3)
print(evaluate())  # 6

It looks a little shaky like this but you can use a decorator to clean it up:


import functools

def gencalc(gfunc):
    @functools.wraps(gfunc)
    def wrapper(*args, **kwargs):
        gen = gfunc(*args, **kwargs)
        evaluate = next(gen)
        next(gen)  # Move to second yield
        push = gen.send
        return push, evaluate
    return wrapper

@gencalc
def gsum(start=0):
    total = start
    yield lambda: total
    while True:
        total += yield

push, evaluate = gsum()


Then it's easy to make other generator calculation functions:

@gencalc
def gmax():
    yield lambda : currentmax
    currentmax = yield
    while True:
        newval = yield
        if newval > currentmax:
            currentmax = newval

@gencalc
def gvar():  # I haven't tested that this is correct
    yield lambda: totalvar / (count - 1) if count > 1 else 0
    mean = 0
    totalvar = 0
    for count in itertools.count():
        newval = yield
        oldmean = mean
        mean += (newval - mean) / count
        totalvar += (newval - oldmean) * (newval - mean)


The obvious way to do this without generators is something like:

class Sum:
    def __init__(self, start=0):
        self.total = start
    def push(self, value):
        self.total += value
    def evaluate(self):
        return self.total

But I prefer the version that keeps it all in one function and still
looks a lot like the normal function that consumes an iterable.


Oscar


More information about the Tutor mailing list