Welcome, guest | Sign In | My Account | Store | Cart

An example which shows the power of decorators when combined with generators.

This recipe allows to generate different kinds of series of numbers by applying a decorator over an infinite integer generator. A processing function and a condition function can be used to specify the rules.

Python, 68 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# Simple series generator with
# generators & decorators.
# Author : Anand B Pillai

# Beginning of recipe

def myfunc(**kwds):

    def func(f):
        # Condition function
        cond = kwds['condition']
        # Processing function
        proc = kwds['process']
        # Number of items
        num = kwds['number']

        x, l = 0, []
        for item in f():
            
            if cond and cond(item):
                if proc: item=proc(item)
                l.append(item)
                x += 1
                
            if x==num:
                break

        return l

    return func

def series(condition=None, process=None, number=10):
    """ Infinite integer generator """

    @myfunc(condition=condition,process=process,number=number)    
    def wrapper():
        x = 1
        while 1:
            yield x
            x += 1

    return wrapper

# End of recipe
-----snip-------------snip-------------------------
Examples.
  
def prime(x):
    is_prime=True
    for y in range(2,int(pow(x,0.5)) + 1):
        if x % y==0:
            is_prime=False
            break

    return is_prime

# Print first 10 prime numbers
print series(condition=prime, process=None, number=10)
[1, 2, 3, 5, 7, 11, 13, 17, 19, 23]
# Print first 10 odd numbers
print series(condition=lambda x: x%2, process=None, number=10)
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
# Print squares of first 10 numbers
print series(condition=lambda x: x, process=lambda x:  x*x, number=10)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# Print a few random numbers
import random
print series(condition=lambda x: x, process=lambda x: random.random(), number=10)

This is a convenient way of extracting a required series with a required size out of a generator that returns an infinite stream of numbers.

Since we are using a generator instead of a list comprehension or FP tools such as map/filter, memory is saved for large series. Also custom processing can be done on the items of the series which allows you to write all kinds of series using this recipe.

The generator can also be modified to do some processing of its own, something which it does not do currently.

1 comment

Raymond Hettinger 16 years, 9 months ago  # | flag

Generator expressions offer better flexibility, speed, and economy of expression. Since the condition, process, and counter do not share a namespace, lambdas have to be used to specify the expression variable. This is slow, verbose, and clumsy compared to equivalent generator expressions.

If itertools.count is used to generate the series inputs, you get the additional advantage of being able to specify the starting count (from zero, from one, or anywhere else).

Also, itertools.islice() is a flexibile tool for extracting the first n elements of a series. However, more versatility can come from a design that allows an infinite series to be generated simply by dropping the enclosing islice().

>>> from itertools import count, islice
>>> def take(n, iterable):
    return list(islice(iterable, n))

>>> take(10, (x for x in count(1) if prime(x)))
[1, 2, 3, 5, 7, 11, 13, 17, 19, 23]
>>> take(10, (x for x in count() if x%2))
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
>>> take(10, (x*x for x in count(1)))
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>> import random
>>> take(10, (random.random() for x in count()))
[0.94710345491708259, 0.4736249879152532, 0.059853883359122562, 0.65141323268525519, 0.60859729173749066, 0.82585367661224152, 0.91566522952506124, 0.43985135483694404, 0.43158061837573281, 0.16226950910826099]

# Combined example:  first ten squares of primes over 4
>>> take(10, (x*x for x in count(4) if prime(x)))
[25, 49, 121, 169, 289, 361, 529, 841, 961, 1369]

# Example of flexibility and simplicity of an infinite generator:
>>> zip(xrange(5), (x*x for x in count(2) if prime(x)))
[(0, 4), (1, 9), (2, 25), (3, 49), (4, 121)]
Created by Anand on Wed, 23 Feb 2005 (PSF)
Python recipes (4591)
Anand's recipes (38)

Required Modules

  • (none specified)

Other Information and Tasks