Apa yang dilakukan kata kunci “hasil”?

10197

Apa gunanya yieldkata kunci dalam Python, dan apa fungsinya?

Misalnya, saya mencoba memahami kode 1 ini :

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

Dan ini peneleponnya:

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

Apa yang terjadi ketika metode _get_child_candidatesini dipanggil? Apakah daftar dikembalikan? Satu elemen? Apakah dipanggil lagi? Kapan panggilan selanjutnya akan berhenti?


1. Potongan kode ini ditulis oleh Jochen Schulz (jrschulz), yang membuat perpustakaan Python yang bagus untuk ruang metrik. Ini adalah tautan ke sumber lengkap: Module mspace .

Alex S.
sumber

Jawaban:

14650

Untuk memahami apa yang yielddilakukan, Anda harus memahami apa itu generator . Dan sebelum Anda dapat memahami generator, Anda harus memahami iterables .

Iterables

Saat Anda membuat daftar, Anda dapat membaca itemnya satu per satu. Membaca itemnya satu per satu disebut iterasi:

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylistadalah iterable . Ketika Anda menggunakan pemahaman daftar, Anda membuat daftar, dan iterable:

>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
...    print(i)
0
1
4

Segala sesuatu yang Anda dapat gunakan " for... in..." pada adalah iterable; lists,, stringsfile ...

Iterables ini berguna karena Anda dapat membacanya sebanyak yang Anda inginkan, tetapi Anda menyimpan semua nilai dalam memori dan ini tidak selalu seperti yang Anda inginkan ketika Anda memiliki banyak nilai.

Generator

Generator adalah iterator, semacam iterable yang hanya bisa Anda ulangi sekali . Generator tidak menyimpan semua nilai dalam memori, mereka menghasilkan nilai dengan cepat :

>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
...    print(i)
0
1
4

Itu sama saja kecuali Anda menggunakan ()bukan []. TETAPI, Anda tidak dapat melakukan for i in mygeneratorkedua kalinya karena generator hanya dapat digunakan satu kali: mereka menghitung 0, lalu melupakannya dan menghitung 1, dan akhirnya menghitung 4, satu per satu.

Menghasilkan

yieldadalah kata kunci yang digunakan seperti return, kecuali fungsi akan mengembalikan generator.

>>> def createGenerator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

Ini adalah contoh yang tidak berguna, tetapi berguna ketika Anda tahu fungsi Anda akan mengembalikan sejumlah besar nilai yang hanya perlu Anda baca sekali.

Untuk menguasai yield, Anda harus memahami bahwa ketika Anda memanggil fungsi, kode yang Anda tulis di badan fungsi tidak berjalan. Fungsi hanya mengembalikan objek generator, ini agak rumit :-)

Kemudian, kode Anda akan dilanjutkan dari tempat penghentiannya setiap kali formenggunakan generator.

Sekarang bagian yang sulit:

Pertama kali formemanggil objek generator yang dibuat dari fungsi Anda, itu akan menjalankan kode dalam fungsi Anda dari awal sampai hits yield, maka itu akan mengembalikan nilai pertama dari loop. Kemudian, setiap panggilan berikutnya akan menjalankan iterasi lain dari loop yang telah Anda tulis dalam fungsi dan mengembalikan nilai berikutnya. Ini akan berlanjut sampai generator dianggap kosong, yang terjadi ketika fungsi berjalan tanpa memukul yield. Itu bisa karena loop telah berakhir, atau karena Anda tidak lagi memuaskan "if/else".


Kode Anda dijelaskan

Generator:

# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):

    # Here is the code that will be called each time you use the generator object:

    # If there is still a child of the node object on its left
    # AND if the distance is ok, return the next child
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild

    # If there is still a child of the node object on its right
    # AND if the distance is ok, return the next child
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild

    # If the function arrives here, the generator will be considered empty
    # there is no more than two values: the left and the right children

Penelepon:

# Create an empty list and a list with the current object reference
result, candidates = list(), [self]

# Loop on candidates (they contain only one element at the beginning)
while candidates:

    # Get the last candidate and remove it from the list
    node = candidates.pop()

    # Get the distance between obj and the candidate
    distance = node._get_dist(obj)

    # If distance is ok, then you can fill the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate in the candidate's list
    # so the loop will keep running until it will have looked
    # at all the children of the children of the children, etc. of the candidate
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))

return result

Kode ini mengandung beberapa bagian cerdas:

  • Pengulangan berulang pada daftar, tetapi daftar mengembang saat pengulangan diulangi :-) Ini adalah cara ringkas untuk menelusuri semua data bersarang ini meskipun itu sedikit berbahaya karena Anda bisa berakhir dengan pengulangan tak terbatas. Dalam hal ini, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))buang semua nilai generator, tetapi whileterus membuat objek generator baru yang akan menghasilkan nilai yang berbeda dari yang sebelumnya karena tidak diterapkan pada node yang sama.

  • The extend()Metode adalah metode daftar objek yang mengharapkan iterable dan menambahkan nilai-nilai ke dalam daftar.

Biasanya kami memberikan daftar itu:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

Tetapi dalam kode Anda, ia mendapatkan generator, yang bagus karena:

  1. Anda tidak perlu membaca nilai dua kali.
  2. Anda mungkin memiliki banyak anak dan Anda tidak ingin mereka semua tersimpan dalam memori.

Dan itu berhasil karena Python tidak peduli jika argumen suatu metode adalah daftar atau tidak. Python mengharapkan iterables sehingga akan bekerja dengan string, daftar, tupel, dan generator! Ini disebut pengetikan bebek dan merupakan salah satu alasan mengapa Python sangat keren. Tapi ini cerita lain, untuk pertanyaan lain ...

Anda bisa berhenti di sini, atau membaca sedikit untuk melihat penggunaan generator tingkat lanjut:

Mengontrol kelelahan generator

>>> class Bank(): # Let's create a bank, building ATMs
...    crisis = False
...    def create_atm(self):
...        while not self.crisis:
...            yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
...    print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...

Catatan: Untuk Python 3, gunakan print(corner_street_atm.__next__())atauprint(next(corner_street_atm))

Ini dapat berguna untuk berbagai hal seperti mengendalikan akses ke sumber daya.

Itertools, sahabatmu

Modul itertools berisi fungsi-fungsi khusus untuk memanipulasi iterables. Pernah ingin menduplikasi generator? Rantai dua generator? Nilai grup dalam daftar bersarang dengan satu garis? Map / Ziptanpa membuat daftar lain?

Lalu saja import itertools.

Sebuah contoh? Mari kita lihat kemungkinan pesanan kedatangan untuk balap empat kuda:

>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
 (1, 2, 4, 3),
 (1, 3, 2, 4),
 (1, 3, 4, 2),
 (1, 4, 2, 3),
 (1, 4, 3, 2),
 (2, 1, 3, 4),
 (2, 1, 4, 3),
 (2, 3, 1, 4),
 (2, 3, 4, 1),
 (2, 4, 1, 3),
 (2, 4, 3, 1),
 (3, 1, 2, 4),
 (3, 1, 4, 2),
 (3, 2, 1, 4),
 (3, 2, 4, 1),
 (3, 4, 1, 2),
 (3, 4, 2, 1),
 (4, 1, 2, 3),
 (4, 1, 3, 2),
 (4, 2, 1, 3),
 (4, 2, 3, 1),
 (4, 3, 1, 2),
 (4, 3, 2, 1)]

Memahami mekanisme internal iterasi

Iterasi adalah proses yang menyiratkan iterables (menerapkan __iter__()metode) dan iterator (menerapkan __next__()metode). Iterables adalah objek apa pun yang bisa Anda peroleh dengan iterator. Iterator adalah objek yang memungkinkan Anda beralih di iterables.

Ada lebih banyak tentang hal ini dalam artikel ini tentang cara forkerja loop .

