스택큐힙리스트

파이썬에서 "yield" 키워드는 어떤 용도로 사용되나요? 본문

카테고리 없음

파이썬에서 "yield" 키워드는 어떤 용도로 사용되나요?

스택큐힙리스트 2023. 2. 28. 11:17
반응형

파이썬에서 yield 키워드는 어떤 용도로 사용되나요? 어떤 기능을 하나요?

예를 들어, 다음 코드1를 이해하려고 합니다:

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild와 distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild와 distance + max_dist >= self._median:
        yield self._rightchild  
그리고 이것이 호출자입니다:

결과, 후보 = [], [self]
동안 후보자:
    노드 = 후보자.팝()
    distance = node._get_dist(obj)
    거리 <= max_dist 및 거리 >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
결과 반환
get_child_candidates 메서드를 호출하면 어떻게 되나요? 리스트가 반환되나요? 단일 요소가 반환되나요? 다시 호출되나요? 후속 호출은 언제 중단되나요?

 

 

파이썬에서 "yield" 키워드 이해하기: 초보자 가이드

파이썬에서 "yield" 키워드는 제너레이터를 생성하는 데 사용됩니다. 제너레이터는 리스트나 튜플과 같은 이터러블의 일종이지만, 값을 메모리에 저장하지 않고 즉석에서 생성합니다. 따라서 대규모 데이터 세트로 작업하거나 사용자 정의 이터레이터를 만드는 데 강력한 도구입니다.

"yield" 작동 방식

함수에 "yield" 키워드가 포함되어 있으면 제너레이터 함수가 됩니다. 제너레이터 함수는 값을 반환하는 대신, 호출될 때마다 한 번에 하나씩 값을 산출합니다. 함수의 상태가 저장되므로 다음에 함수를 호출할 때 처음부터 다시 시작하지 않고 중단한 부분부터 다시 시작합니다.

다음은 예제입니다:

python
코드 복사
def count_up_to(n):
    i = 1
    동안 i <= n:
        yield i
        i += 1
이 제너레이터 함수는 주어진 숫자까지 카운트하고 각 숫자를 산출합니다. 제너레이터를 사용하려면 다음과 같이 함수를 호출하기만 하면 됩니다:

scss
코드 복사
for num in count_up_to(5):
    print(num)
이렇게 출력됩니다:

코드 복사
1
2
3
4
5
"yield" 사용의 장점

"yield"을 사용하여 제너레이터를 생성하면 데이터를 반복하는 다른 방법보다 몇 가지 장점이 있습니다. 첫째, 제너레이터는 값을 모두 메모리에 저장하지 않고 필요한 만큼만 생성하므로 메모리 효율이 높습니다. 따라서 대규모 데이터 세트 작업에 적합합니다.

둘째, 제너레이터는 사용하기 쉬우며 루프 및 목록 이해와 같은 다른 Python 도구와 원활하게 통합할 수 있습니다. 또한 제너레이터를 결합하여 더 복잡한 반복기를 만들 수 있으므로 데이터에 대한 반복 방법을 사용자 정의하는 데 강력한 도구가 될 수 있습니다.

결론

"yield" 키워드는 파이썬에서 제너레이터로 작업하는 데 필수적인 도구입니다. 제너레이터를 생성하면 대규모 데이터 세트로 효율적으로 작업하고 필요에 맞게 반복자를 사용자 정의할 수 있습니다. Python을 처음 사용하거나 "yield"의 작동 방식에 대해 자세히 알고 싶다면 직접 제너레이터 함수를 만들어 코드를 간소화하는 방법을 알아보세요.

 

 

 

 

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
리스트, 문자열, 파일 등 "...에..."를 사용할 수 있는 모든 것은 이터러블입니다.

이러한 이터러블은 원하는 만큼 읽을 수 있기 때문에 편리하지만 모든 값을 메모리에 저장하므로 값이 많을 때 항상 원하는 것은 아닙니다.

제너레이터
제너레이터는 한 번만 반복할 수 있는 이터러블의 일종인 이터레이터입니다. 제너레이터는 모든 값을 메모리에 저장하지 않고 즉석에서 값을 생성합니다:

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
[] 대신 ()를 사용했다는 점을 제외하면 동일합니다. 그러나 제너레이터는 한 번만 사용할 수 있으므로 내 제너레이터에서 i에 대해 두 번 수행 할 수 없습니다. 0을 계산 한 다음 잊어 버리고 1을 계산하고 4 계산을 끝내고 하나씩 계산합니다.

Yield
yield는 반환과 비슷하게 사용되는 키워드이지만 함수가 제너레이터를 반환한다는 점이 다릅니다.

>>> def create_generator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = create_generator() # 생성기 생성
>>> print(mygenerator) # mygenerator는 객체입니다!
<제너레이터 객체 create_generator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
여기서는 쓸모없는 예시이지만 함수가 한 번만 읽으면 되는 방대한 값 집합을 반환한다는 것을 알고 있을 때 유용합니다.

Yield를 마스터하려면 함수를 호출할 때 함수 본문에서 작성한 코드가 실행되지 않는다는 점을 이해해야 합니다. 함수는 제너레이터 객체만 반환하므로 약간 까다롭습니다.

그러면 제너레이터를 사용할 때마다 코드가 중단된 지점부터 계속됩니다.

이제 어려운 부분입니다:

for가 함수에서 생성된 제너레이터 객체를 처음 호출할 때, 함수의 코드를 처음부터 yield에 도달할 때까지 실행한 다음 루프의 첫 번째 값을 반환합니다. 이후 호출할 때마다 함수에 작성한 루프의 또 다른 반복을 실행하고 다음 값을 반환합니다. 이 과정은 제너레이터가 비어 있는 것으로 간주될 때까지 계속되며, 이는 함수가 수율에 도달하지 않고 실행될 때 발생합니다. 이는 루프가 종료되었거나 더 이상 "if/else"를 만족하지 않기 때문일 수 있습니다.

 

코드 설명
제너레이터:

생성기: # 여기서 생성기를 반환할 노드 객체의 메서드를 생성합니다.
def _get_child_candidates(self, distance, min_dist, max_dist):

    # 다음은 제너레이터 객체를 사용할 때마다 호출될 코드입니다:

    # 왼쪽에 노드 오브젝트의 자식이 남아있다면
    # 거리가 괜찮으면 다음 자식을 반환합니다.
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # 오른쪽에 노드 객체의 자식이 여전히 있는 경우
    # 거리가 괜찮으면 다음 자식을 반환합니다.
    self._rightchild와 distance + max_dist >= self._median이면 다음 자식을 반환합니다:
        yield self._rightchild

    # 함수가 여기에 도달하면 제너레이터는 비어있는 것으로 간주됩니다.
    # 왼쪽과 오른쪽 자식 두 개 이상의 값이 없습니다.
호출자:

# 빈 리스트와 현재 객체 참조가 있는 리스트를 생성합니다.
결과, 후보자 = list(), [self]

# 후보에 대해 반복합니다 (처음에 하나의 요소만 포함).
동안 반복합니다:

    # 마지막 후보를 가져와 목록에서 제거합니다.
    node = candidates.pop()

    # 객체와 후보 사이의 거리를 구합니다.
    distance = node._get_dist(obj)

    # 거리가 괜찮으면 결과를 채울 수 있습니다.
    거리 <= max_dist, 거리 >= min_dist:
        result.extend(node._values)

    # 후보의 자식들을 후보 목록에 추가합니다.
    # 루프가 모든 자식을 찾을 때까지 계속 실행됩니다.
    # 후보의 자식 자식 자식 등의 모든 자식을 찾을 때까지 루프가 계속 실행됩니다.
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

결과 반환
이 코드에는 몇 가지 스마트한 부분이 포함되어 있습니다:

루프가 목록을 반복하지만 루프가 반복되는 동안 목록이 확장됩니다. 무한 루프로 끝날 수 있기 때문에 약간 위험하더라도 중첩된 데이터를 모두 살펴볼 수 있는 간결한 방법입니다. 이 경우 candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))는 제너레이터의 모든 값을 소진하지만, 동일한 노드에 적용되지 않기 때문에 이전 값과 다른 값을 생성하는 새로운 제너레이터 객체를 계속 생성합니다.

extend() 메서드는 이터러블을 예상하고 그 값을 목록에 추가하는 목록 객체 메서드입니다.

일반적으로 목록을 전달합니다:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
하지만 코드에서는 제너레이터가 생성되므로 좋습니다:

값을 두 번 읽을 필요가 없습니다.
자식이 많은데 모두 메모리에 저장하고 싶지 않을 수도 있습니다.
그리고 파이썬은 메서드의 인자가 리스트인지 아닌지를 신경 쓰지 않기 때문에 작동합니다. 파이썬은 이터러블을 기대하므로 문자열, 리스트, 튜플, 제너레이터와 함께 작동합니다! 이를 duck typing이라고 하며 파이썬이 멋진 이유 중 하나입니다. 하지만 이것은 다른 질문의 다른 이야기입니다...

 

 

여기서 멈추거나 제너레이터의 고급 사용법을 조금 더 읽어보실 수 있습니다:

제너레이터 소진 제어하기
>>> 클래스 Bank(): # ATM을 만드는 은행을 만들어 봅시다.
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # 모든 것이 정상이면 ATM은 원하는 만큼 돈을 줍니다.
>>> 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 # 위기가 다가오고 있습니다, 더 이상 돈이 없습니다!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # 새 ATM에도 마찬가지입니다.
>>> print(wall_street_atm.next())
<유형 '예외.StopIteration'> >>>
>>> hsbc.crisis = False # 문제는 위기 이후에도 ATM이 비어 있다는 것입니다.
>>> print(corner_street_atm.next())
<유형 '예외.StopIteration'> > >>>
>>> brand_new_atm = hsbc.create_atm() # 영업 재개를 위해 새 ATM을 구축합니다.
brand_new_atm에서 현금을 >>>:
... 현금 인쇄
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
참고: Python 3의 경우, useprint(corner_street_atm.__next__()) 또는 print(next(corner_street_atm))

리소스에 대한 접근을 제어하는 등 다양한 용도로 유용하게 사용할 수 있습니다.

가장 친한 친구, 이터툴즈
itertools 모듈에는 이터러블을 조작하는 특수 함수가 포함되어 있습니다. 제너레이터를 복제하고 싶으신가요? 두 개의 제너레이터를 연결하고 싶으신가요? 한 줄로 중첩된 목록의 값을 그룹화하고 싶으신가요? 다른 목록을 만들지 않고 매핑/압축하고 싶으신가요?

그렇다면 이터툴을 가져오기만 하면 됩니다.

예를 들어볼까요? 네 마리의 말이 경주를 할 때 가능한 도착 순서를 살펴봅시다:

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations 객체 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__() 메서드를 구현)를 암시하는 프로세스입니다. 이터러블은 이터레이터를 가져올 수 있는 모든 객체입니다. 이터레이터는 이터러블을 반복할 수 있는 객체입니다.

이 문서에서 반복자 작동 방식에 대한 자세한 내용을 확인할 수 있습니다.

반응형
Comments