제너레이터를 호출하는 함수에서 리턴 또는 수율?


30

발전기 generator와 편리한 방법이 있습니다 generate_all.

def generator(some_list):
  for i in some_list:
    yield do_something(i)

def generate_all():
  some_list = get_the_list()
  return generator(some_list) # <-- Is this supposed to be return or yield?

할까요 generate_all return또는 yield? 두 방법을 사용하는 사용자가 동일하게 사용하기를 원합니다. 즉

for x in generate_all()

와 같아야한다

some_list = get_the_list()
for x in generate(some_list)

2
둘 중 하나를 사용해야 할 이유가 있습니다. 이 예에서는 귀환이 더 효율적입니다
Mad Physicist

1
이것은 내가 한 번 제기 비슷한 질문을 생각 나게 : "반복 가능한에서 수율을" "ITER (반복자)을 반환"대 . 생성기에 대해서는 구체적으로 설명하지 않지만 기본적으로 생성기와 동일하며 반복기는 파이썬에서 매우 유사합니다. 또한 대답에서 제안한 바이트 코드를 비교하는 전략이 여기에서 사용될 수 있습니다.
PeterE

답변:


12

발전기는-게으른 평가 이렇게 있습니다 return또는 yield예외가 발생하는 경우 당신은 당신의 코드를 디버깅 할 때 다르게 행동이나됩니다.

return당신에 발생하는 예외 generator에 대해 아무것도 알 수 없습니다 generate_all때 때문입니다, generator정말 실행 이미 남아있는 generate_all기능을. 함께 yield거기가있을 것이다 generate_all역 추적에.

def generator(some_list):
    for i in some_list:
        raise Exception('exception happened :-)')
        yield i

def generate_all():
    some_list = [1,2,3]
    return generator(some_list)

for item in generate_all():
    ...
Exception                                 Traceback (most recent call last)
<ipython-input-3-b19085eab3e1> in <module>
      8     return generator(some_list)
      9 
---> 10 for item in generate_all():
     11     ...

<ipython-input-3-b19085eab3e1> in generator(some_list)
      1 def generator(some_list):
      2     for i in some_list:
----> 3         raise Exception('exception happened :-)')
      4         yield i
      5 

Exception: exception happened :-)

그리고 그것을 사용한다면 yield from:

def generate_all():
    some_list = [1,2,3]
    yield from generator(some_list)

for item in generate_all():
    ...
Exception                                 Traceback (most recent call last)
<ipython-input-4-be322887df35> in <module>
      8     yield from generator(some_list)
      9 
---> 10 for item in generate_all():
     11     ...

<ipython-input-4-be322887df35> in generate_all()
      6 def generate_all():
      7     some_list = [1,2,3]
----> 8     yield from generator(some_list)
      9 
     10 for item in generate_all():

<ipython-input-4-be322887df35> in generator(some_list)
      1 def generator(some_list):
      2     for i in some_list:
----> 3         raise Exception('exception happened :-)')
      4         yield i
      5 

Exception: exception happened :-)

그러나 이는 성능 저하를 초래합니다. 추가 생성기 레이어에는 약간의 오버 헤드가 있습니다. 따라서 return일반적으로 yield from ...(또는 for item in ...: yield item) 보다 약간 빠릅니다 . 대부분의 경우 생성기에서 수행하는 작업이 일반적으로 런타임을 지배하므로 추가 계층이 눈에 띄지 않기 때문에 이것은 중요하지 않습니다.

그러나 yield몇 가지 추가 장점이 있습니다. 단일 iterable로 제한되지 않고 추가 항목을 쉽게 생성 할 수도 있습니다.

def generator(some_list):
    for i in some_list:
        yield i

def generate_all():
    some_list = [1,2,3]
    yield 'start'
    yield from generator(some_list)
    yield 'end'

for item in generate_all():
    print(item)
start
1
2
3
end

