Python Boş Üretici Fonksiyonu


108

Python'da, verim anahtar sözcüğünü işlevin gövdesine koyarak bir yineleyici işlevi kolayca tanımlanabilir, örneğin:

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

Değer vermeyen (0 değer üreten) bir üretici fonksiyonunu nasıl tanımlayabilirim, aşağıdaki kod çalışmaz, çünkü python bunun normal bir fonksiyon değil bir jeneratör olduğunu varsayamaz:

def empty():
    pass

Gibi bir şey yapabilirim

def empty():
    if False:
        yield None

Ama bu çok çirkin olur. Boş bir yineleyici işlevi gerçekleştirmenin güzel bir yolu var mı?

Yanıtlar:


142

returnBir jeneratörde bir kez kullanabilirsiniz ; hiçbir şey üretmeden yinelemeyi durdurur ve böylece işlevin kapsam dışında kalmasına izin vermek için açık bir alternatif sağlar. Bu yüzden yieldişlevi bir jeneratöre dönüştürmek için kullanın , ancak returnherhangi bir şey üretmeden önce jeneratörü sonlandırmak için ondan önce gelin.

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

Sahip olduğunuzdan çok daha iyi olduğundan emin değilim - sadece işlemsiz bir ififadeyi işlemsiz bir yieldifadeyle değiştirir. Ama daha deyimsel. Sadece kullanmanın yieldişe yaramadığını unutmayın.

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

Neden sadece kullanmıyorsun iter(())?

Bu soru özellikle boş bir jeneratör işlevini soruyor . Bu nedenle, genel olarak boş bir yineleyici oluşturmanın en iyi yolu hakkında bir soru olmaktan ziyade, Python'un sözdiziminin iç tutarlılığıyla ilgili bir soru olarak kabul ediyorum.

Soru aslında boş bir yineleyici oluşturmanın en iyi yolu ile ilgiliyse , iter(())bunun yerine kullanmak konusunda Zectbumo ile aynı fikirde olabilirsiniz . Ancak, bunun iter(())bir işlev döndürmediğini gözlemlemek önemlidir ! Doğrudan boş bir yinelenebilir döndürür. Sıradan bir jeneratör işlevi gibi, her çağrıldığında bir yinelenebilir döndüren bir çağrılabilir bekleyen bir API ile çalıştığınızı varsayalım . Bunun gibi bir şey yapmanız gerekecek:

def empty():
    return iter(())

( Bu cevabın ilk doğru versiyonunu vermek için kredi Unutbu'ya gitmelidir .)

Şimdi, yukarıdakileri daha net bulabilirsiniz, ancak daha az net olacağı durumları hayal edebiliyorum. Oluşturucu işlev tanımlarının uzun bir listesinin bu örneğini düşünün:

def zeros():
    while True:
        yield 0

def ones():
    while True:
        yield 1

...

Bu uzun listenin sonunda, içinde a olan bir şey görmeyi tercih ederim, şöyle yield:

def empty():
    return
    yield

veya Python 3.3 ve üstünde ( DSM tarafından önerildiği gibi ), bu:

def empty():
    yield from ()

yieldAnahtar kelimenin varlığı , en kısa bakışta bunun sadece diğerleri gibi başka bir jeneratör işlevi olduğunu açıkça ortaya koymaktadır. iter(())Sürümün aynı şeyi yaptığını görmek biraz daha zaman alıyor .

Bu ince bir fark, ancak dürüst olmak gerekirse, yieldtemelli işlevlerin daha okunabilir ve sürdürülebilir olduğunu düşünüyorum .

Ayrıca , bu yaklaşımın neden tercih edildiğinin başka bir nedenini göstermek için kullanan user3840170'in bu harika cevabına da bakın dis: derlendiğinde en az talimat verir.


Bu gerçekten daha iyi olduğunu if False: yieldama yine de biraz bu modeli bilmeyen insanlar için kafa karıştırıcı
Konstantin Weitz

1
Ew, döndükten sonra bir şey var mı? Gibi bir şey bekliyordum itertools.empty().
Grault

1
@Jesdisciple, returnjeneratörlerin içinde farklı bir anlama geliyor. Daha çok benziyor break.
2014

Bu çözümü seviyorum çünkü (nispeten) kısa ve karşılaştırmak gibi fazladan bir iş yapmıyor False.
Pi Marillion

Unutbu'nun yanıtı, bir yineleyici döndürdüğü için bahsettiğiniz gibi "gerçek bir üretici işlevi" değildir.
Zectbumo

71
iter(())

Bir jeneratöre ihtiyacınız yok . Hadi beyler!


3
Bu cevabı kesinlikle en çok beğendim. Hızlı, yazması kolay, uygulamada hızlı ve bana her çağrıldığında bellekte yeni bir liste nesnesi oluşturabilen sabit bir süre iter([])olan basit gerçeğinden daha çekici . ()[]
Mumbleskates

2
Bu konuyu tekrar gözden geçirirken, bir jeneratör işlevi için gerçek bir drop-in değişimi istiyorsanız, empty = lambda: iter(())veya gibi bir şey yazmanız gerektiğini belirtmek zorunda hissediyorum def empty(): return iter(()).
46'da gönderen

Bir jeneratörünüz olması gerekiyorsa, diğerlerinin önerdiği gibi (_ for _ in ()) kullanabilirsiniz
Zectbumo

1
@Zectbumo, bu hala bir jeneratör işlevi değil . Bu sadece bir jeneratör. Bir üreteç işlevi her çağrıldığında yeni bir oluşturucu döndürür.
2017

1
Bu, a tuple_iteratoryerine a döndürür generator. Jeneratörünüzün hiçbir şey vermemesi gereken bir durum varsa, bu cevabı kullanmayın.
Boris

54

Python 3.3 (çünkü çok yield fromheyecanlıyım ve @senderle ilk düşüncemi çaldığı için):

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

Ama itiraf etmeliyim ki, bunun için eşit derecede iyi çalışacak iter([])ya da (x)range(0)olmayacak bir kullanım senaryosu bulmakta zorlanıyorum .


Bu sözdizimini gerçekten seviyorum. verimi harika!
Konstantin Weitz

2
Sanırım bu da daha acemi çok daha okunabilir olduğunu düşünüyorum return; yieldya if False: yield None.
abarnert

1
Bu en zarif çözüm
Maksym Ganenko

"Ama itiraf etmeliyim ki, bunun için eşit derecede iyi çalışacak iter([])veya (x)range(0)olmayacak bir kullanım senaryosu bulmakta zorlanıyorum ." -> Ne olduğundan emin değilim (x)range(0), ancak bir kullanım durumu, bazı miras alan sınıflarda tam gelişmiş bir jeneratör ile geçersiz kılınması gereken bir yöntem olabilir. Tutarlılık amacıyla, başkalarının miras aldığı temel olanın bile, onu geçersiz kılanlar gibi bir jeneratör döndürmesini istersiniz.
Vedran Šego

20

Diğer bir seçenek ise:

(_ for _ in ())

Pycharm, diğer seçeneklerden farklı olarak, bunun jeneratörler için kullanılan standart tip ipuçlarıyla tutarlı olduğunu düşünüyor, meselaGenerator[str, Any, None]
Michał Jabłoński

3

@Senderle'ın dediği gibi, şunu kullanın:

def empty():
    return
    yield

Bu cevabı çoğunlukla başka bir gerekçeyi paylaşmak için yazıyorum.

Bu çözümü diğerlerinin üzerinde seçmenin bir nedeni, tercüman söz konusu olduğunda en uygun çözüm olmasıdır.

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

Gördüğümüz empty_returngibi, normal bir boş işlevle tam olarak aynı bayt koduna sahiptir; geri kalanı, yine de davranışı değiştirmeyen bir dizi başka işlem gerçekleştirir. Arasındaki tek fark empty_returnve noopeski jeneratör bayrak kümesi olmasıdır:

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

Elbette, bu argümanın gücü kullanımdaki Python uygulamasına çok bağlıdır; yeterince akıllı bir alternatif yorumlayıcı, diğer işlemlerin hiçbir işe yaramadığını fark edebilir ve bunları optimize edebilir. Bununla birlikte, bu tür optimizasyonlar mevcut olsa bile, yorumlayıcının bunları gerçekleştirmek için zaman harcamasını ve iterküresel kapsamdaki tanımlayıcının başka bir şeye geri dönmesi gibi optimizasyon varsayımlarının bozulmasına karşı koruma sağlamasını gerektirirler (bu büyük olasılıkla bir hataya işaret etse bile, aslında oldu). empty_returnOptimize edilecek hiçbir şey olmaması durumunda , nispeten saf CPython bile herhangi bir sahte işlemle zaman kaybetmeyecektir.


Oh iyi. Sonuçlarını da ekleyebilir misiniz yield from ()? ( DSM'nin cevabına bakın .)
gönderen

3

Bir jeneratör işlevi olmalı mı? Değilse, ne dersin

def f():
    return iter(())


0
generator = (item for item in [])

0

Henüz bir önerimiz olmadığı için sınıf bazlı bir örnek vermek istiyorum. Bu, hiçbir öğe oluşturmayan çağrılabilir bir yineleyicidir. Bunun sorunu çözmenin basit ve açıklayıcı bir yolu olduğuna inanıyorum.

class EmptyGenerator:
    def __iter__(self):
        return self
    def __next__(self):
        raise StopIteration

>>> list(EmptyGenerator())
[]

Bunun OP'nin sorununu çözmek için neden / nasıl çalıştığına dair bir açıklama ekleyebilir misiniz?
SherylHohman

Lütfen yanıt olarak yalnızca kod göndermeyin, aynı zamanda kodunuzun ne yaptığını ve sorunun sorununu nasıl çözdüğünü de açıklayın. Açıklamalı yanıtlar genellikle daha yararlıdır ve daha kalitelidir ve olumlu oyları alma olasılığı daha yüksektir.
SherylHohman
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.