我在 python 3.7 上有一些奇怪的行为,其中包含涉及生成器的嵌套列表理解。
这有效:
i = range(20)
n = [1, 2, 3]
result = [min(x + y for x in i) for y in n]
它不工作,如果i
是发电机:
i = (p for p in range(20))
n = [1, 2, 3]
result = [min(x + y for x in i) for y in n]
这引发了一个 ValueError: min() arg is an empty sequence
现在,即使生成器i
被list
它包裹,仍然会产生相同的错误:
i = (p for p in range(20))
n = [1, 2, 3]
result = [min(x + y for x in list(i)) for y in n]
这是一个python错误还是预期的行为?如果这是预期的行为,你能解释为什么这不起作用吗?
在i = range(20)
将range(20)
要产生发电机的承诺。whilei = (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
打印,但是(生成器在第一次调用中用完了)然后你进入下一轮 aValueError: min() arg is an empty sequence
因为生成器i
已经在第一个 for 循环调用中消耗了 y 为 1。而 ifi
定义为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
该innerst发生器内不是一个函数调用。但尽管如此,它会创建 - 在评估时 - 一个新的生成器 - 至少在用作 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 中,对于这样的实现和细粒度控制,必须求助于 OOP。
我的范围函数和类
最后,我弄清楚了范围函数必须是如何粗略地实现的。(为了好玩,虽然我可以在我知道的 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)
可以像使用 range 函数一样使用它。
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]
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句