파이썬 빈 생성기 함수


98

파이썬에서는 다음과 같이 yield 키워드를 함수 본문에 넣어 반복자 함수를 쉽게 정의 할 수 있습니다.

def gen():
    for i in range(100):
        yield i

값을 생성하지 않는 (0 값 생성) 생성기 함수를 어떻게 정의 할 수 있습니까? 다음 코드는 작동하지 않습니다. 왜냐하면 파이썬은 그것이 정상적인 함수가 아닌 생성기인지 알 수 없기 때문입니다.

def empty():
    pass

나는 다음과 같은 것을 할 수 있었다.

def empty():
    if False:
        yield None

그러나 그것은 매우 추한 것입니다. 빈 반복기 함수를 실현하는 좋은 방법이 있습니까?

답변:


132

return생성기에서 한 번만 사용할 수 있습니다 . 아무것도 산출하지 않고 반복을 중지하므로 함수가 범위를 벗어나도록하는 것에 대한 명시적인 대안을 제공합니다. 따라서 yield함수를 제너레이터로 바꾸려면을 사용 하십시오. 그러나 return어떤 것을 산출하기 전에 제너레이터를 종료하려면 먼저 사용하십시오.

>>> def f():
...     return
...     yield
... 
>>> list(f())
[]

나는 그것이 당신이 가진 것보다 훨씬 낫다는 것을 확신하지 못합니다-단지 no-op if문을 no-op 문으로 대체합니다 yield. 그러나 그것은 더 관용적입니다. 그냥 사용 yield하는 것은 작동하지 않습니다.

>>> def f():
...     yield
... 
>>> list(f())
[None]

왜 그냥 사용하지 iter(())않습니까?

이 질문은 빈 생성기 함수 에 대해 구체적으로 묻습니다 . 따라서 일반적으로 빈 반복기를 만드는 가장 좋은 방법에 대한 질문 이라기보다는 Python 구문의 내부 일관성에 대한 질문으로 간주합니다.

질문이 실제로 빈 반복기를 만드는 가장 좋은 방법에 관한 것이라면iter(()) 대신 사용 하는 것에 대해 Zectbumo 에 동의 할 수 있습니다 . 그러나 iter(())함수를 반환하지 않는 것을 관찰하는 것이 중요 합니다! 빈 이터 러블을 직접 반환합니다. 이터 러블 을 반환 하는 콜 러블을 기대하는 API로 작업한다고 가정합니다 . 다음과 같이해야합니다.

def empty():
    return iter(())

( 이 답변의 첫 번째 올바른 버전을 제공 한 크레딧은 Unutbu 에 가야합니다 .)

이제 위의 내용이 더 명확해질 수 있지만 덜 명확 할 상황을 상상할 수 있습니다. (기여 된) 생성기 함수 정의의 긴 목록의 다음 예를 고려하십시오.

def zeros():
    while True:
        yield 0

def ones():
    while True:
        yield 1

...

긴 목록 끝에 다음과 같이 a가 yield있는 것을보고 싶습니다 .

def empty():
    return
    yield

또는 Python 3.3 이상에서 ( DSM 에서 제안한대로 ) 다음과 같습니다.

def empty():
    yield from ()

yield키워드 의 존재 는 이것이 다른 모든 함수와 똑같이 또 다른 제너레이터 함수라는 것을 아주 간략하게 보여줍니다. iter(())버전이 동일한 작업을 수행 하는지 확인하는 데 시간이 조금 더 걸립니다 .

미묘한 차이이지만 솔직히 yield기반 함수가 더 읽기 쉽고 유지 관리가 가능 하다고 생각합니다 .


이것은 참으로 좋은보다 if False: yield하지만 여전히 좀이 패턴을 모르는 사람에 대한 혼란
— 콘스탄틴 바이 츠

1
으, 돌아온 후 뭔가? 나는 같은 것을 기대했다 itertools.empty().
— Grault

1
@Jesdisciple은 return제너레이터 내부에서 다른 것을 의미합니다. 더 비슷 break합니다.
— senderle 2014

이 솔루션이 (상대적으로) 간결하고 .NET과 비교하는 것과 같은 추가 작업을 수행하지 않기 때문에이 솔루션을 좋아합니다 False.
— Pi Marillion

Unutbu의 대답은 반복자를 반환하기 때문에 언급했듯이 "진정한 생성기 함수"가 아닙니다.
— Zectbumo

72
iter(())

발전기가 필요 하지 않습니다 . 어서!


3
이 답변이 가장 마음에 듭니다. 그것은 빠른 실행에, 쉽게 쓰기에, 신속하게, 그리고보다 더 나에게 호소 iter([])단순한 사실에 대한 ()하면서 상수 []메모리에 호출 될 때마다 새 목록 객체를 생성 할 수 있습니다.
— Mumbleskates

