파이썬

[Python] "yield" 키워드는 무엇을 합니까?

zooheon 2022. 7. 31. 13:17
반응형

yield

파이썬에서 키워드 의 용도는 무엇입니까 ? 그것은 무엇을합니까?

예를 들어, 이 코드 1

을 이해하려고 합니다 .
def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  
그리고 이것은 호출자입니다:
result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

_get_child_candidates

메서드 가 호출 되면 어떻게 됩니까 ? 목록이 반환됩니까? 단일 요소? 또 불려? 후속 호출은 언제 중지됩니까?
1. 이 코드는 미터법 공간을 위한 훌륭한 Python 라이브러리를 만든 Jochen Schulz(jrschulz)가 작성했습니다. 이것은 전체 소스에 대한 링크입니다: Module mspace .

 

무슨 역할 을 하는지 이해하려면 생성기

yield

가 무엇인지 이해해야 합니다 . 그리고 생성자를 이해하기 전에 iterables 를 이해해야 합니다 .

반복 가능

목록을 만들 때 항목을 하나씩 읽을 수 있습니다. 항목을 하나씩 읽는 것을 반복이라고 합니다.
>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylist

iterable

입니다. 목록 이해를 사용할 때 목록을 만들고 반복 가능합니다.
>>> 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

()

대신 를 사용한 것을 제외하고는 동일합니다

[]

. 그러나 생성기는 한 번만 사용할 수 있으므로 두 번째 수행 할

수 없습니다 . 생성기는 0을 계산한 다음 잊어버리고 1을 계산하고 4를 하나씩 계산합니다.

for i in mygenerator

생산하다

yield

return

함수가 생성기를 반환한다는 점을 제외하고는 와 같이 사용되는 키워드입니다 .
>>> def create_generator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = create_generator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object create_generator 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]
그러나 코드에서는 다음과 같은 이유로 좋은 생성기를 얻습니다.
  1. 값을 두 번 읽을 필요가 없습니다.
  2. 자녀가 많을 수 있으며 모든 자녀를 메모리에 저장하고 싶지 않을 수 있습니다.
그리고 파이썬은 메소드의 인자가 리스트인지 아닌지 상관하지 않기 때문에 작동합니다. Python은 iterable을 기대하므로 문자열, 목록, 튜플 및 생성기와 함께 작동합니다! 이것을 덕 타이핑이라고 하며 파이썬이 멋진 이유 중 하나입니다. 그러나 이것은 또 다른 이야기입니다. 다른 질문에 대해...여기에서 멈추거나 생성기의 고급 사용법을 보려면 조금 읽을 수 있습니다.

발전기 고갈 제어

>>> 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을 조작하는 특수 함수가 포함되어 있습니다. 발전기를 복제하고 싶으신가요? 체인 2개의 발전기? 한 줄짜리 중첩 목록의 값을 그룹화하시겠습니까?

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)]

반복의 내부 메커니즘 이해

반복은 iterables(

__iter__()

메소드 구현) 및 iterators(메소드 구현)를 의미하는 프로세스

__next__()

입니다. Iterables는 iterator를 얻을 수 있는 모든 객체입니다. Iterator는 iterable을 반복할 수 있는 객체입니다.루프 작동

방식for

에 대한 자세한 내용은 이 기사에 있습니다 .

 

이해의 지름길yield

문이 포함 된 함수를 보면 다음과

yield

같은 쉬운 트릭을 적용하여 어떤 일이 일어날지 이해하십시오.
  1. result = []함수 시작 부분에 줄을 삽입 합니다.
  2. yield expr각각 을 로 바꿉니다 result.append(expr).
  3. return result함수의 맨 아래에 줄을 삽입 합니다.
  4. 예 - 더 이상 yield진술이 없습니다! 코드를 읽고 파악합니다.
  5. 기능을 원래 정의와 비교하십시오.
이 트릭은 함수 이면의 논리에 대한 아이디어를 제공할 수 있지만 실제로 발생하는

yield

일은 목록 기반 접근 방식에서 발생하는 것과 크게 다릅니다. 많은 경우에 yield 접근 방식은 훨씬 더 메모리 효율적이고 빠릅니다. 다른 경우에는 원래 기능이 제대로 작동하더라도 이 트릭을 사용하면 무한 루프에 빠지게 됩니다. 자세히 알아보려면 계속 읽으십시오...

Iterable, Iterator 및 Generator를 혼동하지 마십시오.

첫째,

반복자 프로토콜

- 작성할 때
for x in mylist:
    ...loop body...
Python은 다음 두 단계를 수행합니다.
  1. 다음 에 대한 반복자를 가져옵니다
    mylist
    .
    [이것은 대부분의 사람들이 당신에게 말하는 것을 잊는 단계입니다]
  2. 호출
    iter(mylist)
    -> 메서드가 있는 객체를 반환합니다
    next()
    (또는
    __next__()
    Python 3에서).
  3. 반복자를 사용하여 항목을 반복합니다.
  4.  
    next()
    1단계에서 반환된 반복자에서 메서드를 계속 호출합니다 . 반환 값 from
    next()
    이 할당되고
    x
    루프 본문이 실행됩니다. 내부에서 예외
    StopIteration
    가 발생
    next()
    하면 반복자에 더 이상 값이 없고 루프가 종료됨을 의미합니다.

