简体   繁体   中英

What are the advantages of using a generator function in the following case?

The objective of my assignment is to produce list elements indefinitely. So I did this:

SERVERS = ['APP1', 'APP2', 'APP3']
#SERVERS = ['APP1', 'APP2', 'APP3', 'APP4', 'APP5', 'APP6']
length = len(SERVERS)

def get_server():
    current_server = SERVERS.pop(0)
    SERVERS.append(current_server)
    return current_server

if __name__ == '__main__':
    for i in range(9):
        print get_server()

The solution has something like this:

SERVERS = ['APP1', 'APP2', 'APP3']
#SERVERS = ['APP1', 'APP2', 'APP3', 'APP4', 'APP5', 'APP6']

def get_server():
    def f():
        while True:
            i = SERVERS.pop(0)
            SERVERS.append(i)
            yield i
    return next(f())

if __name__ == '__main__':
    for i in range(9):
        print get_server()

The output although is same in both the cases:

codewingx@CodeLair:~/repo/python$ python load_balancer.py
APP1
APP2
APP3
APP1
APP2
APP3
APP1
APP2
APP3

So how is generator function beneficial?

Use itertools.cycle()

The generator does not add anything useful here. I would try to avoid pop(0) as it triggers a rebuild of the whole server list each time.

I would recommend itertools.cycle() :

from __future__ import print_function

from itertools import cycle

SERVERS = ['APP1', 'APP2', 'APP3']

servers = cycle(SERVERS)

for i in range(9):
    print(next(servers))

Output:

APP1
APP2
APP3
APP1
APP2
APP3
APP1
APP2
APP3

Our wrapped in a function to match your usage:

def make_get_server():
    servers = cycle(SERVERS)
    def get_server():
        return next(servers)
    return get_server

get_server = make_get_server()

for i in range(9):
    print(get_server())

Output:

APP1
APP2
APP3
APP1
APP2
APP3
APP1
APP2
APP3

Write you own generator function

To make the point for a generator, a variation that takes advantage of its ability to store stet might more useful:

def gen():
    index = 0
    end = len(SERVERS)
    while True:
        yield SERVERS[index]
        index += 1
        if index >= end:
            index = 0

While this illustrates nicely that you have state working with index , the same can be achieved more easily with:

def gen():
    while True:
        for server in SERVERS:
            yield server

g = gen()

def get_server():
    return next(g)

This avoids modifying the list of SERVERS . The result is the same:

for i in range(9):
    print(get_server())

Output:

APP1
APP2
APP3
APP1
APP2
APP3
APP1
APP2
APP3

How a generator works

A simple generator function:

>>> def gen():
...     print('start')
...     yield 1
...     print('after 1')
...     yield 2
...     print('after 2')
...

Make an instance:

>>> g = gen()

Use next to get the next value returned by yield :

>>> next(g)
start
1

Keep going:

>>> next(g)
after 1
2

Now it is exhausted:

>>> next(g)
after 2

StopIteration  next(g)

You may think of cursor that moves along in the generator function. Every time you call next() it moves to the next yield . So putting the yield in a while True loop makes an infinite generator. As long as you don't call close() on it, it gives you a new value with yield . Also, you have state inside the generator. This means you can do things like incrementing counters between calls to next() .

A list is its own generator in this context:

for i in SERVERS:
    do_something_with_element(i)

If you want an infinite generator, @MikeMüller's itertools.cycle is preferred for not-reinventing-the-wheel. If you must do your own:

def my_cycle(s):
    while True:
        for i in s:
             yield i

but don't, it is less efficient and asks more of the code reader's memory.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM