2017-10-25 16 views
2

반복 가능한 객체의 모든 요소를 ​​서로 조합하여 비교하고 싶습니다. 재현 가능한 다음 예제는 일반 목록의 기능을 모방 한 것이지만 내 문제를 보여줍니다. 이 예제에서는 [ "A", "B", "C", "D"] 목록을 가지고 다음과 같은 16 줄의 출력을 얻고 싶습니다. 각 항목의 모든 조합은 서로 다릅니다. 100 개의 항목 목록은 100 * 100 = 10,000 행을 생성해야합니다.동시에 하나의 python iterable을 통해 여러 iterator를 가질 수 있습니까?

A A True 
A B False 
A C False 
... 10 more lines ... 
D B False 
D C False 
D D True 

다음 코드는 작업을 수행해야하는 것처럼 보입니다.

class C(): 
    def __init__(self): 
     self.stuff = ["A","B","C","D"] 
    def __iter__(self): 
     self.idx = 0 
     return self 
    def __next__(self): 
     self.idx += 1 
     if self.idx > len(self.stuff): 
      raise StopIteration 
     else: 
      return self.stuff[self.idx - 1] 

thing = C() 
for x in thing: 
    for y in thing: 
     print(x, y, x==y) 

그러나 Y-루프를 마친 후

는 X-루프는 그것은 단지 반복자의 첫 번째 항목을 사용하여, 비록도 다 보인다.
A A True 
A B False 
A C False 
A D False 

많이 검색 한 후, 나는 결국, 다음 코드를 시도 itertools.tee 같은 데이터를 통해 나에게 두 개의 독립적 인 반복자를 허용 할 것으로 기대 :

import itertools 
thing = C() 
thing_one, thing_two = itertools.tee(thing) 
for x in thing_one: 
    for y in thing_two: 
     print(x, y, x==y) 

을하지만 이전과 같은 결과를 얻었다.

이 실제 개체는 다양한 수의 파일과 하위 디렉터리가 트리의 다양한 깊이에있는 디렉터리 및 파일 구조의 모델입니다. 이 예제와 마찬가지로 수천 개의 멤버에 대한 링크를 중첩하여 올바르게 반복합니다. 하지만 반복 작업을하기 전에 전체 복사본을 만들어야 할 경우 작업 부하를 두 배로 늘리는 비교를 위해 필요에 따라 즉석에서 많은 내부 개체 내에서 값 비싼 처리를 수행합니다. 가능한 한 모든 데이터가 포함 된 단일 개체를 가리키는 여러 개의 반복자를 사용하고 싶습니다. 답변에


편집 : 질문 코드의 중요한 결함은, 모든 대답에 지적 독립적으로 여러 발신자를 처리 할 수 ​​없습니다되는 하나의 내부 self.idx 변수입니다. 받아 들인 대답은 내 실제 수업 (이 재현 가능한 예에서는 지나치게 단순화)에 가장 적합하며, 또 다른 대답은 여기에 제시된 목록과 같은 간단한 데이터 구조를위한 간단하고 우아한 해결책을 제시합니다.

+0

그런 소스를 사용하여 iterables를 작성하도록 가르쳤다면 사용을 중지하십시오. – user2357112

+0

개체를 인덱싱 할 수 있습니까? 그것은'__len__' 메소드를 가지고 있습니까? –

+0

이것은 본질적으로 중첩 된 디렉토리와 파일 구조를 표현하기 때문에 여러 레벨에서 하나의 인덱스가 필요하지 않습니다. 그러나 노드의 전체 개수를 가지고 있으므로 쉽게 __len__을 작성할 수 있습니다. – mightypile

답변

1

실제로 iterator 인 컨테이너 클래스를 만드는 것은 실제로 불가능합니다. 컨테이너는 반복자의 상태를 알지 않아야하며 반복자는 컨테이너의 내용을 알 필요가 없으며 해당 객체가 해당 컨테이너이고 "어디"인지 알 필요가 있습니다. 반복자와 컨테이너를 섞으면 서로 다른 반복자가 정확한 결과를주지 못하는 서로 다른 상태 (예 : self.idx)를 공유합니다 (동일한 변수를 읽고 수정 함).

>>> l = [1, 2, 3] 
>>> iter(l) 
<list_iterator at 0x15e360c86d8> 
>>> reversed(l) 
<list_reverseiterator at 0x15e360a5940> 

>>> t = (1, 2, 3) 
>>> iter(t) 
<tuple_iterator at 0x15e363fb320> 

>>> s = '123' 
>>> iter(s) 
<str_iterator at 0x15e363fb438> 

그래서, 기본적으로 당신은 단지 __iter__iter(self.stuff)를 반환하고 드롭 수 : 모든 내장 왜 종류의 별도의 반복자 클래스가 (심지어 일부는 역 반복자 클래스가) 이유입니다

class C: 
    def __init__(self): 
     self.stuff = ["A","B","C","D"] 
    def __iter__(self): 
     return iter(self.stuff) 

