Możesz użyć return
raz w generatorze; zatrzymuje iterację bez zwracania niczego, a tym samym zapewnia jawną alternatywę dla pozostawienia funkcji poza zakresem. Użyj więc, yield
aby zamienić funkcję w generator, ale poprzedź ją, return
aby zakończyć generator przed uzyskaniem czegokolwiek.
>>> def f():
... return
... yield
...
>>> list(f())
[]
Nie jestem pewien, czy jest o wiele lepszy niż to, co masz - po prostu zastępuje if
oświadczenie „no-op” yield
oświadczeniem „ no-op” . Ale to jest bardziej idiomatyczne. Zwróć uwagę, że samo użycie yield
nie działa.
>>> def f():
... yield
...
>>> list(f())
[None]
Dlaczego po prostu nie użyć iter(())
?
To pytanie dotyczy konkretnie pustej funkcji generatora . Z tego powodu traktuję to jako pytanie o wewnętrzną spójność składni Pythona, a nie o najlepszy sposób tworzenia pustego iteratora w ogóle.
Jeśli faktycznie pytanie dotyczy najlepszego sposobu utworzenia pustego iteratora, możesz zgodzić się z Zectbumo na użycie iter(())
zamiast tego. Należy jednak zauważyć, że iter(())
nie zwraca funkcji! Bezpośrednio zwraca pustą iterowalną. Załóżmy, że pracujesz z interfejsem API, który oczekuje wywołania, które zwraca iterowalną wartość za każdym razem, gdy jest wywoływana, tak jak zwykła funkcja generatora. Musisz zrobić coś takiego:
def empty():
return iter(())
( Podziękowania należy się Unutbu za podanie pierwszej poprawnej wersji tej odpowiedzi).
Powyższe może być dla ciebie jaśniejsze, ale mogę sobie wyobrazić sytuacje, w których byłoby to mniej jasne. Rozważmy następujący przykład długiej listy (wymyślonych) definicji funkcji generatora:
def zeros():
while True:
yield 0
def ones():
while True:
yield 1
...
Na końcu tej długiej listy wolałbym zobaczyć coś, co zawiera znak yield
, na przykład:
def empty():
return
yield
lub w Pythonie 3.3 i nowszych (zgodnie z sugestią DSM ):
def empty():
yield from ()
Obecność yield
słowa kluczowego sprawia, że na pierwszy rzut oka widać, że jest to tylko kolejna funkcja generatora, dokładnie taka, jak wszystkie inne. Potrzeba trochę więcej czasu, aby zobaczyć, że iter(())
wersja robi to samo.
To subtelna różnica, ale szczerze uważam, że yield
funkcje oparte na systemie są bardziej czytelne i łatwiejsze w utrzymaniu.
Zobacz także tę świetną odpowiedź od użytkownika 3840170, która używa, dis
aby pokazać inny powód, dla którego takie podejście jest lepsze: po kompilacji emituje najmniej instrukcji.
if False: yield
ale wciąż trochę mylące dla ludzi, którzy nie znają tego wzoruitertools.empty()
.return
oznacza coś innego wewnątrz generatorów. To bardziej przypominabreak
.False
.Nie potrzebujesz generatora. Dalej chłopaki!
źródło
iter([])
prosty fakt, że()
jest stały, a przy każdym[]
wywołaniu może utworzyć w pamięci nowy obiekt listy.empty = lambda: iter(())
lubdef empty(): return iter(())
.tuple_iterator
zamiast agenerator
. Jeśli masz przypadek, w którym twój generator nie musi nic zwracać, nie używaj tej odpowiedzi.Python 3.3 (ponieważ jestem na
yield from
fali i ponieważ @senderle ukradł moją pierwszą myśl):>>> def f(): ... yield from () ... >>> list(f()) []
Muszę jednak przyznać, że trudno jest mi wymyślić przypadek użycia, który
iter([])
lub(x)range(0)
nie działałby równie dobrze.źródło
return; yield
lub drugieif False: yield None
.iter([])
lub(x)range(0)
nie działałby równie dobrze”. -> Nie jestem pewien, co to(x)range(0)
jest, ale przypadkiem użycia może być metoda, która ma zostać zastąpiona przez pełny generator w niektórych klasach dziedziczących. Aby zachować spójność, chciałbyś, aby nawet podstawowy, z którego dziedziczą inni, zwracał generator, taki jak ten, który go zastępuje.Inną opcją jest:
(_ for _ in ())
źródło
Generator[str, Any, None]
Jak powiedział @senderle, użyj tego:
def empty(): return yield
Piszę tę odpowiedź głównie po to, aby podzielić się innym uzasadnieniem.
Jednym z powodów wyboru tego rozwiązania spośród innych jest to, że jest ono optymalne z punktu widzenia tłumacza.
>>> 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
Jak widać,
empty_return
ma dokładnie ten sam kod bajtowy, co zwykła pusta funkcja; reszta wykonuje szereg innych operacji, które i tak nie zmieniają zachowania. Jedyna różnica międzyempty_return
inoop
polega na tym, że ten pierwszy ma ustawioną flagę generatora:>>> 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
Oczywiście siła tego argumentu jest bardzo zależna od konkretnej implementacji używanego Pythona; dostatecznie inteligentny interpretator alternatywny może zauważyć, że inne operacje nie są pożyteczne i zoptymalizować je. Jednak nawet jeśli takie optymalizacje są obecne, wymagają one od tłumacza poświęcenia czasu na ich wykonanie i zabezpieczenia się przed złamaniem założeń optymalizacyjnych, takich jak
iter
odbicie identyfikatora w zakresie globalnym do czegoś innego (nawet jeśli najprawdopodobniej wskazywałoby to na błąd, gdyby faktycznie się wydarzyło). W tym przypadkuempty_return
po prostu nie ma co optymalizować, więc nawet stosunkowo naiwny CPython nie będzie tracił czasu na żadne fałszywe operacje.źródło
yield from ()
również dla? (Zobacz odpowiedź DSM .)Czy musi to być funkcja generatora? Jeśli nie, to co powiesz
def f(): return iter(())
źródło
„Standardowym” sposobem tworzenia pustego iteratora jest iter ([]). Zasugerowałem, aby ustawić [] jako domyślny argument iter (); zostało to odrzucone z dobrymi argumentami, patrz http://bugs.python.org/issue25215 - Jurjen
źródło
generator = (item for item in [])
źródło
Chcę podać przykład oparty na klasach, ponieważ nie mieliśmy jeszcze żadnych sugestii. Jest to wywoływalny iterator, który nie generuje żadnych elementów. Uważam, że jest to prosty i opisowy sposób rozwiązania problemu.
class EmptyGenerator: def __iter__(self): return self def __next__(self): raise StopIteration >>> list(EmptyGenerator()) []
źródło