다음과 같이 (잠재적으로) 길이가 다른 2 개의 발전기를 구문 분석하고 싶습니다 zip
.
for el1, el2 in zip(gen1, gen2):
print(el1, el2)
그러나 gen2
요소가 적 으면 추가 요소 중 하나 gen1
가 “소비”됩니다.
예를 들어
def my_gen(n:int):
for i in range(n):
yield i
gen1 = my_gen(10)
gen2 = my_gen(8)
list(zip(gen1, gen2)) # Last tuple is (7, 7)
print(next(gen1)) # printed value is "9" => 8 is missing
gen1 = my_gen(8)
gen2 = my_gen(10)
list(zip(gen1, gen2)) # Last tuple is (7, 7)
print(next(gen2)) # printed value is "8" => OK
분명히, 더 이상 요소가 없다는 것을 깨닫기 전에 읽히므로 (따라서 값을 생성 8
하기 때문에) 값이 누락되었습니다 ( 이전 예제에서) 그러나이 값은 우주에서 사라집니다. 때 “이상”입니다, 그런 “문제”가 없다.gen1
8
gen2
gen2
질문 :이 누락 된 값을 검색하는 방법이 8
있습니까 (예 : 이전 예에서)? … 다양한 수의 인수를 사용하는 것이 이상적입니다 zip
.
참고 : 현재 다른 방법으로 사용하여 구현 itertools.zip_longest
했지만이 누락 값을 사용 zip
하거나 이와 동등한 방법을 궁금하게 생각합니다 .
참고 2 : 새로운 구현을 제출하고 시도하려는 경우이 REPL에서 다양한 구현에 대한 테스트를 만들었습니다. : https://repl.it/@jfthuong/MadPhysicistChester
답변
한 가지 방법은 마지막 값을 캐시 할 수있는 생성기를 구현하는 것입니다.
class cache_last(collections.abc.Iterator):
"""
Wraps an iterable in an iterator that can retrieve the last value.
.. attribute:: obj
A reference to the wrapped iterable. Provided for convenience
of one-line initializations.
"""
def __init__(self, iterable):
self.obj = iterable
self._iter = iter(iterable)
self._sentinel = object()
@property
def last(self):
"""
The last object yielded by the wrapped iterator.
Uninitialized iterators raise a `ValueError`. Exhausted
iterators raise a `StopIteration`.
"""
if self.exhausted:
raise StopIteration
return self._last
@property
def exhausted(self):
"""
`True` if there are no more elements in the iterator.
Violates EAFP, but convenient way to check if `last` is valid.
Raise a `ValueError` if the iterator is not yet started.
"""
if not hasattr(self, '_last'):
raise ValueError('Not started!')
return self._last is self._sentinel
def __next__(self):
"""
Retrieve, record, and return the next value of the iteration.
"""
try:
self._last = next(self._iter)
except StopIteration:
self._last = self._sentinel
raise
# An alternative that has fewer lines of code, but checks
# for the return value one extra time, and loses the underlying
# StopIteration:
#self._last = next(self._iter, self._sentinel)
#if self._last is self._sentinel:
# raise StopIteration
return self._last
def __iter__(self):
"""
This object is already an iterator.
"""
return self
이를 사용하려면 입력을 zip
다음으로 랩핑하십시오 .
gen1 = cache_last(range(10))
gen2 = iter(range(8))
list(zip(gen1, gen2))
print(gen1.last)
print(next(gen1))
gen2
iterable 대신 iterator 를 만드는 것이 중요 하므로 어느 것이 소진되었는지 알 수 있습니다. gen2
소진 된 경우 확인하지 않아도됩니다 gen1.last
.
다른 접근 방식은 별도의 반복 가능한 항목 대신 가변 가능한 반복 가능한 시퀀스를 허용하도록 zip을 재정의하는 것입니다. 그러면 iterables를 “탐색 된”항목이 포함 된 체인 버전으로 바꿀 수 있습니다.
def myzip(iterables):
iterators = [iter(it) for it in iterables]
while True:
items = []
for it in iterators:
try:
items.append(next(it))
except StopIteration:
for i, peeked in enumerate(items):
iterables[i] = itertools.chain([peeked], iterators[i])
return
else:
yield tuple(items)
gens = [range(10), range(8)]
list(myzip(gens))
print(next(gens[0]))
이 접근법은 여러 가지 이유로 문제가 있습니다. 원본 iterable을 잃을뿐만 아니라 원본 객체를 객체로 대체하여 가질 수있는 유용한 속성을 잃게됩니다 chain
.
답변
def zip(*iterables):
# zip('ABCD', 'xy') --> Ax By
sentinel = object()
iterators = [iter(it) for it in iterables]
while iterators:
result = []
for it in iterators:
elem = next(it, sentinel)
if elem is sentinel:
return
result.append(elem)
yield tuple(result)
첫 번째 예에서 gen1 = my_gen(10)
및 gen2 = my_gen(8)
. 두 발전기는 모두 7 번째 반복까지 소비됩니다. 이제 8 번째 반복 gen1
호출 에서는 8 elem = next(it, sentinel)
을 반환하지만 gen2
호출 elem = next(it, sentinel)
할 때 sentinel
(이것이 gen2
소진 되었기 때문에 ) if elem is sentinel
반환되고 만족되고 함수는 return과 stop을 실행합니다. 이제 next(gen1)
9를 반환합니다.
두 번째 예에서 gen1 = gen(8)
및 gen2 = gen(10)
. 두 발전기는 모두 7 번째 반복까지 소비됩니다. 이제 8 번째 반복에서 gen1
호출 elem = next(it, sentinel)
하는 수익률 sentinel
(때문에이 점에 gen1
소진)과 if elem is sentinel
만족과 기능이 실행 반환 및 정지. 이제 next(gen2)
8을 반환합니다.
Mad Physicist ‘s answer 에서 영감을 얻은 이 Gen
래퍼를 사용 하여 이에 대응할 수 있습니다.
편집 : Jean-Francois T가 지적한 사례를 처리합니다 .
이터레이터에서 값이 소비되면 이터레이터에서 영원히 사라지고 이터레이터가 이터레이터를 다시 이터레이터에 다시 추가 할 수있는 적절한 돌연변이 방법이 없습니다. 한 가지 해결 방법은 마지막으로 소비 된 값을 저장하는 것입니다.
class Gen:
def __init__(self,iterable):
self.d = iter(iterable)
self.sentinal = object()
self.prev = self.sentinal
def __iter__(self):
return self
@property
def last_val_consumed(self):
if self.prev is None:
raise StopIteration
if self.prev == self.sentinal:
raise ValueError('Nothing has been consumed')
return self.prev
def __next__(self):
self.prev = next(self.d,None)
if self.prev is None:
raise StopIteration
return self.prev
예 :
# When `gen1` is larger than `gen2`
gen1 = Gen(range(10))
gen2 = Gen(range(8))
list(zip(gen1,gen2))
# [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7)]
gen1.last_val_consumed
# 8 #as it was the last values consumed
next(gen1)
# 9
gen1.last_val_consumed
# 9
# 2. When `gen1` or `gen2` is empty
gen1 = Gen(range(0))
gen2 = Gen(range(5))
list(zip(gen1,gen2))
gen1.last_val_consumed
# StopIteration error is raised
gen2.last_val_consumed
# ValueError is raised saying `ValueError: Nothing has been consumed`
답변
나는 당신 이이 답변을 이미 발견했으며 의견에 올라 왔지만 대답을 할 것이라고 생각했습니다. itertools.zip_longest()
짧은 생성기의 빈 값을 다음으로 대체하는 을 사용하려고합니다 None
.
import itertools
def my_gen(n:int):
for i in range(n):
yield i
gen1 = my_gen(10)
gen2 = my_gen(8)
for i, j in itertools.zip_longest(gen1, gen2):
print(i, j)
인쇄물:
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 None
9 None
fillvalue
호출 zip_longest
할 때 인수를 None
기본값 으로 대체 할 수도 있지만 기본적으로 for 루프에서 None
( i
또는 j
)를 누르면 다른 변수에 8
.
답변
@GrandPhuba의 설명에서 영감을 얻은 zip
“안전한”변형 ( 여기서 단위 테스트 )을 만들어 보겠습니다 .
def safe_zip(*args):
"""
Safe zip that restores last consumed element in eachgenerator
if not able to consume an element in all of them
Returns:
* generators in tuple
* generator for zipped generators
"""
continue_ = True
n = len(args)
result = (_ for _ in [])
while continue_:
addend = []
for i, gen in enumerate(args):
try:
value = next(gen)
addend.append(value)
except StopIteration:
genlist = list(args)
args = tuple([chain([v], g) for v, g in zip(addend, genlist[:i])]+genlist[i:])
continue_ = False
break
if len(addend)==n: result = chain(result, [tuple(addend)])
return args, result
기본 테스트는 다음과 같습니다.
g1, g2 = (i for i in range(10)), (i for i in range(4))
# Create (g1, g2), g3 first, then loop over g3 as one would with zip
(g1, g2), g3 = safe_zip(g1, g2)
for a, b in g3:
print(a, b)#(0, 0) to (3, 3)
for x in g1:
print(x)#4 to 9
답변
itertools.tee 및 itertools.islice를 사용할 수 있습니다 .
from itertools import islice, tee
def zipped(gen1, gen2, pred=list):
g11, g12 = tee(gen1)
z = pred(zip(g11, gen2))
return (islice(g12, len(z), None), gen2), z
gen1 = iter(range(10))
gen2 = iter(range(5))
(gen1, gen2), output = zipped(gen1, gen2)
print(output)
print(next(gen1))
# [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
# 5
답변
코드를 재사용하려면 가장 쉬운 해결책은 다음과 같습니다.
from more_itertools import peekable
a = peekable(a)
b = peekable(b)
while True:
try:
a.peek()
b.peek()
except StopIteration:
break
x = next(a)
y = next(b)
print(x, y)
print(list(a), list(b)) # Misses nothing.
설정을 사용하여이 코드를 테스트 할 수 있습니다.
def my_gen(n: int):
yield from range(n)
a = my_gen(10)
b = my_gen(8)
인쇄됩니다 :
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
[8, 9] []
답변
소진 된 반복자 zip(..., ...).__iter__
가 소진되면 삭제되고 액세스 할 수 없기 때문에 기본 for 루프를 사용하여 값을 검색 할 수 있다고 생각하지 않습니다 .
당신은 당신의 우편 번호를 변경해야합니다, 그리고 당신은 일부 해키 코드와 함께 드롭 항목의 위치를 얻을 수 있습니다
z = zip(range(10), range(8))
for _ in iter(z.__next__, None):
...
_, (one, other) = z.__reduce__()
_, (i_one,), p_one = one.__reduce__() # p_one == current pos, 1 based
import itertools
val = next(itertools.islice(iter(i_one), p_one - 1, p_one))