web-dev-qa-db-fra.com

Python céder ou retourner un générateur?

J'ai écrit ce morceau de code simple:

def mymap(func, *seq):
  return (func(*args) for args in Zip(*seq))

maintenant j'ai un doute, est-il préférable d'utiliser l'instruction 'return' comme ci-dessus pour renvoyer un générateur ou d'utiliser une instruction 'return from' comme ceci:

def mymap(func, *seq):
 yield from (func(*args) for args in Zip(*seq))

et au-delà de la différence technique entre «rendement» et «rendement», quelle est la meilleure approche dans les cas généraux?

17
AleMal

La différence est que votre première mymap est simplement une fonction habituelle, Dans ce cas, une usine qui renvoie un générateur. Tout À l'intérieur du corps est exécuté dès que vous appelez la fonction.

def gen_factory(func, seq):
    """Generator factory returning a generator."""
    # do stuff ... immediately when factory gets called
    print("build generator & return")
    return (func(*args) for args in seq)

La deuxième mymap est aussi une usine, mais c'est aussi un générateur Lui-même, cédant à partir d'un sous-générateur construit de manière autonome à l'intérieur. Comme c'est un générateur lui-même, l'exécution du corps fait ne commence pas avant la première invocation de next (générateur).

def gen_generator(func, seq):
    """Generator yielding from sub-generator inside."""
    # do stuff ... first time when 'next' gets called
    print("build generator & yield")
    yield from (func(*args) for args in seq)

Je pense que l'exemple suivant le clarifiera. Nous définissons des paquets de données qui doivent être traités avec des fonctions, Groupées dans des tâches que nous transmettons aux générateurs.

def add(a, b):
    return a + b

def sqrt(a):
    return a ** 0.5

data1 = [*Zip(range(1, 5))]  # [(1,), (2,), (3,), (4,)]
data2 = [(2, 1), (3, 1), (4, 1), (5, 1)]

job1 = (sqrt, data1)
job2 = (add, data2)

Nous exécutons maintenant le code suivant dans un shell interactif tel que IPython pour Voir le comportement différent. gen_factory affiche immédiatement , alors que gen_generator ne le fait qu'après avoir appelé next().

gen_fac = gen_factory(*job1)
# build generator & return <-- printed immediately
next(gen_fac)  # start
# Out: 1.0
[*gen_fac]  # deplete rest of generator
# Out: [1.4142135623730951, 1.7320508075688772, 2.0]

gen_gen = gen_generator(*job1)
next(gen_gen)  # start
# build generator & yield <-- printed with first next()
# Out: 1.0
[*gen_gen]  # deplete rest of generator
# Out: [1.4142135623730951, 1.7320508075688772, 2.0]

Pour vous donner un exemple de cas d'utilisation plus raisonnable pour une construction Telle que gen_generator, nous l'étendrons un peu et en ferons une coroutine En assignant un rendement à des variables, de manière à pouvoir injecter des emplois .____.] dans le générateur en cours d'exécution avec send().

De plus, nous créons une fonction d'assistance qui exécutera toutes les tâches Dans un travail et en demandera une nouvelle à la fin.

def gen_coroutine():
    """Generator coroutine yielding from sub-generator inside."""
    # do stuff... first time when 'next' gets called
    print("receive job, build generator & yield, loop")
    while True:
        try:
            func, seq = yield "send me work ... or I quit with next next()"
        except TypeError:
            return "no job left"
        else:
            yield from (func(*args) for args in seq)


def do_job(gen, job):
    """Run all tasks in job."""
    print(gen.send(job))
    while True:
        result = next(gen)
        print(result)
        if result == "send me work ... or I quit with next next()":
            break

Nous exécutons maintenant gen_coroutine avec notre fonction d'assistance do_jobet deux travaux.

gen_co = gen_coroutine()
next(gen_co)  # start
# receive job, build generator & yield, loop  <-- printed with first next()
# Out:'send me work ... or I quit with next next()'
do_job(gen_co, job1)  # prints out all results from job
# 1
# 1.4142135623730951
# 1.7320508075688772
# 2.0
# send me work... or I quit with next next()
do_job(gen_co, job2)  # send another job into generator
# 3
# 4
# 5
# 6
# send me work... or I quit with next next()
next(gen_co)
# Traceback ...
# StopIteration: no job left

Pour revenir à votre question, quelle version est la meilleure approche en général. Un IMO comme gen_factory n’a de sens que si vous avez besoin de faire la même chose pour plusieurs générateurs que vous allez créer, ou dans le cas où votre processus de construction Les générateurs sont suffisamment compliqués pour justifier l’utilisation d’une usine au lieu de construire des générateurs individuels avec un système de compréhension.

Remarque:

La description ci-dessus pour la fonction gen_generator (deuxième mymap) indique "It est un générateur lui-même". C’est un peu vague et techniquement pas Vraiment correct, mais facilite de raisonner sur les différences entre les fonctions Dans cette configuration délicate où gen_factory renvoie également un générateur, à savoir que Est construit par la compréhension du générateur à l'intérieur.

En fait any function (pas seulement ceux de cette question avec des compréhensions de générateur à l'intérieur!) Avec un yield à l'intérieur, lors de l'invocation, seulement retourne un objet générateur qui est construit à partir de le corps de la fonction.

type(gen_coroutine) # function
gen_co = gen_coroutine(); type(gen_co) # generator

Ainsi, toute l'action que nous avons observée ci-dessus pour gen_generator et gen_coroutine Se déroule dans ces objets générateurs, les fonctions avec yield à l'intérieur ayant déjà été crachées auparavant.

9
Darkonaut

Générateurs use yield, fonctions use return.

Les générateurs sont généralement utilisés dans les boucles for pour parcourir plusieurs fois les valeurs fournis automatiquement par un générateur , mais peuvent également être utilisés dans un autre contexte, e. g. dans list () fonction pour créer une liste - encore à partir de valeurs fournie automatiquement par un générateur .

Les fonctions sont appelées pour fournir la valeur renvoyée , une seule valeur pour chaque appel.

0
MarianD