web-dev-qa-db-fra.com

Évaluation paresseuse dans Python

Qu'est-ce qu'une évaluation paresseuse en Python?

Un site Web a déclaré:

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

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

Qu'entend-on par là?

42
Vipul

L'objet renvoyé par range() (ou xrange() en Python2.x) est connu sous le nom de générateur .

Au lieu de stocker la plage entière, [0,1,2,..,9], En mémoire, le générateur stocke une définition de (i=0; i<10; i+=1) Et ne calcule la valeur suivante qu'en cas de besoin (AKA lazy-evaluation).

Essentiellement, un générateur vous permet de renvoyer une structure de type liste, 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ée autant que vous le souhaitez, un générateur ne peut être itéré que exactement une fois.
  3. Une liste peut obtenir des éléments par index, pas un générateur - 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 manières:

(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, utiliser yield pour renvoyer la valeur suivante:

# 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 dans Python3.x, [x/2 for x in range(5000000)] est toujours une liste. range(...) fait son travail et génère x une à la fois, mais la liste entière des valeurs x/2 sera calculée lors de la création de cette liste.

62
bcorso

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

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

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

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

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

Ce n'est que lorsque vous le consommerez qu'il sera réellement évalué - en d'autres termes, il ne renverra les chiffres de la plage que lorsque vous en aurez réellement besoin.

13
Burhan Khalid

Un dépôt github nommé motifs python et wikipedia nous dit ce qu'est une évaluation paresseuse.

Retarde l'évaluation d'une expr jusqu'à ce que sa valeur soit nécessaire et évite les répétitions.

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

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 (a.k.a lazy_property) est un décorateur qui convertit un func en une propriété d'évaluation paresseuse. 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 correct, un objet générateur python comme range est plus comme conçu via call_by_need modèle, plutôt que évaluation paresseuse

2
Vi.Ci