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à?
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:
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.
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.
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