e-satis
sumber
355
yieldtidak ajaib seperti yang dijawab oleh jawaban ini. Saat Anda memanggil fungsi yang berisi yieldpernyataan di mana saja, Anda mendapatkan objek generator, tetapi tidak ada kode yang berjalan. Kemudian setiap kali Anda mengekstrak objek dari generator, Python mengeksekusi kode dalam fungsi sampai datang ke yieldpernyataan, lalu berhenti sebentar dan mengirimkan objek. Ketika Anda mengekstrak objek lain, Python melanjutkan setelah yielddan berlanjut hingga mencapai yang lain yield(sering kali sama, tetapi satu iterasi kemudian). Ini berlanjut sampai fungsi mati, di mana generator dianggap habis.
Matthias Fripp
30
"Iterables ini berguna ... tetapi Anda menyimpan semua nilai dalam memori dan ini tidak selalu seperti yang Anda inginkan", salah atau membingungkan. Iterable mengembalikan iterator saat memanggil iter () di iterable, dan iterator tidak selalu harus menyimpan nilai-nilainya dalam memori, tergantung pada penerapan metode iter , iterable juga dapat menghasilkan nilai dalam urutan sesuai permintaan.
pikmate 涅
Akan lebih baik untuk menambahkan jawaban yang bagus mengapa ini sama saja kecuali Anda menggunakan ()bukan[] , khususnya apa ()yang ada (mungkin ada kebingungan dengan tuple).
WoJ
Saya mungkin salah, tetapi generator bukanlah iterator, "generator yang disebut" adalah iterator.
aderchox
@MatthiasFripp "Ini berlanjut sampai fungsi mati akhirnya" - atau bertemu dengan returnpernyataan. ( returndiizinkan dalam fungsi yang mengandung yield, asalkan tidak menentukan nilai kembali.)
alaniwi
2007

Jalan pintas menuju pemahaman yield

Saat Anda melihat fungsi dengan yieldpernyataan, terapkan trik mudah ini untuk memahami apa yang akan terjadi:

  1. Masukkan garis result = []di awal fungsi.
  2. Ganti masing yield expr- masing dengan result.append(expr).
  3. Masukkan garis return resultdi bagian bawah fungsi.
  4. Yay - tidak ada lagi yieldpernyataan! Baca dan temukan kode.
  5. Bandingkan fungsi dengan definisi asli.

Trik ini mungkin memberi Anda gagasan tentang logika di balik fungsi, tetapi apa yang sebenarnya terjadi dengan yieldsangat berbeda dari apa yang terjadi dalam pendekatan berbasis daftar. Dalam banyak kasus, pendekatan hasil akan jauh lebih efisien dan lebih cepat. Dalam kasus lain, trik ini akan membuat Anda terjebak dalam infinite loop, meskipun fungsi aslinya berfungsi dengan baik. Baca terus untuk mengetahui lebih lanjut ...

Jangan membingungkan Iterables, Iterators, dan Generator Anda

Pertama, protokol iterator - saat Anda menulis

for x in mylist:
    ...loop body...

Python melakukan dua langkah berikut:

  1. Mendapat iterator untuk mylist:

    Panggil iter(mylist)-> ini mengembalikan objek dengan next()metode (atau __next__()dengan Python 3).

    [Ini adalah langkah yang kebanyakan orang lupa untuk memberi tahu Anda tentang]

  2. Menggunakan iterator untuk mengulang item:

    Terus memanggil next()metode pada iterator yang dikembalikan dari langkah 1. Nilai kembali dari next()ditugaskan ke xdan tubuh loop dijalankan. Jika pengecualian StopIterationdimunculkan dari dalam next(), itu berarti tidak ada lagi nilai di iterator dan loop keluar.

Yang benar adalah Python melakukan dua langkah di atas kapan saja ia ingin mengulang isi suatu objek - jadi itu bisa untuk perulangan, tetapi bisa juga berupa kode otherlist.extend(mylist)(di mana otherlistada daftar Python).

Berikut mylistini adalah iterable karena mengimplementasikan protokol iterator. Di kelas yang ditentukan pengguna, Anda bisa mengimplementasikan __iter__()metode untuk membuat instance kelas Anda dapat diubah. Metode ini harus mengembalikan iterator . Iterator adalah objek dengan next()metode. Dimungkinkan untuk mengimplementasikan keduanya __iter__()dan next()di kelas yang sama, dan __iter__()kembali self. Ini akan bekerja untuk kasus-kasus sederhana, tetapi tidak ketika Anda ingin dua iterator mengulangi objek yang sama secara bersamaan.

Jadi itulah protokol iterator, banyak objek mengimplementasikan protokol ini:

  1. Daftar, kamus, tupel, set, file bawaan.
  2. Kelas yang ditentukan pengguna yang menerapkan __iter__().
  3. Generator.

Perhatikan bahwa satu forloop tidak tahu objek apa yang dihadapinya - itu hanya mengikuti protokol iterator, dan dengan senang hati mendapatkan item setelah item yang dipanggil next(). Daftar built-in mengembalikan item mereka satu per satu, kamus mengembalikan kunci satu per satu, file mengembalikan baris satu per satu, dll. Dan generator kembali ... baik di situlah yieldmasuk:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Alih-alih yieldpernyataan, jika Anda memiliki tiga returnpernyataan f123()hanya yang pertama akan dieksekusi, dan fungsi akan keluar. Tetapi f123()tidak ada fungsi biasa. Ketika f123()dipanggil, itu tidak mengembalikan nilai apa pun dalam laporan hasil! Ini mengembalikan objek generator. Juga, fungsi tidak benar-benar keluar - itu masuk ke status ditangguhkan. Ketika forloop mencoba untuk loop di atas objek generator, fungsi melanjutkan dari status ditangguhkan di baris berikutnya setelah yielditu kembali dari sebelumnya, mengeksekusi baris kode berikutnya, dalam hal ini, yieldpernyataan, dan mengembalikannya sebagai berikutnya barang. Ini terjadi sampai fungsi keluar, pada titik mana generator menaikkan StopIteration, dan loop keluar.

Jadi objek generator seperti adaptor - di satu sisi ia memperlihatkan protokol iterator, dengan mengekspos __iter__()dan next()metode untuk menjaga forloop tetap bahagia. Di ujung lain, ia menjalankan fungsi cukup untuk mendapatkan nilai berikutnya darinya, dan mengembalikannya ke mode ditangguhkan.

Mengapa Menggunakan Generator?

Biasanya, Anda dapat menulis kode yang tidak menggunakan generator tetapi mengimplementasikan logika yang sama. Salah satu pilihan adalah menggunakan 'trik' daftar sementara yang saya sebutkan sebelumnya. Itu tidak akan berfungsi dalam semua kasus, misalnya jika Anda memiliki loop tak terbatas, atau mungkin menggunakan memori yang tidak efisien ketika Anda memiliki daftar yang sangat panjang. Pendekatan lain adalah menerapkan kelas iterable baru SomethingIter yang membuat negara anggota tetap dan melakukan langkah logis berikutnya dalam metode itu next()(atau __next__()dengan Python 3). Bergantung pada logika, kode di dalam next()metode ini mungkin terlihat sangat rumit dan rentan terhadap bug. Di sini generator memberikan solusi yang bersih dan mudah.

pengguna28409
sumber
20
"Ketika Anda melihat fungsi dengan pernyataan hasil, terapkan trik mudah ini untuk memahami apa yang akan terjadi" Bukankah ini sepenuhnya mengabaikan fakta bahwa Anda dapat sendmenjadi generator, yang merupakan bagian besar dari titik generator?
DanielSank
10
"bisa jadi for loop, tetapi bisa juga berupa kode seperti otherlist.extend(mylist)" -> Ini tidak benar. extend()memodifikasi daftar di tempat dan tidak mengembalikan iterable. Mencoba untuk mengulang otherlist.extend(mylist)akan gagal dengan mengembalikan TypeErrorkarena extend()secara implisit None, dan Anda tidak dapat mengulang None.
Pedro
4
@pedro Anda salah mengerti kalimat itu. Ini berarti bahwa python melakukan dua langkah yang disebutkan pada mylist(tidak aktif otherlist) ketika mengeksekusi otherlist.extend(mylist).
hari ini
555

Pikirkan seperti ini:

Sebuah iterator hanyalah istilah terdengar mewah untuk objek yang memiliki next()metode. Jadi fungsi hasil-ed akhirnya menjadi seperti ini:

Versi asli:

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

Ini pada dasarnya adalah apa yang dilakukan penerjemah Python dengan kode di atas:

class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i

Untuk wawasan lebih lanjut tentang apa yang terjadi di balik layar, forloop dapat ditulis ulang untuk ini:

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

Apakah itu lebih masuk akal atau hanya membuat Anda lebih bingung? :)

Saya harus mencatat bahwa ini adalah penyederhanaan yang berlebihan untuk tujuan ilustrasi. :)

Jason Baker
sumber
1
__getitem__dapat didefinisikan sebagai ganti __iter__. Sebagai contoh:, class it: pass; it.__getitem__ = lambda self, i: i*10 if i < 10 else [][0]; for i in it(): print(i)Ini akan mencetak: 0, 10, 20, ..., 90
jfs
17
Saya mencoba contoh ini di Python 3.6 dan jika saya membuat iterator = some_function(), variabel iteratortidak memiliki fungsi yang disebut next()lagi, tetapi hanya sebuah __next__()fungsi. Kupikir aku akan menyebutkannya.
Peter
Di mana forimplementasi loop yang Anda tulis memanggil __iter__metode iterator, instantiated instance of it?
SystematicDisintegrasi
455

