파이썬 빈 생성기 함수


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_returnnoop전자가 생성 플래그 세트를 갖는 것이다 :

>>> 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.