무엇을 이해하려면 생성기 가 yield
무엇인지 이해해야합니다 . 그리고 생성자를 이해하기 전에 반복 가능한 것을 이해해야합니다 .
이터 러블
목록을 만들면 항목을 하나씩 읽을 수 있습니다. 항목을 하나씩 읽는 것을 반복이라고합니다.
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
입니다 반복 가능한 . 리스트 이해를 사용하면리스트를 작성하므로 반복 가능합니다.
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
" for... in...
"를 사용할 수있는 모든 것은 반복 가능합니다. lists
,, strings
파일 ...
이 iterable은 원하는만큼 읽을 수 있기 때문에 편리하지만 모든 값을 메모리에 저장하므로 많은 값을 가질 때 항상 원하는 것은 아닙니다.
발전기
제너레이터는 반복자입니다. 반복자는 한 번만 반복 할 수 있습니다 . 생성기는 메모리에 모든 값을 저장하지 않고 값을 즉시 생성합니다 .
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
()
대신에 사용한 것을 제외하고는 동일합니다 []
. 그러나 발전기는 한 번만 사용할 수 있으므로 두 번째로 수행 할 수 없습니다. 제로는 for i in mygenerator
0을 계산 한 다음 잊어 버리고 1을 계산하고 4를 하나씩 계산하지 않습니다.
수율
yield
이 return
함수는 생성기를 반환한다는 점을 제외하고 와 같이 사용되는 키워드입니다 .
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
여기서는 쓸모없는 예이지만 함수가 한 번만 읽어야 할 거대한 값 집합을 반환한다는 것을 알면 편리합니다.
마스터하려면 함수를 호출 할 때 함수 본문에 작성한 코드가 실행되지 않는다는yield
것을 이해해야 합니다. 이 함수는 생성기 객체 만 반환합니다.
그런 다음 for
생성기를 사용할 때 마다 중단 된 지점부터 코드가 계속됩니다 .
이제 어려운 부분 :
for
함수에서 생성 된 생성기 객체를 처음 호출하면 처음부터 치기까지 함수에서 코드를 실행 yield
한 다음 루프의 첫 번째 값을 반환합니다. 그런 다음 각 호출은 함수에 작성한 루프의 다른 반복을 실행하고 다음 값을 반환합니다. 이것은 생성기가 비어있는 것으로 간주 될 때까지 계속됩니다 yield
. 루프가 종료되었거나 더 이상을 만족하지 않기 때문일 수 있습니다 "if/else"
.
코드 설명
발전기:
# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):
# Here is the code that will be called each time you use the generator object:
# If there is still a child of the node object on its left
# AND if the distance is ok, return the next child
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
# If there is still a child of the node object on its right
# AND if the distance is ok, return the next child
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
# If the function arrives here, the generator will be considered empty
# there is no more than two values: the left and the right children
방문객:
# Create an empty list and a list with the current object reference
result, candidates = list(), [self]
# Loop on candidates (they contain only one element at the beginning)
while candidates:
# Get the last candidate and remove it from the list
node = candidates.pop()
# Get the distance between obj and the candidate
distance = node._get_dist(obj)
# If distance is ok, then you can fill the result
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
# Add the children of the candidate in the candidate's list
# so the loop will keep running until it will have looked
# at all the children of the children of the children, etc. of the candidate
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
이 코드에는 여러 가지 스마트 한 부분이 포함되어 있습니다.
루프는 목록에서 반복되지만 루프가 반복되는 동안 목록이 확장됩니다. 이 경우 candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
생성기의 모든 값을 소진하지만 while
동일한 노드에 적용되지 않으므로 이전 생성기와 다른 값을 생성하는 새 생성기 객체를 계속 생성합니다.
이 extend()
메소드는 iterable을 예상하고 해당 값을 목록에 추가하는리스트 오브젝트 메소드입니다.
일반적으로 목록을 전달합니다.
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
그러나 코드에서 생성기를 얻습니다.
- 값을 두 번 읽을 필요는 없습니다.
- 자녀가 많을 수 있으며 자녀가 모두 메모리에 저장되는 것을 원하지 않습니다.
그리고 파이썬은 메소드의 인수가 목록인지 아닌지 신경 쓰지 않기 때문에 작동합니다. 파이썬은 이터 러블을 기대하므로 문자열,리스트, 튜플, 생성기에서 작동합니다! 이것을 오리 타이핑이라고하며 파이썬이 그렇게 멋진 이유 중 하나입니다. 그러나 이것은 또 다른 질문입니다. 또 다른 질문입니다 ...
여기에서 멈추거나 약간만 읽으면 발전기의 고급 사용법을 볼 수 있습니다.
발전기 피로 제어
>>> class Bank(): # Let's create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
참고 : Python 3의 경우 print(corner_street_atm.__next__())
또는print(next(corner_street_atm))
리소스에 대한 액세스 제어와 같은 다양한 작업에 유용 할 수 있습니다.
Itertools, 가장 친한 친구
itertools 모듈에는 iterable을 조작하는 특수 함수가 포함되어 있습니다. 발전기를 복제하고 싶습니까? 두 발전기를 연결? 하나의 라이너로 중첩 목록의 값을 그룹화합니까? Map / Zip
다른 목록을 만들지 않고?
그럼 그냥 import itertools
.
예를 들어? 4 마리 경주에 가능한 도착 순서를 보자.
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
(1, 2, 4, 3),
(1, 3, 2, 4),
(1, 3, 4, 2),
(1, 4, 2, 3),
(1, 4, 3, 2),
(2, 1, 3, 4),
(2, 1, 4, 3),
(2, 3, 1, 4),
(2, 3, 4, 1),
(2, 4, 1, 3),
(2, 4, 3, 1),
(3, 1, 2, 4),
(3, 1, 4, 2),
(3, 2, 1, 4),
(3, 2, 4, 1),
(3, 4, 1, 2),
(3, 4, 2, 1),
(4, 1, 2, 3),
(4, 1, 3, 2),
(4, 2, 1, 3),
(4, 2, 3, 1),
(4, 3, 1, 2),
(4, 3, 2, 1)]
반복의 내부 메커니즘 이해
반복은 이터 러블 ( __iter__()
방법 구현 )과 반복자 (방법 구현)를 암시하는 프로세스 __next__()
입니다. 이터 러블은 이터레이터를 얻을 수있는 객체입니다. 이터레이터는 이터 러블을 반복 할 수있는 객체입니다.
이 기사에는 루프 작동 방식for
에 대한 자세한 내용이 있습니다 .
yield
이 답변이 제안하는 것처럼 마법이 아닙니다.yield
어디서나 명령문 이 포함 된 함수를 호출하면 생성기 객체가 생성되지만 코드는 실행되지 않습니다. 그런 다음 생성기에서 객체를 추출 할 때마다 Python은yield
명령문에 도달 할 때까지 함수에서 코드를 실행 한 다음 객체를 일시 중지하고 전달합니다. 다른 객체를 추출하면 파이썬은 바로 다음에 재개되어 다른 객체에yield
도달 할 때까지 계속 됩니다yield
(종종 동일한 객체 이지만 나중에 반복). 이것은 기능이 끝날 때까지 계속되며,이 시점에서 발전기가 소진 된 것으로 간주됩니다.