귀하의 경우 작업이 매우 간단하고이를 위해 여러 함수를 작성해야하는지도 모르겠습니다 map. 대신 내장 또는 생성기 표현식을 대신 사용할 수 있습니다 .

map(do_something, get_the_list())          # map
(do_something(i) for i in get_the_list())  # generator expression

둘 다 동일해야합니다 (예외가 발생할 때 약간의 차이는 제외). 그리고 그들이 더 설명적인 이름이 필요하다면, 당신은 여전히 ​​그것들을 하나의 기능으로 포장 할 수 있습니다.

iterables 빌트인에 대한 매우 일반적인 조작을 랩핑하는 여러 헬퍼가 있으며, 빌트인 itertools모듈 에서 추가 툴을 찾을 수 있습니다 . 그런 간단한 경우에 나는 단순히 이것에 의지하고 사소한 경우에만 자신의 발전기를 씁니다.

그러나 실제 코드가 더 복잡하여 적용 할 수 없을 수도 있지만 대안을 언급하지 않으면 완전한 답변이 아니라고 생각했습니다.


17

발전기 위임 (PEP380)을 찾고 계십니까?

간단한 반복자의 경우 yield from iterable본질적으로 단축 된 형태입니다.for item in iterable: yield item

def generator(iterable):
  for i in iterable:
    yield do_something(i)

def generate_all():
  yield from generator(get_the_list())

꽤 간결하고 임의 / 다른 반복 가능 체인을 연결할 수있는 것과 같은 다른 많은 장점이 있습니다!


오 당신의 이름을 의미 list합니까? 질문에 붙여 넣은 실제 코드가 아닌 나쁜 예입니다. 아마도 편집해야합니다.
hyankov

예-걱정하지 마십시오. 처음 질문 할 때도 실행되지 않는 예제 코드가 매우 유죄입니다.
ti7

2
첫 번째는 하나의 라이너 일 수도 있습니다 :). yield from map(do_something, iterable)yield from (do_something(x) for x in iterable)
물리학 자

2
"이것은 예제 코드입니다!"
ti7

3
새로운 발전기를 반환하는 것 이외의 일을하는 경우에만 위임이 필요합니다. 새 발전기 만 반납하면 위임이 필요하지 않습니다. 그래서 yield from래퍼가하지 않는 한 무의미 뭔가 다른 발전기-Y를.
ShadowRanger

14

return generator(list)당신이 원하는 것을합니다. 그러나 참고

yield from generator(list)

동일하지만, generator소진 된 후에 더 많은 가치를 산출 할 수있는 기회가 주어집니다 . 예를 들면 다음과 같습니다.

def generator_all_and_then_some():
    list = get_the_list()
    yield from generator(list)
    yield "one last thing"

5
발전기의 소비자와 발전기 내부의 예외 사이 yield from와 스택 추적의 영향을받는 다른 작업과 는 미묘한 차이가 있다고 생각 합니다. returnthrows
WorldSEnder

9

이 특별한 경우에 다음 두 문장은 기능적으로 동등한 것으로 보입니다.

return generator(list)

yield from generator(list)

후자는 대략

for i in generator(list):
    yield i

return명령문은 찾고있는 생성기를 리턴합니다. yield from또는 yield문은 무언가로 그 반환 당신이 찾고있는 하나를 통과 발전기를, 전체 기능을집니다.

사용자 관점에서는 차이가 없습니다. 그러나 내부적 으로 불필요한 패스 스루 생성기에 싸이 지 return않기 때문에 더 효율적 generator(list)입니다. 랩핑 된 생성기의 요소에 대한 처리 를 계획중인 경우 , 어떤 형태 yield의 과정을 사용하십시오.


4

당신은 return그것을 할 것입니다.

yielding *을 사용하면 generate_all()생성기 자체를 평가할 수 있으며 next해당 외부 생성기를 호출 하면 첫 번째 함수에서 반환 한 내부 생성기가 반환됩니다.

* 를 포함하지 않는 yield from

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.