É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à?
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:
- 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.
- Une liste peut être itéré autant que de besoin, d'un générateur ne peut être itéré exactement une fois.
- 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.
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.
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