Évaluation paresseuse en Python

Qu'est-ce que l'évaluation paresseuse en Python?

Un site Web a déclaré:

En Python 3.x la fonction range() renvoie un objet range spécial qui calcule les éléments de la liste à la demande (évaluation paresseuse ou différée):

>>> r = range(10)
>>> print(r)
range(0, 10)
>>> print(r[3])
3

Qu'entend-on par là?

33
demandé sur Filipp W. 2013-12-12 08:55:22

3 réponses

L'objet retourné par range() (ou xrange() en Python2.x) est connu comme un générateur .

Au lieu de stocker toute la plage, [0,1,2,..,9], en mémoire, le générateur stocke une définition pour (i=0; i<10; i+=1) et calcule la valeur suivante uniquement si nécessaire (aka lazy-evaluation).

Essentiellement, un générateur vous permet de renvoyer une liste comme structure, mais voici quelques différences:

  1. une liste stocke tous les éléments lors de sa création. Un générateur génère l'élément suivant lorsqu'il est nécessaire.
  2. Une liste peut être itéré autant que de besoin, d'un générateur ne peut être itéré exactement une fois.
  3. une liste peut obtenir des éléments par index, un générateur ne peut pas -- il ne génère des valeurs qu'une seule fois, du début à la fin.

Un générateur peut être créé de deux façons:

(1) Très similaire à une compréhension de liste:

# this is a list, create all 5000000 x/2 values immediately, uses []
lis = [x/2 for x in range(5000000)]

# this is a generator, creates each x/2 value only when it is needed, uses ()
gen = (x/2 for x in range(5000000)) 

(2) en tant que fonction, en utilisant yield pour renvoyer le suivant valeur:

# this is also a generator, it will run until a yield occurs, and return that result.
# on the next call it picks up where it left off and continues until a yield occurs...
def divby2(n):
    num = 0
    while num < n:
        yield num/2
        num += 1

# same as (x/2 for x in range(5000000))
print divby2(5000000)

Remarque:, Même si range(5000000) est un générateur en Python3.x, [x/2 for x in range(5000000)] est toujours une liste. range(...) fait son travail et génère x un à la fois, mais la liste entière des valeurs x/2 sera calculée lorsque cette liste sera créée.

51
répondu bcorso 2014-06-30 15:38:08

En un mot, l'évaluation paresseuse signifie que l'objet est évalué quand il est nécessaire, pas quand il est créé.

En Python 2, range renverra une liste-cela signifie que si vous lui donnez un grand nombre, il calculera la plage et retournera au moment de la création:

>>> i = range(100)
>>> type(i)
<type 'list'>

Dans Python 3, cependant, vous obtenez un objet range spécial:

>>> i = range(100)
>>> type(i)
<class 'range'>

Ce n'est que lorsque vous le consommez, qu'il sera réellement évalué - en d'autres termes, il ne retournera que les nombres dans la plage lorsque vous réellement besoin d'eux.

12
répondu Burhan Khalid 2013-12-12 05:02:07

Un repo GitHub nommé Python patterns et wikipedia nous disent ce qu'est l'évaluation paresseuse.

Retarde l'évaluation d'un expr jusqu'à ce que sa valeur soit nécessaire et évite les évaluations répétées.

range en python3 n'est pas une évaluation paresseuse complète, car elle n'évite pas l'évaluation répétée.

Un exemple plus classique d'évaluation paresseuse est cached_property:

import functools

class cached_property(object):
    def __init__(self, function):
        self.function = function
        functools.update_wrapper(self, function)

    def __get__(self, obj, type_):
        if obj is None:
            return self
        val = self.function(obj)
        obj.__dict__[self.function.__name__] = val
        return val

Le cached_property (aka lazy_property) est un décorateur qui convertit un func en un lazy l'évaluation de la propriété. La première fois que la propriété est accédée, la fonction est appelée pour obtenir le résultat, puis la valeur est utilisée la prochaine fois que vous accédez à la propriété.

Par exemple:

class LogHandler:
    def __init__(self, file_path):
        self.file_path = file_path

    @cached_property
    def load_log_file(self):
        with open(self.file_path) as f:
            # the file is to big that I have to cost 2s to read all file
            return f.read()

log_handler = LogHandler('./sys.log')
# only the first time call will cost 2s.
print(log_handler.load_log_file)
# return value is cached to the log_handler obj.
print(log_handler.load_log_file)

Pour utiliser un mot, un python objet de générateur comme range sont plus comme conçu par call_by_need modèle, plutôt que de évaluation différée

0
répondu Vi.Ci 2018-01-24 07:27:48