В i = range(20)
range(20)
- это обещание создать генератор.
Пока i = (p for p in range(20))
уже является генератором.
Теперь напишите ваше выражение списка как:
for y in [1, 2, 3]:
print(min(x + y for x in i))
## 1
## ...
## ValueError: min() arg is an empty sequence
Вы печатаете 1
, но (генератор исчерпан при первом вызове)
и тогда вы получите в следующем раунде
ValueError: min() arg is an empty sequence
потому что генератор i
уже использовался в первом вызове цикла for для y как 1.
Хотя, если i
определено как range(20)
,
каждый раз, когда вызывается for x in i
, генератор воссоздается снова и снова.
Вы можете подражать тому, что делает range(20)
:
def gen():
return (p for p in range(20))
for y in [1, 2, 3]:
print(min(x + y for x in gen()))
# range() like gen() is a promise to generate the generator
## 1
## 2
## 3
Теперь генератор создается каждый раз заново.
Но на самом деле, range
еще круче, если вы сделаете:
i = range(20)
for y in [1, 2, 3]:
print(min(x + y for x in i))
## 1
## 2
## 3
i
внутри внутреннего генератора не является вызовом функции.
Но, несмотря на это, он создает - при оценке - новый генератор -
по крайней мере при использовании в качестве итерируемого внутри цикла for.
Это на самом деле реализовано в Python с использованием класса и путем определения метода __iter__()
. Который определяет поведение в интеграторах - здесь особенно ленивое поведение.
Чтобы имитировать это поведение, мы можем сгенерировать ленивый генератор (lazy_gen
).
class lazy_gen:
def __init__(self):
pass
def __iter__(self): # everytime when used as an iterator
return self.gen() # recreate the generator # real lazy behavior
def gen(self):
return (p for p in range(20))
Который мы можем использовать как:
i = lazy_gen()
for y in [1, 2, 3]:
print(min(x + y for x in i))
## 1
## 2
## 3
Так что это еще лучше отражает поведение range()
.
Другие языки (функциональные языки), такие как Lisp
семейные языки (common-lisp, Racket, Scheme, Clojure), R
или Haskell
иметь лучший контроль над оценкой - таким образом, над ленивой оценкой и обещаниями. Но в Python для таких реализаций и тонкого контроля нужно прибегнуть к ООП.
Моя функция диапазона и класс
Наконец, я понял, как должна быть реализована функция диапазона.
(Ради интереса, хотя я мог бы найти его в исходном коде Python, который я знаю, но иногда рассуждения - это весело.)
class Myrange:
def __init__(self, start, end, step):
self.start = start
self.end = end
self.step = step
def __iter__(self):
return self.generate_range()
def generate_range(self):
x = self.start - self.step
while x + self.step < self.end:
x = x + self.step
yield x
def __repr__(self):
return "myrange({}, {})".format(self.start, self.end)
def myrange(start=None, end=None, step=1):
if start is None and end is None:
raise "Please provide at least one number for the range-limits."
elif start is not None and end is None:
_start = 0
_end = start
elif start is not None and end is not None:
_start = start
_end = end
else:
_start = 0
_end = end
_step = step
return Myrange(_start, _end, _step)
Можно использовать точно так же, как функцию диапазона.
i = myrange(20)
n = [1, 2, 3]
result = [min(x + y for x in i) for y in n]
result
## [1, 2, 3]
i
## myrange(0, 20) # representation of a Myrange object.
myrange(20)
## myrange(0, 20)
list(myrange(3, 10))
## [3, 4, 5, 6, 7, 8, 9]
list(myrange(0, 10))
## [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list(myrange(10))
## [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
list(myrange(0, 10, 2))
## [0, 2, 4, 6, 8]
list(myrange(3, 10, 2))
## [3, 5, 7, 9]