진실은 파이썬이 객체의 내용을 반복

하고 싶을 때마다 위의 두 단계를 수행한다는 것 입니다. 따라서 for 루프가 될 수 있지만 다음과 같은 코드가 될 수도 있습니다

otherlist.extend(mylist)

(여기서

otherlist

는 파이썬 목록).다음

mylist

iterator 프로토콜을 구현하기 때문에

iterable 입니다.

__iter__()

사용자 정의 클래스에서 클래스 의 인스턴스를 반복 가능하게 만드는 메서드를 구현할 수 있습니다 . 이 메서드는

iterator

를 반환해야 합니다 . 반복자는 메서드가 있는 개체입니다

next()

. 동일한 클래스에 둘 다 구현

__iter__()

하고 return 을 가질 수 있습니다 . 이것은 간단한 경우에는 작동하지만 동일한 객체에 대해 두 개의 반복자를 동시에 반복하려는 경우에는 작동하지 않습니다.

next()

__iter__()

self

이것이 반복자 프로토콜이며 많은 객체가 이 프로토콜을 구현합니다.
  1. 내장 목록, 사전, 튜플, 집합, 파일.
  2. 를 구현하는 사용자 정의 클래스 __iter__().
  3. 발전기.
루프 는

for

어떤 종류의 객체를 다루고 있는지 알지 못합니다. 반복자 프로토콜을 따르고 호출할 때 항목 다음에 항목을 가져오는 것을 기쁘게 생각합니다

next()

. 기본 제공 목록은 항목을 하나씩 반환하고 사전은

를 하나씩 반환하고 파일은

을 하나씩 반환합니다. 그리고 생성기는 반환합니다

yield

.
def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item
명령문 대신에

yield

세 개의

return

명령문이 있으면 첫 번째 명령문

f123()

만 실행되고 함수가 종료됩니다. 그러나

f123()

일반적인 기능이 아닙니다.

f123()

가 호출 되면 yield 문의 값을 반환

하지 않습니다 !

생성자 객체를 반환합니다. 또한 함수는 실제로 종료되지 않고 일시 중단된 상태가 됩니다.

for

루프가 생성기 객체에 대해 루프를 시도 할 때 함수는

yield

이전에 반환된 바로 다음 줄에서 일시 중단된 상태에서 다시 시작하고 다음 코드 줄(이 경우

yield

명령문)을 실행하여 다음 줄로 반환합니다. 안건. 이것은 함수가 종료될 때까지 발생하며, 이 지점에서 생성기가 를 발생

StopIteration

시키고 루프가 종료됩니다.그래서 생성기 객체는 일종의 어댑터와 같습니다. 한쪽 끝 에서 루프를 만족스럽게 유지하기 위해

__iter__()

next()

메서드를 노출하여 반복자 프로토콜을 나타냅니다.

for

그러나 다른 쪽 끝에서는 다음 값을 가져올 만큼만 함수를 실행하고 일시 중단 모드로 되돌립니다.

제너레이터를 사용하는 이유

일반적으로 생성기를 사용하지 않고 동일한 논리를 구현하는 코드를 작성할 수 있습니다. 한 가지 옵션은 앞에서 언급한 임시 목록 '트릭'을 사용하는 것입니다. 예를 들어 무한 루프가 있거나 정말 긴 목록이 있을 때 메모리를 비효율적으로 사용할 수 있는 경우와 같이 모든 경우에 작동하지는 않습니다.

next()

다른 접근 방식은 인스턴스 멤버의 상태를 유지하고 해당 (또는

__next__()

Python 3) 메서드에서 다음 논리적 단계를 수행하는 새로운 반복 가능한 클래스 SomethingIter를 구현하는 것입니다 . 논리에 따라

next()

메서드 내부의 코드가 매우 복잡해 보이고 버그가 발생하기 쉽습니다. 여기에서 발전기는 깨끗하고 쉬운 솔루션을 제공합니다.

 

다음과 같이 생각하십시오.

next()

반복자는 메서드 가 있는 개체에 대한 멋진 용어입니다 . 따라서 yield-ed 함수는 다음과 같이 됩니다.원본 버전:
def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i
이것은 기본적으로 Python 인터프리터가 위의 코드로 수행하는 작업입니다.
class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i
무대 뒤에서 무슨 일이 일어나고 있는지에 대한 더 많은 통찰력을 얻으려면

for

루프를 다음과 같이 다시 작성할 수 있습니다.
iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass
그게 더 이해가 됩니까 아니면 더 혼란스럽습니까? :)

이것은 설명 을

위해 지나치게 단순화한 것임에 유의해야 합니다. :)

 

반응형