Kata yieldkunci dikurangi menjadi dua fakta sederhana:

  1. Jika kompiler mendeteksi yieldkata kunci di mana saja di dalam suatu fungsi, fungsi itu tidak lagi kembali melalui returnpernyataan. Sebagai gantinya , ia segera mengembalikan objek "pending list" yang malas yang disebut generator
  2. Generator itu bisa diubah. Apa itu iterable ? Ini seperti listatau setatau rangeatau tampilan dict, dengan protokol bawaan untuk mengunjungi setiap elemen dalam urutan tertentu .

Singkatnya: generator adalah daftar malas, tambahan-tertunda , dan yieldpernyataan memungkinkan Anda untuk menggunakan notasi fungsi untuk memprogram nilai - nilai daftar generator yang harus dimuntahkan secara bertahap.

generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

Contoh

Mari kita mendefinisikan fungsi makeRangeyang persis seperti Python range. Memanggil makeRange(n)RETURNS GENERATOR:

def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

Untuk memaksa generator untuk segera mengembalikan nilai yang tertunda, Anda dapat meneruskannya list()(sama seperti Anda dapat melakukannya):

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

Membandingkan contoh dengan "hanya mengembalikan daftar"

Contoh di atas dapat dianggap sebagai hanya membuat daftar yang Anda tambahkan dan kembalikan:

# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]

Namun, ada satu perbedaan besar; lihat bagian terakhir.


Bagaimana Anda bisa menggunakan generator

Iterable adalah bagian terakhir dari pemahaman daftar, dan semua generator bisa diubah, jadi mereka sering digunakan seperti:

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

Untuk mendapatkan rasa yang lebih baik untuk generator, Anda dapat bermain-main dengan itertoolsmodul (pastikan untuk menggunakan chain.from_iterabledaripada chainsaat diperlukan). Sebagai contoh, Anda bahkan dapat menggunakan generator untuk mengimplementasikan daftar malas yang sangat panjang seperti itertools.count(). Anda dapat menerapkan sendiri def enumerate(iterable): zip(count(), iterable), atau melakukannya dengan yieldkata kunci dalam loop sementara.

Harap dicatat: generator sebenarnya dapat digunakan untuk banyak hal lagi, seperti menerapkan coroutine atau pemrograman non-deterministik atau hal-hal elegan lainnya. Namun, sudut pandang "daftar malas" yang saya sajikan di sini adalah penggunaan paling umum yang akan Anda temukan.


Di balik layar

Ini adalah cara kerja "protokol iterasi Python" bekerja. Yaitu, apa yang terjadi ketika Anda melakukannya list(makeRange(5)). Inilah yang saya gambarkan sebelumnya sebagai "daftar malas, tambahan".

>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Fungsi built-in next()hanya memanggil .next()fungsi objek , yang merupakan bagian dari "protokol iterasi" dan ditemukan di semua iterator. Anda dapat secara manual menggunakan next()fungsi (dan bagian lain dari protokol iterasi) untuk mengimplementasikan hal-hal mewah, biasanya dengan mengorbankan keterbacaan, jadi cobalah untuk menghindari melakukan itu ...


Detel

Biasanya, kebanyakan orang tidak akan peduli dengan perbedaan berikut dan mungkin ingin berhenti membaca di sini.

Dalam Python-speak, iterable adalah objek apa pun yang "memahami konsep for-loop" seperti daftar [1,2,3], dan iterator adalah contoh spesifik dari yang diminta untuk-loop seperti [1,2,3].__iter__(). Sebuah generator persis sama dengan iterator apa pun, kecuali cara itu ditulis (dengan sintaks fungsi).

Ketika Anda meminta iterator dari daftar, itu membuat iterator baru. Namun, ketika Anda meminta iterator dari iterator (yang jarang Anda lakukan), itu hanya memberi Anda salinannya sendiri.

Jadi, jika Anda gagal melakukan sesuatu seperti ini ...

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

... maka ingatlah bahwa generator adalah iterator ; yaitu, ini hanya sekali pakai. Jika Anda ingin menggunakannya kembali, Anda harus menelepon myRange(...)lagi. Jika Anda perlu menggunakan hasilnya dua kali, konversikan hasilnya ke daftar dan simpan dalam variabel x = list(myRange(5)). Mereka yang benar-benar perlu mengkloning generator (misalnya, yang melakukan metaprogramming yang sangat menakutkan) dapat menggunakannya itertools.teejika benar-benar diperlukan, karena proposal standar Python PEP yang dapat disalin telah ditangguhkan.

ninjagecko
sumber
378

Apa yang dilakukan yieldkata kunci dengan Python?

Jawab Garis Besar / Ringkasan

  • Fungsi dengan yield, ketika dipanggil, mengembalikan Generator .
  • Generator adalah iterator karena mereka mengimplementasikan protokol iterator , sehingga Anda dapat menggunakannya lagi.
  • Generator juga dapat dikirim informasi , sehingga secara konseptual menjadi coroutine .
  • Dalam Python 3, Anda dapat mendelegasikan dari satu generator ke yang lain di kedua arah denganyield from .
  • (Lampiran mengkritik beberapa jawaban, termasuk yang teratas, dan membahas penggunaan returndalam generator.)

Generator:

yieldhanya legal di dalam definisi fungsi, dan dimasukkannya yielddalam definisi fungsi membuatnya mengembalikan generator.

Gagasan untuk generator berasal dari bahasa lain (lihat catatan kaki 1) dengan beragam implementasi. Dalam Python Generator, eksekusi kode dibekukan pada titik hasil. Ketika generator dipanggil (metode dibahas di bawah) eksekusi akan dilanjutkan dan kemudian membeku di hasil berikutnya.

yieldmenyediakan cara mudah untuk mengimplementasikan protokol iterator , yang didefinisikan oleh dua metode berikut: __iter__dan next(Python 2) atau __next__(Python 3). Kedua metode tersebut membuat objek sebuah iterator yang bisa Anda ketik-periksa dengan IteratorAbstrak Base Class dari collectionsmodul.

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

Jenis generator adalah sub-jenis iterator:

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

Dan jika perlu, kita bisa mengetik-cek seperti ini:

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

Suatu fitur dari suatu Iterator yang sekali habis , Anda tidak dapat menggunakan kembali atau meresetnya:

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

Anda harus membuat yang lain jika ingin menggunakan fungsinya lagi (lihat catatan kaki 2):

>>> list(func())
['I am', 'a generator!']

Seseorang dapat menghasilkan data secara terprogram, misalnya:

def func(an_iterable):
    for item in an_iterable:
        yield item

Generator sederhana di atas juga setara dengan di bawah ini - seperti Python 3.3 (dan tidak tersedia di Python 2), Anda dapat menggunakan yield from:

def func(an_iterable):
    yield from an_iterable

Namun, yield from juga memungkinkan untuk didelegasikan kepada subgenerator, yang akan dijelaskan pada bagian berikut tentang delegasi koperasi dengan sub-coroutine.

Coroutine:

yield membentuk ekspresi yang memungkinkan data dikirim ke generator (lihat catatan kaki 3)

Berikut ini sebuah contoh, perhatikan receivedvariabel, yang akan menunjuk ke data yang dikirim ke generator:

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

Pertama, kita harus membuat antrian generator dengan fungsi builtin next,. Ini akan memanggil metode nextatau yang sesuai __next__, tergantung pada versi Python yang Anda gunakan:

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

Dan sekarang kita dapat mengirim data ke generator. ( Mengirim Nonesama dengan meneleponnext .):

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

Delegasi Koperasi untuk Sub-Coroutine dengan yield from

Sekarang, ingatlah bahwa yield fromtersedia dalam Python 3. Ini memungkinkan kita untuk mendelegasikan coroutine ke suboroutine:

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

Dan sekarang kita dapat mendelegasikan fungsionalitas ke sub-generator dan dapat digunakan oleh generator seperti di atas:

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

Anda dapat membaca lebih lanjut tentang semantik yang tepat yield fromdalam PEP 380.

Metode lain: tutup dan lempar

The closeMetode menimbulkan GeneratorExitpada titik eksekusi fungsi membeku. Ini juga akan dipanggil oleh __del__sehingga Anda dapat menempatkan kode pembersihan di mana Anda menangani GeneratorExit:

>>> my_account.close()

Anda juga bisa melempar pengecualian yang bisa ditangani di generator atau disebarkan kembali ke pengguna:

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

Kesimpulan

Saya yakin saya telah membahas semua aspek dari pertanyaan berikut:

Apa yang dilakukan yieldkata kunci dengan Python?

