Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python function that produces both generator and aggregate results

What is the Pythonic way to make a generator that also produces aggregate results? In meta code, something like this (but not for real, as my Python version does not support mixing yield and return):

def produce():
    total = 0
    for item in find_all():
        total += 1
        yield item

    return total

As I see it, I could:

  1. Not make produce() a generator, but pass it a callback function to call on every item.
  2. With every yield, also yield the aggregate results up until now. I'd rather not calculate the intermediate results with every yield, only when finishing.
  3. Send a dict as argument to produce() that will be populated with the aggregate results.
  4. Use a global to store aggregate results.

All of them don't seem very attractive...

NB. total is a simple example, my actual code requires complex aggregations. And I need intermediate results before produce() finishes, hence a generator.

like image 767
Willem Avatar asked Apr 06 '26 06:04

Willem


2 Answers

Maybe you shouldn't use a generator but an iterator.

def findall():  # no idea what your "find_all" does so I use this instead. :-)
    yield 1
    yield 2
    yield 3

class Produce(object):
    def __init__(self, iterable):
        self._it = iterable
        self.total = 0

    def __iter__(self):
        return self

    def __next__(self):
        self.total += 1
        return next(self._it)

    next = __next__  # only necessary for python2 compatibility

Maybe better to see this with an example:

>>> it = Produce(findall())
>>> it.total
0
>>> next(it)
1
>>> next(it)
2
>>> it.total
2
like image 84
MSeifert Avatar answered Apr 08 '26 19:04

MSeifert


you can use enumerate to count stuff, for example

i=0
for i,v in enumerate(range(10), 1 ):
    print(v)
print("total",i)

(notice the start value of the enumerate)

for more complex stuff, you can use the same principle, make produce a generator that yield both values and ignore one in the iteration and use it later when finished.

other alternative is passing a modifiable object, for example

def produce(mem):
    t=0
    for x in range(10):
        t+=1
        yield x
    mem.append(t)

aggregate=[]
for x in produce(aggregate):
    print(x)
print("total",aggregate[0])

in either case the result is the same for this example

0
1
2
3
4
5
6
7
8
9
total 10
like image 28
Copperfield Avatar answered Apr 08 '26 21:04

Copperfield



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!