나에게 이것은 또한 가장 우아한 해결책으로 보인다.
— Matthias CM Troffaes 2015 년

2
이 스레드를 다시 살펴보면 제너레이터 함수에 대한 진정한 드롭 인 대체품을 원한다면 empty = lambda: iter(())또는 같은 것을 작성해야한다는 점을 지적해야합니다 def empty(): return iter(()).
— senderle

발전기가 있어야하는 경우 다른 사람들이 제안한대로 (_ for _ in ())을 사용할 수도 있습니다.
— Zectbumo

1
@Zectbumo, 아직 생성기 함수 가 아닙니다 . 그것은 단지 발전기입니다. 생성기 함수는 호출 될 때마다 새 생성기를 반환합니다.
— senderle

50

Python 3.3 (나는 yield from킥을했고 @senderle이 내 첫 생각을 훔 쳤기 때문에) :

>>> def f():
...     yield from ()
... 
>>> list(f())
[]

그러나 나는 인정해야한다. 나는 이것에 대해 똑같이 잘 작동 iter([])하거나 (x)range(0)작동하지 않을 사용 사례를 찾는 데 어려움을 겪고있다 .


이 구문이 정말 마음에 듭니다. 수익률은 굉장합니다!
— Konstantin Weitz 2012

2
나는 이것이 return; yield나 보다 초보자에게 훨씬 더 읽기 쉽다고 생각합니다 if False: yield None.
— abarnert

1
이것은 가장 우아한 해결책
— Maksym Ganenko

"하지만 인정해야합니다. 이것에 대해 똑같이 잘 작동 iter([])하거나 (x)range(0)작동하지 않을 사용 사례를 찾는 데 어려움을 겪고 있습니다." -> 무엇인지 확실하지 (x)range(0)않지만 사용 사례는 일부 상속 클래스에서 완전한 생성기로 재정의되는 메서드가 될 수 있습니다. 일관성을 위해 다른 사람들이 상속하는 기본 생성자도이를 재정의하는 생성자처럼 반환하기를 원할 것입니다.
— Vedran Šego


4

생성기 함수 여야합니까? 그렇지 않다면 어떨까요

def f():
    return iter([])


1

@senderle이 말했듯이 다음을 사용하십시오.

def empty():
    return
    yield

나는 이것에 대한 또 다른 정당성을 공유하기 위해 주로이 답변을 작성하고 있습니다.

이 솔루션을 다른 솔루션보다 선택하는 한 가지 이유는 인터프리터가 고려하는 한 최적이기 때문입니다.

>>> import dis
>>> def empty_yield_from():
...     yield from ()
... 
>>> def empty_iter():
...     return iter(())
... 
>>> def empty_return():
...     return
...     yield
...
>>> def noop():
...     pass
...
>>> dis.dis(empty_yield_from)
  2           0 LOAD_CONST               1 (())
              2 GET_YIELD_FROM_ITER
              4 LOAD_CONST               0 (None)
              6 YIELD_FROM
              8 POP_TOP
             10 LOAD_CONST               0 (None)
             12 RETURN_VALUE
>>> dis.dis(empty_iter)
  2           0 LOAD_GLOBAL              0 (iter)
              2 LOAD_CONST               1 (())
              4 CALL_FUNCTION            1
              6 RETURN_VALUE
>>> dis.dis(empty_return)
  2           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE
>>> dis.dis(noop)
  2           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE

보시다시피, empty_return는 일반 빈 함수와 정확히 동일한 바이트 코드를 가지고 있습니다. 나머지는 어쨌든 동작을 변경하지 않는 여러 다른 작업을 수행합니다. 유일한 차이점 empty_return과 noop전자가 생성 플래그 세트를 갖는 것이다 :

>>> dis.show_code(noop)
Name:              noop
Filename:          <stdin>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  0
Stack size:        1
Flags:             OPTIMIZED, NEWLOCALS, NOFREE
Constants:
   0: None
>>> dis.show_code(empty_return)
Name:              empty_return
Filename:          <stdin>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  0
Stack size:        1
Flags:             OPTIMIZED, NEWLOCALS, GENERATOR, NOFREE
Constants:
   0: None

물론이 주장의 강점은 사용중인 Python의 특정 구현에 따라 크게 달라집니다. 충분히 똑똑한 대체 통역사는 다른 작업이 유용하지 않다는 것을 알아 차리고 최적화 할 수 있습니다. 그러나 이러한 최적화가 존재하더라도 인터프리터는이를 수행하는 데 시간을 할애 iter하고 전역 범위 의 식별자가 다른 항목으로 리 바인드되는 것과 같이 최적화 가정이 깨지지 않도록 보호 해야합니다. 실제로 일어났습니다). 의 경우 empty_return가 너무도 상대적으로 순진한 CPython의 어떤 의사 작업에 시간을 낭비하지 않을 것이다, 단순히 최적화에 불과하다.


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