Ternyata itu yieldbanyak artinya. Saya yakin saya bisa menambahkan contoh yang lebih teliti untuk ini. Jika Anda ingin lebih atau memiliki kritik yang membangun, beri tahu saya dengan berkomentar di bawah ini.


Lampiran:

Kritik Atas / Jawaban yang Diterima **

  • Bingung tentang apa yang membuat iterable , hanya menggunakan daftar sebagai contoh. Lihat referensi saya di atas, tetapi dalam ringkasan: iterable memiliki __iter__metode mengembalikan iterator . Sebuah iterator menyediakan metode .next(Python 2 atau .__next__(Python 3), yang secara implisit disebut dengan forloop sampai memunculkan StopIteration, dan begitu berhasil, ia akan terus melakukannya.
  • Kemudian menggunakan ekspresi generator untuk menggambarkan apa itu generator. Karena generator hanyalah cara mudah untuk membuat iterator , itu hanya membingungkan masalah, dan kami masih belum sampai pada yieldbagian itu.
  • Dalam Mengontrol kelelahan generator ia memanggil .nextmetode, ketika sebaliknya ia harus menggunakan fungsi builtin next,. Ini akan menjadi lapisan tipuan yang tepat, karena kodenya tidak berfungsi di Python 3.
  • Itertools? Ini tidak relevan dengan apa yang yieldterjadi.
  • Tidak ada diskusi tentang metode yang yieldmenyediakan bersama dengan fungsionalitas baru yield fromdi Python 3. Jawaban teratas / diterima adalah jawaban yang sangat tidak lengkap.

Kritik jawaban yang disarankan yielddalam ekspresi atau pemahaman generator.

Tata bahasanya saat ini memungkinkan setiap ekspresi dalam pemahaman daftar.

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

Karena hasil adalah ekspresi, itu telah disebut-sebut oleh beberapa orang sebagai menarik untuk menggunakannya dalam pemahaman atau ekspresi generator - meskipun mengutip tidak ada kasus penggunaan yang sangat baik.

Pengembang inti CPython sedang mendiskusikan penghentian pemberiannya . Berikut pos yang relevan dari milis:

Pada 30 Januari 2017 pukul 19:05, Brett Cannon menulis:

On Sun, 29 Jan 2017 pukul 16:39 Craig Rodrigues menulis:

Saya setuju dengan kedua pendekatan itu. Membiarkan hal-hal seperti mereka di Python 3 tidak baik, IMHO.

Pilihan saya adalah SyntaxError karena Anda tidak mendapatkan apa yang Anda harapkan dari sintaks.

Saya setuju bahwa itu adalah tempat yang masuk akal bagi kita untuk berakhir, karena kode apa pun yang bergantung pada perilaku saat ini benar-benar terlalu pintar untuk dipertahankan.

Dalam hal menuju ke sana, kita mungkin akan menginginkan:

  • SyntaxWarning atau DeprecationWarning in 3.7
  • Peringatan Py3k di 2.7.x
  • Sintaksisor dalam 3.8

Ceria, Nick.

- Nick Coghlan | ncoghlan di gmail.com | Brisbane, Australia

Lebih jauh lagi, ada masalah luar biasa (10544) yang tampaknya menunjuk ke arah ini tidak pernah menjadi ide yang baik (PyPy, implementasi Python yang ditulis dengan Python, sudah menaikkan peringatan sintaksis.)

Intinya, sampai pengembang CPython memberi tahu kami sebaliknya: Jangan menaruh yieldekspresi atau pemahaman generator.

The returnpernyataan dalam sebuah generator

Dengan Python 2 :

Dalam fungsi generator, returnpernyataan itu tidak diizinkan untuk menyertakan expression_list. Dalam konteks itu, telanjang returnmenunjukkan bahwa generator sudah selesai dan akan menyebabkan StopIterationdinaikkan.

An expression_listpada dasarnya adalah sejumlah ekspresi yang dipisahkan oleh koma - pada dasarnya, dengan Python 2, Anda dapat menghentikan generator return, tetapi Anda tidak dapat mengembalikan nilai.

Dengan Python 3 :

Dalam fungsi generator, returnpernyataan menunjukkan bahwa generator sudah selesai dan akan menyebabkan StopIterationdinaikkan. Nilai yang dikembalikan (jika ada) digunakan sebagai argumen untuk membangun StopIterationdan menjadi StopIteration.valueatribut.

Catatan kaki

  1. Bahasa CLU, Sather, dan Icon dirujuk dalam proposal untuk memperkenalkan konsep generator ke Python. Gagasan umum adalah bahwa suatu fungsi dapat mempertahankan keadaan internal dan menghasilkan titik data menengah pada permintaan oleh pengguna. Ini berjanji akan unggul dalam kinerja dibandingkan pendekatan lain, termasuk Python threading , yang bahkan tidak tersedia pada beberapa sistem.

  2. Ini berarti, misalnya, bahwa xrangeobjek ( rangedalam Python 3) tidak Iterators, meskipun mereka dapat diubah, karena mereka dapat digunakan kembali. Seperti daftar, __iter__metode mereka mengembalikan objek iterator.

  3. yieldpada awalnya diperkenalkan sebagai pernyataan, yang berarti bahwa itu hanya bisa muncul di awal baris dalam blok kode. Sekarang yieldbuat ekspresi hasil. https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt Perubahan ini diusulkan untuk memungkinkan pengguna mengirim data ke generator sama seperti orang mungkin menerimanya. Untuk mengirim data, seseorang harus dapat menetapkannya untuk sesuatu, dan untuk itu, pernyataan tidak akan berfungsi.

Aaron Hall
sumber
328

yieldsama seperti return- mengembalikan apa pun yang Anda katakan (sebagai generator). Perbedaannya adalah bahwa lain kali Anda memanggil generator, eksekusi dimulai dari panggilan terakhir ke yieldpernyataan. Tidak seperti pengembalian, bingkai tumpukan tidak dibersihkan ketika hasil terjadi, namun kontrol ditransfer kembali ke pemanggil, sehingga statusnya akan dilanjutkan saat fungsi berikutnya dipanggil.

Dalam kasus kode Anda, fungsinya get_child_candidatesberfungsi seperti iterator sehingga ketika Anda memperluas daftar Anda, itu menambahkan satu elemen pada satu waktu ke daftar baru.

list.extendmemanggil iterator sampai habis. Dalam hal contoh kode yang Anda posting, akan jauh lebih jelas untuk hanya mengembalikan tuple dan menambahkannya ke daftar.

Douglas Mayle
sumber
107
Ini dekat, tetapi tidak benar. Setiap kali Anda memanggil fungsi dengan pernyataan hasil di dalamnya, ia mengembalikan objek generator baru. Hanya ketika Anda memanggil metode .next () generator yang eksekusi dilanjutkan setelah hasil terakhir.
kurosch
239

Ada satu hal tambahan untuk disebutkan: fungsi yang menghasilkan sebenarnya tidak harus berakhir. Saya sudah menulis kode seperti ini:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

Maka saya dapat menggunakannya dalam kode lain seperti ini:

for f in fib():
    if some_condition: break
    coolfuncs(f);

Ini benar-benar membantu menyederhanakan beberapa masalah, dan membuat beberapa hal lebih mudah untuk dikerjakan.

Claudiu
sumber
233

Bagi mereka yang lebih suka contoh kerja minimal, renungkan sesi Python interaktif ini:

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print(i)
... 
1
2
3
>>> for i in g:
...   print(i)
... 
>>> # Note that this time nothing was printed
Daniel
sumber
209

TL; DR

Alih-alih ini:

def square_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

melakukan hal ini:

def square_yield(n):
    for x in range(n):
        y = x * x
        yield y                           # with this one.

Setiap kali Anda menemukan diri Anda membuat daftar dari awal, yieldmasing-masing bagian sebagai gantinya.

Ini adalah momen "aha" pertamaku dengan hasil.


yieldadalah cara yang manis untuk mengatakan

membangun serangkaian barang

Perilaku yang sama:

>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9

Perilaku yang berbeda:

Hasil adalah single-pass : Anda hanya dapat mengulanginya sekali saja. Ketika suatu fungsi memiliki hasil di dalamnya kita menyebutnya fungsi generator . Dan sebuah iterator adalah apa yang dikembalikan. Istilah-istilah itu mengungkapkan. Kami kehilangan kenyamanan sebuah wadah, tetapi mendapatkan kekuatan seri yang dihitung sesuai kebutuhan, dan panjang sewenang-wenang.

Menghasilkan malas , itu menunda perhitungan. Fungsi dengan hasil di dalamnya tidak benar-benar mengeksekusi sama sekali ketika Anda menyebutnya. Ini mengembalikan objek iterator yang mengingat di mana ia tinggalkan. Setiap kali Anda memanggil next()iterator (ini terjadi dalam for-loop) eksekusi inci ke depan untuk hasil berikutnya. returnmemunculkan StopIteration dan mengakhiri seri (ini adalah akhir alami for-loop).

Hasil panen serba guna . Data tidak harus disimpan bersama-sama, data dapat dibuat satu per satu. Itu bisa tanpa batas.

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

Jika Anda membutuhkan beberapa lintasan dan seri ini tidak terlalu panjang, panggil list()saja:

>>> list(square_yield(4))
[0, 1, 4, 9]

Pilihan kata yang brilian yieldkarena kedua arti berlaku:

hasil - produksi atau penyediaan (seperti di bidang pertanian)

... berikan data berikutnya dalam seri.

hasil - memberi jalan atau melepaskan (seperti dalam kekuatan politik)

... lepaskan eksekusi CPU hingga iterator bergerak maju.

Bob Stein
sumber
194

Yield memberi Anda generator.

def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

Seperti yang Anda lihat, dalam kasus pertama foomenyimpan seluruh daftar di memori sekaligus. Ini bukan masalah besar untuk daftar dengan 5 elemen, tetapi bagaimana jika Anda ingin daftar 5 juta? Tidak hanya pemakan memori yang besar ini, juga membutuhkan banyak waktu untuk membangun pada saat fungsi dipanggil.

Dalam kasus kedua, barhanya memberi Anda generator. Generator adalah iterable - yang berarti Anda dapat menggunakannya dalam satu forlingkaran, dll, tetapi setiap nilai hanya dapat diakses satu kali. Semua nilai juga tidak disimpan dalam memori pada saat yang sama; objek generator "mengingat" di mana ia berada dalam perulangan saat terakhir Anda menyebutnya - dengan cara ini, jika Anda menggunakan iterable untuk (katakanlah) menghitung hingga 50 miliar, Anda tidak perlu menghitung hingga 50 miliar semua sekaligus dan simpan 50 miliar angka untuk dihitung.

Sekali lagi, ini adalah contoh yang cukup dibuat-buat, Anda mungkin akan menggunakan itertools jika Anda benar-benar ingin menghitung hingga 50 miliar. :)