thing = C() 
for x in thing: 
    for y in thing: 
     print(x, y, x==y) 

인쇄 (16 개) 라인을 예상처럼 : __next__ 모두 때문에 list_iterator 목록을 반복하는 방법을 알고있다.

자신 만의 반복기 클래스를 만드는 것이 목표라면 두 개의 클래스가 필요합니다. 역 반복기를 직접 구현하려면 3 개가 필요합니다.

class C: 
    def __init__(self): 
     self.stuff = ["A","B","C","D"] 
    def __iter__(self): 
     return C_iterator(self) 
    def __reversed__(self): 
     return C_reversed_iterator(self) 

class C_iterator: 
    def __init__(self, parent): 
     self.idx = 0 
     self.parent = parent 
    def __iter__(self): 
     return self 
    def __next__(self): 
     self.idx += 1 
     if self.idx > len(self.parent.stuff): 
      raise StopIteration 
     else: 
      return self.parent.stuff[self.idx - 1] 

thing = C() 
for x in thing: 
    for y in thing: 
     print(x, y, x==y) 

도 마찬가지입니다. 대신 당신이 발전기를 사용할 수있는 자신 만의 반복자를 정의

class C_reversed_iterator: 
    def __init__(self, parent): 
     self.parent = parent 
     self.idx = len(parent.stuff) + 1 
    def __iter__(self): 
     return self 
    def __next__(self): 
     self.idx -= 1 
     if self.idx <= 0: 
      raise StopIteration 
     else: 
      return self.parent.stuff[self.idx - 1] 

thing = C() 
for x in reversed(thing): 
    for y in reversed(thing): 
     print(x, y, x==y) 

:

완성도를 들어, 다음은 역 반복자의 하나의 가능한 구현이다. 한 가지 방법은 이미 다른 대답에 나타내었다 명시 적으로

class C: 
    def __init__(self): 
     self.stuff = ["A","B","C","D"] 
    def __iter__(self): 
     yield from self.stuff 
    def __reversed__(self): 
     yield from self.stuff[::-1] 

또는 발전기의 기능에 위임 (즉, 실제로 상당의 위이지만 생산되는 새로운 객체 있다고 어쩌면 더 명확) :

def C_iterator(obj): 
    for item in obj.stuff: 
     yield item 

def C_reverse_iterator(obj): 
    for item in obj.stuff[::-1]: 
     yield item 

class C: 
    def __init__(self): 
     self.stuff = ["A","B","C","D"] 
    def __iter__(self): 
     return C_iterator(self) 
    def __reversed__(self): 
     return C_reverse_iterator(self) 

참고 : __reversed__ 반복기를 구현할 필요가 없습니다. 그것은 단지 대답의 추가 "특징"을 의미합니다.

+0

필자는 필자의 실제 클래스가 호출자에게 불투명 한 여러 목록을 차례로 반복해야하므로 필자가 직접 반복기를 구현해야한다고 확신합니다. 나는 여전히 배우고 있지만, 수율과 같은 발전기가 나의 경우를 다룰 것이라면 놀랄 것이다. 이 대답은 내가 있어야 할 곳에 나를 데려 오는 데있어 많은 근거를 다룹니다. 건배! – mightypile

+1

@mightypile : 여러 목록을 차례로 반복해서 말하십시오. [Hmm ...] (https://docs.python.org/3/library/itertools.html#itertools.chain) – ShadowRanger

1

__iter__은 완전히 고장났습니다. 실제로 모든 호출에 대해 반복자를 작성하는 대신, 일부 상태를 self에 다시 설정하고 self을 반환합니다. 즉, 객체에 대해 한 번에 두 개 이상의 반복자를 사용할 수없고 객체에 대한 다른 루프가 활성화되어있는 동안 __iter__에 대한 호출이 활성화되면 기존 루프를 방해합니다.

실제로 새 개체를 만들어야합니다. 가장 간단한 방법은 yield 구문을 사용하여 생성기 함수를 작성하는 것입니다. 생성기 함수는 매번 새로운 iterator 객체를 자동으로 반환합니다 :

+1

일반 규칙 :'__next__'를 정의하면,'__iter__' *는 반드시 identity 함수 (* nothing *하지만'return self')이어야합니다. 그렇지 않은 경우 코드는 * wrong *입니다. 그리고 일반적으로 iterator 클래스를 직접 구현하고 싶지 않으므로 여기에서 보여준 것처럼'__iter__'을 생성 함수로 만들고'__next__'를 완전히 구현하지 않아야합니다. 생성기 함수 접근법은 더 빠릅니다 (파이썬이 생성자 상태를 관리하면 훨씬 효율적으로 수행 할 수 있습니다). 그리고 유형에 대한 별도의 반복자 클래스를 정의하는 것보다 간단합니다. – ShadowRanger

+0

일반적인 규칙과 마찬가지로 항복 방향이 도움이됩니다. 그들이 내 사용을 위해 작동하는지 알아 내야합니다. 객체 내에서 계층 구조의 여러 단계를 반복하는 경우입니다. 감사합니다. – mightypile