Ini adalah kasus penggunaan generator yang paling sederhana. Seperti yang Anda katakan, ini dapat digunakan untuk menulis permutasi yang efisien, menggunakan hasil untuk mendorong segalanya melalui tumpukan panggilan alih-alih menggunakan semacam variabel tumpukan. Generator juga dapat digunakan untuk melintasi pohon khusus, dan segala hal lainnya.

RBansal
sumber
Hanya sebuah catatan - dalam Python 3, rangejuga mengembalikan generator bukan daftar, jadi Anda juga akan melihat ide yang sama, kecuali bahwa __repr__/ saya __str__ditimpa untuk menunjukkan hasil yang lebih baik, dalam hal ini range(1, 10, 2).
Notalie.
189

Ini mengembalikan generator. Saya tidak terlalu terbiasa dengan Python, tapi saya percaya itu adalah hal yang sama seperti blok iterator C # jika Anda terbiasa dengan itu.

Gagasan utamanya adalah bahwa kompiler / juru bahasa / apa pun melakukan beberapa tipu daya sehingga sejauh pemanggil yang bersangkutan, mereka dapat terus memanggil berikutnya () dan itu akan terus mengembalikan nilai - seolah-olah metode generator dijeda . Sekarang jelas Anda tidak dapat benar-benar "menghentikan" suatu metode, jadi kompiler membuat mesin keadaan agar Anda dapat mengingat di mana Anda saat ini dan seperti apa variabel lokal dll. Ini jauh lebih mudah daripada menulis iterator sendiri.

Jon Skeet
sumber
167

Ada satu jenis jawaban yang menurut saya belum diberikan, di antara banyak jawaban bagus yang menjelaskan cara menggunakan generator. Inilah jawaban teori bahasa pemrograman:

The yieldpernyataan dalam Python mengembalikan generator. Generator dengan Python adalah fungsi yang mengembalikan kelanjutan (dan khususnya jenis coroutine, tetapi kelanjutan mewakili mekanisme yang lebih umum untuk memahami apa yang sedang terjadi).

Kelanjutan dalam teori bahasa pemrograman adalah jenis perhitungan yang jauh lebih mendasar, tetapi mereka tidak sering digunakan, karena mereka sangat sulit untuk dipikirkan dan juga sangat sulit untuk diterapkan. Tetapi gagasan tentang apa yang merupakan kelanjutan, sangat mudah: itu adalah keadaan perhitungan yang belum selesai. Dalam keadaan ini, nilai variabel saat ini, operasi yang belum dilakukan, dan seterusnya, disimpan. Kemudian pada suatu saat nanti dalam program kelanjutan dapat dipanggil, sehingga variabel program diatur ulang ke keadaan itu dan operasi yang disimpan dilakukan.

Lanjutan, dalam bentuk yang lebih umum ini, dapat diimplementasikan dengan dua cara. Di call/ccjalan, tumpukan program secara harfiah disimpan dan kemudian ketika kelanjutan dipanggil, tumpukan dikembalikan.

Dalam gaya kelanjutan kelanjutan (CPS), kelanjutan hanyalah fungsi normal (hanya dalam bahasa di mana fungsi adalah kelas satu) yang dikelola oleh programmer secara eksplisit dan diteruskan ke subrutin. Dalam gaya ini, keadaan program diwakili oleh penutupan (dan variabel yang dikodekan di dalamnya) daripada variabel yang berada di suatu tempat di tumpukan. Fungsi yang mengelola aliran kontrol menerima kelanjutan sebagai argumen (dalam beberapa variasi CPS, fungsi dapat menerima beberapa kelanjutan) dan memanipulasi aliran kontrol dengan memanggilnya hanya dengan memanggilnya dan kembali sesudahnya. Contoh gaya kelanjutan passing yang sangat sederhana adalah sebagai berikut:

def save_file(filename):
  def write_file_continuation():
    write_stuff_to_file(filename)

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

Dalam contoh (sangat sederhana) ini, programmer menyimpan operasi untuk benar-benar menulis file ke dalam kelanjutan (yang berpotensi menjadi operasi yang sangat kompleks dengan banyak detail untuk dituliskan), dan kemudian meneruskan kelanjutan itu (yaitu, sebagai yang pertama- penutupan kelas) ke operator lain yang melakukan beberapa pemrosesan lebih lanjut, dan kemudian memanggilnya jika perlu. (Saya banyak menggunakan pola desain ini dalam pemrograman GUI yang sebenarnya, baik karena itu menyelamatkan saya baris kode atau, yang lebih penting, untuk mengelola aliran kontrol setelah peristiwa GUI memicu.)

Sisa dari posting ini akan, tanpa kehilangan keumuman, mengkonseptualisasikan kelanjutan sebagai CPS, karena itu jauh lebih mudah untuk dipahami dan dibaca.


Sekarang mari kita bicara tentang generator dengan Python. Generator adalah subtipe kelanjutan spesifik. Sementara kelanjutan secara umum dapat menyimpan keadaan perhitungan (yaitu, tumpukan panggilan program), generator hanya dapat menyimpan keadaan iterasi melalui iterator . Meskipun, definisi ini sedikit menyesatkan untuk kasus penggunaan generator tertentu. Contohnya:

def f():
  while True:
    yield 4

Ini jelas merupakan iterable yang beralasan yang perilakunya didefinisikan dengan baik - setiap kali generator mengulanginya, ia mengembalikan 4 (dan melakukannya selamanya). Tapi itu mungkin bukan tipe prototipikal dari iterable yang muncul di pikiran ketika memikirkan iterator (yaitu, for x in collection: do_something(x)). Contoh ini menggambarkan kekuatan generator: jika ada sesuatu yang iterator, generator dapat menyelamatkan keadaan iterasi-nya.

Untuk mengulangi: Lanjutan dapat menyimpan keadaan tumpukan program dan generator dapat menyimpan keadaan iterasi. Ini berarti bahwa kelanjutan lebih kuat daripada generator, tetapi juga bahwa generator jauh lebih mudah. Mereka lebih mudah untuk diterapkan oleh perancang bahasa, dan mereka lebih mudah bagi programmer untuk menggunakan (jika Anda punya waktu untuk membakar, cobalah membaca dan memahami halaman ini tentang kelanjutan dan panggilan / cc ).

Tetapi Anda dapat dengan mudah mengimplementasikan (dan membuat konsep) generator sebagai kasus sederhana, gaya kelanjutan lewat yang spesifik:

Setiap kali yielddipanggil, ia memberi tahu fungsi untuk mengembalikan kelanjutan. Ketika fungsi dipanggil lagi, itu dimulai dari mana saja ia tinggalkan. Jadi, dalam pseudo-pseudocode (yaitu, bukan pseudocode, tetapi bukan kode) metode generator nextpada dasarnya adalah sebagai berikut:

class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

di mana yieldkata kunci sebenarnya gula sintaksis untuk fungsi generator yang sebenarnya, pada dasarnya sesuatu seperti:

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

Ingat bahwa ini hanya pseudocode dan implementasi sebenarnya dari generator di Python lebih kompleks. Tetapi sebagai latihan untuk memahami apa yang sedang terjadi, cobalah untuk menggunakan gaya kelanjutan meneruskan untuk mengimplementasikan objek generator tanpa menggunakan yieldkata kunci.

aestrivex
sumber
152

Berikut adalah contoh dalam bahasa sederhana. Saya akan memberikan korespondensi antara konsep manusia tingkat tinggi dengan konsep Python tingkat rendah.

Saya ingin beroperasi pada urutan angka, tetapi saya tidak ingin mengganggu diri saya dengan penciptaan urutan itu, saya hanya ingin fokus pada operasi yang ingin saya lakukan. Jadi, saya melakukan hal berikut:

  • Saya menghubungi Anda dan memberi tahu Anda bahwa saya menginginkan urutan angka yang diproduksi dengan cara tertentu, dan saya memberi tahu Anda apa algoritmanya.
    Langkah ini sesuai dengan defining fungsi generator, yaitu fungsi yang mengandung a yield.
  • Beberapa saat kemudian, saya memberi tahu Anda, "Oke, bersiaplah untuk memberi tahu saya urutan angka".
    Langkah ini sesuai dengan memanggil fungsi generator yang mengembalikan objek generator. Perhatikan bahwa Anda belum memberi tahu saya nomor apa pun; Anda hanya mengambil kertas dan pensil Anda.
  • Saya bertanya kepada Anda, "beri tahu saya nomor berikutnya", dan Anda memberi tahu saya nomor pertama; setelah itu, Anda menunggu saya untuk meminta nomor berikutnya. Adalah tugas Anda untuk mengingat di mana Anda berada, nomor apa yang sudah Anda katakan, dan apa nomor berikutnya. Saya tidak peduli dengan detailnya.
    Langkah ini sesuai dengan memanggil .next()objek generator.
  • ... ulangi langkah sebelumnya, sampai ...
  • akhirnya, Anda mungkin berakhir. Anda tidak memberi tahu saya nomor; Anda hanya berteriak, "pegang kudamu! Aku sudah selesai! Tidak ada lagi angka!"
    Langkah ini sesuai dengan objek generator yang mengakhiri tugasnya, dan menaikkan StopIterationpengecualian Fungsi generator tidak perlu menaikkan pengecualian. Ini dinaikkan secara otomatis ketika fungsi berakhir atau mengeluarkan a return.

Inilah yang generator lakukan (fungsi yang berisi a yield); itu mulai mengeksekusi, berhenti setiap kali ia melakukan yield, dan ketika diminta untuk .next()nilai itu berlanjut dari titik itu yang terakhir. Ini sangat cocok dengan desain dengan protokol iterator Python, yang menjelaskan bagaimana cara meminta nilai secara berurutan.

Pengguna protokol iterator yang paling terkenal adalah forperintah dengan Python. Jadi, setiap kali Anda melakukan:

for item in sequence:

tidak masalah apakah sequencedaftar, string, kamus atau objek generator seperti dijelaskan di atas; hasilnya sama: Anda membaca item dari urutan satu per satu.

Perhatikan bahwa deffungsi ining yang berisi yieldkata kunci bukan satu-satunya cara untuk membuat generator; itu hanya cara termudah untuk membuatnya.

Untuk informasi yang lebih akurat, baca tentang jenis iterator , pernyataan hasil dan generator dalam dokumentasi Python.

tzot
sumber
130

Sementara banyak jawaban menunjukkan mengapa Anda akan menggunakan yielduntuk membuat generator, ada lebih banyak kegunaan untuk yield. Sangat mudah untuk membuat coroutine, yang memungkinkan berlalunya informasi antara dua blok kode. Saya tidak akan mengulangi salah satu contoh bagus yang telah diberikan tentang penggunaan yielduntuk membuat generator.

Untuk membantu memahami apa yang yielddilakukan dalam kode berikut, Anda dapat menggunakan jari Anda untuk melacak siklus melalui kode apa pun yang memiliki yield. Setiap kali jari Anda menyentuh yield, Anda harus menunggu untuk a nextatau a senddimasukkan. Ketika a nextdipanggil, Anda menelusuri kode sampai Anda menekan yield... kode di sebelah kanan yielddievaluasi dan dikembalikan ke pemanggil ... lalu Anda menunggu. Ketika nextdipanggil lagi, Anda melakukan loop lain melalui kode. Namun, Anda akan mencatat bahwa di coroutine, yieldjuga dapat digunakan dengan send... yang akan mengirim nilai dari pemanggil ke fungsi menghasilkan. Jika sebuahsend diberikan, makayieldmenerima nilai yang dikirim, dan meludahkannya di sisi kiri ... kemudian penelusuran melalui kode berlanjut hingga Anda menekan yieldlagi (mengembalikan nilai di akhir, seolah-olah nextdipanggil).

Sebagai contoh:

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()
Mike McKerns
sumber
Imut! Sebuah trampolin (dalam arti Lisp). Tidak sering orang melihat itu!
00prometheus
129

Ada yieldkegunaan dan makna lain (sejak Python 3.3):

yield from <expr>

Dari PEP 380 - Sintaks untuk Mendelegasikan ke Subgenerator :

Sintaks diusulkan untuk generator untuk mendelegasikan sebagian operasinya ke generator lain. Ini memungkinkan bagian kode yang berisi 'hasil' diperhitungkan dan ditempatkan di generator lain. Selain itu, subgenerator diizinkan untuk kembali dengan nilai, dan nilai tersebut tersedia untuk generator pendelegasian.

Sintaks baru juga membuka beberapa peluang untuk optimasi ketika satu generator menghasilkan kembali nilai yang dihasilkan oleh yang lain.

Selain itu ini akan memperkenalkan (sejak Python 3.5):

async def new_coroutine(data):
   ...
   await blocking_action()

untuk menghindari coroutine dikacaukan dengan generator biasa (hari yieldini digunakan di keduanya).

Sławomir Lenart
sumber
117

Semua jawaban bagus, namun agak sulit bagi pemula.

Saya berasumsi Anda telah mempelajari returnpernyataan itu.

Sebagai analogi, returndan yieldkembar. returnberarti 'kembali dan berhenti' sedangkan 'menghasilkan` berarti' kembali, tetapi lanjutkan '

  1. Cobalah dapatkan num_list bersama return.
def num_list(n):
    for i in range(n):
        return i

Menjalankannya:

In [5]: num_list(3)
Out[5]: 0

Lihat, Anda hanya mendapatkan satu nomor daripada daftar mereka. returntidak pernah membiarkan Anda menang dengan bahagia, hanya menerapkan sekali dan berhenti.

  1. Datang yield

Ganti returndengan yield:

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990>

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

Sekarang, Anda menang untuk mendapatkan semua angka.

Membandingkan returnyang berjalan sekali dan berhenti, yieldberjalan kali yang Anda rencanakan. Anda dapat mengartikan returnsebagai return one of them, dan yieldsebagai return all of them. Ini disebut iterable.

  1. Satu langkah lagi kita bisa menulis ulang yieldpernyataanreturn
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

In [16]: num_list(3)
Out[16]: [0, 1, 2]

Itu inti tentang yield .

Perbedaan antara returnoutput daftar dan objekyield output adalah:

Anda akan selalu mendapatkan [0, 1, 2] dari objek daftar tetapi hanya bisa mengambilnya dari ' yieldoutput objek ' sekali. Jadi, ia memiliki generatorobjek nama baru seperti yang ditampilkan diOut[11]: <generator object num_list at 0x10327c990> .

Kesimpulannya, sebagai metafora untuk grok itu:

  • return dan yieldkembar
  • listdan generatorkembar
Kalkulus
sumber
Ini dapat dimengerti, tetapi satu perbedaan utama adalah bahwa Anda dapat memiliki banyak hasil dalam suatu fungsi / metode. Analogi itu benar-benar hancur pada saat itu. Yield mengingat tempatnya dalam suatu fungsi, jadi saat berikutnya Anda memanggil next (), fungsi Anda berlanjut ke yang berikutnya yield. Ini penting, saya pikir, dan harus diungkapkan.
Mike S
104

Berikut adalah beberapa contoh Python tentang bagaimana sebenarnya mengimplementasikan generator seolah-olah Python tidak memberikan gula sintaksis untuk mereka:

Sebagai generator Python:

from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

Menggunakan penutupan leksikal bukan generator

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

Menggunakan penutupan objek alih-alih generator (karena ClosuresAndObjectsAreEquivalent )

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1

    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r

assert [1,1,2,3,5] == ftake(fib_gen3(), 5)
Dustin Getz
sumber
97

Saya akan memposting "baca halaman 19 dari 'Python: Essential Reference' Beazley untuk deskripsi singkat generator", tetapi sudah banyak yang memposting deskripsi yang baik.

Juga, perhatikan bahwa yielddapat digunakan dalam coroutine sebagai dual dari penggunaannya dalam fungsi generator. Meskipun tidak sama dengan penggunaan potongan kode Anda, (yield)dapat digunakan sebagai ekspresi dalam suatu fungsi. Ketika seorang pemanggil mengirim nilai ke metode menggunakan send()metode, maka coroutine akan mengeksekusi sampai (yield)pernyataan berikutnya ditemui.

Generator dan coroutine adalah cara yang keren untuk mengatur aplikasi tipe aliran data. Saya pikir akan bermanfaat mengetahui tentang penggunaan lain dari yieldpernyataan dalam fungsi.

Johnzachary
sumber
97

Dari sudut pandang pemrograman, iterator diimplementasikan sebagai thunks .

Untuk mengimplementasikan iterator, generator, dan kumpulan utas untuk eksekusi bersamaan, dll. Sebagai thunks (juga disebut fungsi anonim), seseorang menggunakan pesan yang dikirim ke objek penutupan, yang memiliki dispatcher, dan dispatcher menjawab "pesan".

http://en.wikipedia.org/wiki/Message_passing

" selanjutnya " adalah pesan yang dikirim ke penutupan, dibuat oleh panggilan " iter ".

Ada banyak cara untuk mengimplementasikan perhitungan ini. Saya menggunakan mutasi, tetapi mudah melakukannya tanpa mutasi, dengan mengembalikan nilai saat ini dan yielder berikutnya.

Ini adalah demonstrasi yang menggunakan struktur R6RS, tetapi semantiknya benar-benar identik dengan Python. Ini adalah model perhitungan yang sama, dan hanya perubahan sintaks yang diperlukan untuk menulis ulang dengan Python.

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
->
alinsoar
sumber
84

Ini adalah contoh sederhana:

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

Keluaran:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

Saya bukan pengembang Python, tetapi sepertinya bagi saya yield memegang posisi aliran program dan loop berikutnya mulai dari posisi "hasil". Sepertinya sedang menunggu di posisi itu, dan tepat sebelum itu, mengembalikan nilai di luar, dan waktu berikutnya terus bekerja.

Tampaknya menjadi kemampuan yang menarik dan menyenangkan: D

Engin OZTURK
sumber
Anda benar. Tapi apa efeknya pada aliran yaitu melihat perilaku "hasil"? Saya dapat mengubah algoritma atas nama matematika. Apakah akan membantu untuk mendapatkan penilaian "hasil" yang berbeda?
Engin OZTURK
68

Inilah gambaran mental tentang apa yang yielddilakukannya.

Saya suka menganggap utas sebagai memiliki tumpukan (bahkan ketika itu tidak diterapkan seperti itu).

Ketika fungsi normal dipanggil, ia menempatkan variabel lokalnya di stack, melakukan beberapa perhitungan, kemudian membersihkan stack dan kembali. Nilai-nilai variabel lokalnya tidak pernah terlihat lagi.

Dengan suatu yieldfungsi, ketika kodenya mulai berjalan (yaitu setelah fungsi dipanggil, mengembalikan objek generator, yang next()metodenya kemudian dipanggil), ia juga menempatkan variabel lokalnya ke stack dan menghitung untuk sementara waktu. Tetapi kemudian, ketika ia mencapai yieldpernyataan, sebelum membersihkan bagian dari tumpukan dan kembali, ia mengambil snapshot variabel lokalnya dan menyimpannya dalam objek generator. Itu juga menuliskan tempat di mana ia saat ini dalam kode (yaitu yieldpernyataan tertentu ).

Jadi itu semacam fungsi beku yang tergantung pada generator.

Ketika next()dipanggil selanjutnya, ia mengambil barang-barang fungsi ke stack dan menghidupkan kembali animasinya. Fungsi terus menghitung dari tempat itu pergi, tidak menyadari fakta bahwa ia baru saja menghabiskan keabadian dalam penyimpanan dingin.

Bandingkan contoh-contoh berikut:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

Ketika kita memanggil fungsi kedua, ia berperilaku sangat berbeda dengan yang pertama. The yieldPernyataan mungkin tidak terjangkau, tetapi jika itu di mana saja hadir, perubahan sifat dari apa yang kita hadapi.

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

Memanggil yielderFunction()tidak menjalankan kode, tetapi membuat generator keluar dari kode. (Mungkin ide yang baik untuk menyebutkan hal-hal seperti itu dengan yielderawalan agar mudah dibaca.)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

The gi_codedan gi_framebidang yang mana keadaan beku disimpan. Menjelajahi mereka dengan dir(..), kita dapat mengkonfirmasi bahwa model mental kita di atas dapat dipercaya.

Evgeni Sergeev
sumber
59

Seperti setiap jawaban yang disarankan, yielddigunakan untuk membuat generator urutan. Ini digunakan untuk menghasilkan beberapa urutan secara dinamis. Misalnya, saat membaca file baris demi baris di jaringan, Anda dapat menggunakan yieldfungsi sebagai berikut:

def getNextLines():
   while con.isOpen():
       yield con.read()

Anda dapat menggunakannya dalam kode Anda sebagai berikut:

for line in getNextLines():
    doSomeThing(line)

Eksekusi Kontrol, transfer gotcha

Kontrol eksekusi akan ditransfer dari getNextLines () ke forloop ketika hasil dijalankan. Jadi, setiap kali getNextLines () dipanggil, eksekusi dimulai dari titik di mana ia dijeda terakhir kali.

Jadi singkatnya, fungsi dengan kode berikut

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

akan dicetak

"first time"
"second time"
"third time"
"Now some useful value 12"
Mangu Singh Rajpurohit
sumber
59

Contoh mudah untuk memahami apa itu: yield

def f123():
    for _ in range(4):
        yield 1
        yield 2


for i in f123():
    print (i)

Outputnya adalah:

1 2 1 2 1 2 1 2
Gavriel Cohen
sumber
5
apakah Anda yakin tentang output itu? bukankah itu hanya akan dicetak pada satu baris jika Anda menjalankan pernyataan cetak itu menggunakan print(i, end=' ')? Kalau tidak, saya percaya perilaku default akan menempatkan setiap nomor pada baris baru
user9074332
@ user9074332, Anda benar, tetapi ini ditulis pada satu baris untuk memudahkan pemahaman
Gavriel Cohen
57

(Jawaban saya di bawah ini hanya berbicara dari perspektif menggunakan generator Python, bukan implementasi mekanisme generator yang mendasarinya , yang melibatkan beberapa trik stack dan manipulasi heap.)

Ketika yielddigunakan alih-alih returndalam fungsi python, fungsi itu diubah menjadi sesuatu yang disebut khusus generator function. Fungsi itu akan mengembalikan objek generatorbertipe. Kata yieldkunci adalah bendera untuk memberi tahu kompiler python untuk memperlakukan fungsi tersebut secara khusus. Fungsi normal akan berakhir setelah beberapa nilai dikembalikan darinya. Tetapi dengan bantuan kompiler, fungsi generator dapat dianggap sebagai resume. Artinya, konteks eksekusi akan dipulihkan dan eksekusi akan berlanjut dari yang terakhir kali dijalankan. Sampai Anda secara eksplisit memanggil kembali, yang akan memunculkan StopIterationpengecualian (yang juga merupakan bagian dari protokol iterator), atau mencapai akhir fungsi. Saya menemukan banyak referensi tentang generatortetapi ini satudari functional programming perspectiveadalah yang paling mudah dicerna.

(Sekarang saya ingin berbicara tentang alasan di balik generator, dan iteratorberdasarkan pada pemahaman saya sendiri. Saya harap ini dapat membantu Anda memahami motivasi penting dari iterator dan generator. Konsep tersebut muncul dalam bahasa lain juga seperti C #.)

Seperti yang saya mengerti, ketika kita ingin memproses banyak data, kita biasanya menyimpan data di suatu tempat dan kemudian memprosesnya satu per satu. Tetapi pendekatan naif ini bermasalah. Jika volume data sangat besar, sangat mahal untuk menyimpannya secara keseluruhan sebelumnya. Jadi alih-alih menyimpan datasendiri secara langsung, mengapa tidak menyimpan semacam metadatatidak langsung, yaituthe logic how the data is computed .

Ada 2 pendekatan untuk membungkus metadata tersebut.

  1. Pendekatan OO, kami bungkus metadata as a class. Inilah yang disebut iteratoryang mengimplementasikan protokol iterator (yaitu __next__(), dan __iter__()metode). Ini juga merupakan pola desain iterator yang biasa dilihat .
  2. Pendekatan fungsional, kami bungkus metadata as a function. Inilah yang disebut generator function. Tetapi di bawah kap, yang dikembalikan generator objectmasih IS-Aiterator karena juga mengimplementasikan protokol iterator.

Either way, sebuah iterator dibuat, yaitu beberapa objek yang dapat memberi Anda data yang Anda inginkan. Pendekatan OO mungkin agak rumit. Pokoknya, mana yang harus digunakan terserah Anda.

smwikipedia
sumber
54

Singkatnya, yieldpernyataan mengubah fungsi Anda menjadi sebuah pabrik yang menghasilkan objek khusus yang disebut generatormembungkus di sekitar tubuh fungsi asli Anda. Ketika generatoriterasi, itu mengeksekusi fungsi Anda sampai mencapai berikutnya yieldkemudian menunda eksekusi dan mengevaluasi nilai yang diteruskan yield. Itu mengulangi proses ini pada setiap iterasi sampai jalan eksekusi keluar dari fungsi. Contohnya,

def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

hanya keluaran

one
two
three

Kekuatan berasal dari menggunakan generator dengan loop yang menghitung urutan, generator mengeksekusi loop berhenti setiap kali untuk 'menghasilkan' hasil perhitungan berikutnya, dengan cara ini menghitung daftar dengan cepat, manfaatnya adalah memori disimpan untuk perhitungan yang sangat besar

Katakanlah Anda ingin membuat rangefungsi Anda sendiri yang menghasilkan rentang angka yang dapat diubah, Anda bisa melakukannya seperti itu,

def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

dan gunakan seperti ini;

for i in myRangeNaive(10):
    print i

Tetapi ini tidak efisien karena

  • Anda membuat array yang hanya Anda gunakan sekali (ini menghabiskan memori)
  • Kode ini sebenarnya mengulangi array itu dua kali! :(

Untungnya Guido dan timnya cukup dermawan untuk mengembangkan generator sehingga kami bisa melakukan ini;

def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i

Sekarang pada setiap iterasi fungsi pada generator yang disebut next()menjalankan fungsi sampai ia mencapai pernyataan 'hasil' di mana ia berhenti dan 'menghasilkan' nilai atau mencapai akhir fungsi. Dalam hal ini pada panggilan pertama, next()jalankan hingga pernyataan hasil dan hasil 'n', pada panggilan berikutnya akan mengeksekusi pernyataan kenaikan, melompat kembali ke 'sementara', mengevaluasi itu, dan jika benar, itu akan berhenti dan menghasilkan 'n' lagi, itu akan terus seperti itu sampai kondisi sementara mengembalikan false dan generator melompat ke ujung fungsi.

redbandit
sumber
53

Hasil adalah suatu objek

A returndalam suatu fungsi akan mengembalikan nilai tunggal.

Jika Anda ingin fungsi mengembalikan set nilai yang besar , gunakan yield.

Lebih penting lagi, yieldadalah penghalang .

seperti penghalang dalam bahasa CUDA, itu tidak akan mentransfer kontrol sampai selesai.

Artinya, itu akan menjalankan kode dalam fungsi Anda dari awal hingga hits yield. Kemudian, itu akan mengembalikan nilai pertama dari loop.

Kemudian, setiap panggilan lain akan menjalankan loop yang telah Anda tulis dalam fungsi sekali lagi, mengembalikan nilai berikutnya sampai tidak ada nilai untuk kembali.

Kaleem Ullah
sumber
52

Banyak orang menggunakan returndaripada yield, tetapi dalam beberapa kasus yieldbisa lebih efisien dan lebih mudah untuk dikerjakan.

Berikut adalah contoh yang yieldpasti terbaik untuk:

kembali (berfungsi)

import random

def return_dates():
    dates = [] # With 'return' you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates

hasil (dalam fungsi)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # 'yield' makes a generator automatically which works
                   # in a similar way. This is much more efficient.

Fungsi panggilan

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in dates_generator:
    print(i)

Kedua fungsi melakukan hal yang sama, tetapi yieldmenggunakan tiga baris alih-alih lima dan memiliki satu variabel yang kurang perlu dikhawatirkan.

Ini adalah hasil dari kode:

Keluaran

Seperti yang Anda lihat, kedua fungsi melakukan hal yang sama. Satu-satunya perbedaan adalah return_dates()memberikan daftar dan yield_dates()memberikan generator.

Contoh kehidupan nyata adalah sesuatu seperti membaca file baris demi baris atau jika Anda hanya ingin membuat generator.

Tom Fuller
sumber
43

yieldseperti elemen kembali untuk suatu fungsi. Perbedaannya adalah, yieldelemen mengubah fungsi menjadi generator. Generator berperilaku seperti fungsi sampai sesuatu 'dihasilkan'. Generator berhenti sampai dipanggil berikutnya, dan berlanjut dari titik yang persis sama dengan saat dimulai. Anda bisa mendapatkan urutan semua nilai 'yang dihasilkan' dalam satu, dengan menelepon list(generator()).

Will Dereham
sumber
41

Kata yieldkunci hanya mengumpulkan hasil yang kembali. Pikirkan yieldsepertireturn +=

Bahtiyar Özdere
sumber
36

Berikut ini adalah yieldpendekatan berbasis sederhana , untuk menghitung seri fibonacci, menjelaskan:

def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

Ketika Anda memasukkan ini ke dalam REPL Anda dan kemudian mencoba dan menyebutnya, Anda akan mendapatkan hasil membingungkan:

>>> fib()
<generator object fib at 0x7fa38394e3b8>

Ini karena adanya yieldsinyal ke Python bahwa Anda ingin membuat generator , yaitu, objek yang menghasilkan nilai berdasarkan permintaan.

Jadi, bagaimana Anda menghasilkan nilai-nilai ini? Ini dapat dilakukan secara langsung dengan menggunakan fungsi bawaannext , atau, secara tidak langsung dengan mengumpankannya ke konstruk yang mengonsumsi nilai.

Menggunakan next()fungsi bawaan, Anda secara langsung memanggil .next/ __next__, memaksa generator untuk menghasilkan nilai:

>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

Secara tidak langsung, jika Anda memberikan fibke forloop, listpenginisialisasi, tuplepenginisialisasi, atau apa pun yang mengharapkan objek yang menghasilkan / menghasilkan nilai, Anda akan "mengkonsumsi" generator sampai tidak ada lagi nilai yang dapat dihasilkan olehnya (dan mengembalikan) :

results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib

Demikian pula dengan tuplepenginisialisasi:

>>> tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

Generator berbeda dari fungsi dalam arti malas. Ini menyelesaikan ini dengan mempertahankan keadaan lokal dan memungkinkan Anda untuk melanjutkan kapan pun Anda perlu.

Ketika Anda pertama kali memanggil fibdengan memanggilnya:

f = fib()

Python mengkompilasi fungsi, menemukan yieldkata kunci dan hanya mengembalikan objek generator kembali kepada Anda. Sepertinya tidak terlalu membantu.

Ketika Anda kemudian meminta itu menghasilkan nilai pertama, langsung atau tidak langsung, itu mengeksekusi semua pernyataan yang ditemukannya, sampai bertemu a yield, itu kemudian menghasilkan kembali nilai yang Anda berikan yielddan jeda. Untuk contoh yang menunjukkan ini dengan lebih baik, mari kita gunakan beberapa printpanggilan (ganti dengan print "text"jika pada Python 2):

def yielder(value):
    """ This is an infinite generator. Only use next on it """ 
    while 1:
        print("I'm going to generate the value for you")
        print("Then I'll pause for a while")
        yield value
        print("Let's go through it again.")

Sekarang, masukkan dalam REPL:

>>> gen = yielder("Hello, yield!")

Anda memiliki objek generator sekarang menunggu perintah untuk itu menghasilkan nilai. Gunakan nextdan lihat apa yang dicetak:

>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

Hasil yang tidak dikutip adalah yang dicetak. Hasil yang dikutip adalah apa yang dikembalikan dari yield. Telepon nextlagi sekarang:

>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

Generator ingat itu dijeda yield valuedan dilanjutkan dari sana. Pesan berikutnya dicetak dan pencarian yieldpernyataan untuk menghentikannya dilakukan lagi (karena whileloop).

Dimitris Fasarakis Hilliard
sumber