Pertanyaan Apa yang dilakukan kata kunci “hasil”?


Apa gunanya yield kata kunci dengan Python? Apa fungsinya?

Misalnya, saya mencoba memahami kode ini1:

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 adalah si penelepon:

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_candidates disebut? Apakah daftar kembali? Satu elemen? Apakah itu dipanggil lagi? Kapan panggilan selanjutnya akan berhenti?


1. Kode berasal dari Jochen Schulz (jrschulz), yang membuat pustaka Python besar untuk ruang metrik. Ini adalah tautan ke sumber lengkap: Modul mspace.


8311
2017-10-23 22:21


asal


Jawaban:


Untuk mengerti apa yield tidak, Anda harus mengerti apa generator adalah. Dan sebelum generator datang iterables.

Iterables

Ketika 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

mylist adalah iterable. Saat Anda menggunakan pemahaman daftar, Anda membuat daftar, dan dengan demikian dapat diulang:

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

Semua yang bisa Anda gunakan "for... in..."pada adalah iterable; lists, strings, file ...

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 Anda hanya dapat melakukan iterasi satu kali. 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 kamu digunakan () dari pada []. Tapi kamu tidak bisa melakukan for i in mygenerator untuk kedua kalinya karena generator hanya dapat digunakan satu kali: mereka menghitung 0, lalu melupakannya dan menghitung 1, dan mengakhiri perhitungan 4, satu per satu.

Menghasilkan

yield adalah kata kunci yang digunakan seperti return, kecuali fungsinya 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 sekumpulan besar nilai yang hanya perlu Anda baca satu kali.

Untuk menguasai yieldAnda harus mengerti itu ketika Anda memanggil fungsi, kode yang Anda tulis di dalam fungsi tidak berjalan. Fungsi hanya mengembalikan objek generator, ini agak rumit :-)

Kemudian, kode Anda akan dijalankan setiap kali for menggunakan generator.

Sekarang bagian yang sulit:

Pertama kali for memanggil objek generator yang dibuat dari fungsi Anda, ia akan menjalankan kode di fungsi Anda dari awal hingga muncul yield, maka akan mengembalikan nilai pertama dari loop. Kemudian, setiap panggilan lainnya akan menjalankan loop yang telah Anda tulis dalam fungsi sekali lagi, dan mengembalikan nilai berikutnya, hingga tidak ada nilai yang dikembalikan.

Generator dianggap kosong setelah fungsi berjalan, tetapi tidak memukul yield lagi. Bisa jadi karena loop sudah berakhir, atau karena Anda tidak memuaskan "if/else" lagi.


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 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 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 candidates 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 berisi beberapa bagian pintar:

  • Loop iterates pada daftar, tetapi daftar mengembang sementara loop sedang iterated :-) Ini adalah cara ringkas untuk melewati semua data yang bersarang bahkan jika itu agak berbahaya karena Anda dapat berakhir dengan loop tak terbatas. Pada kasus ini, candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) menghabiskan semua nilai generator, tetapi while terus membuat objek generator baru yang akan menghasilkan nilai yang berbeda dari yang sebelumnya karena tidak diterapkan pada node yang sama.

  • Itu extend() metode adalah metode objek daftar yang mengharapkan iterable dan menambahkan nilainya ke 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 itu mendapat generator, yang bagus karena:

  1. Anda tidak perlu membaca nilainya dua kali.
  2. Anda mungkin memiliki banyak anak dan Anda tidak ingin semuanya disimpan dalam memori.

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

Anda dapat berhenti di sini, atau membaca sedikit untuk melihat penggunaan generator yang canggih:

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, gunakanprint(corner_street_atm.__next__()) atau print(next(corner_street_atm))

Ini dapat berguna untuk berbagai hal seperti mengontrol 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 liner satu? Map / Zip tanpa membuat daftar lain?

Lalu, adil import itertools.

Sebuah contoh? Mari kita lihat kemungkinan kedatangan untuk perlombaan 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 mengimplikasikan iterables (mengimplementasikan __iter__() metode) dan iterator (menerapkan __next__() metode). Iterables adalah objek apa pun yang bisa Anda dapatkan dari iterator. Iterators adalah objek yang memungkinkan Anda melakukan iterasi pada iterables.

Ada lebih banyak tentang hal ini di artikel ini bagaimana for loop bekerja.


12173
2017-10-23 22:48



Pintasan ke Grokking  yield

Saat Anda melihat fungsi dengan yield pernyataan, menerapkan trik mudah ini untuk memahami apa yang akan terjadi:

  1. Masukkan sebuah garis result = [] pada awal fungsi.
  2. Gantilah masing-masing yield expr dengan result.append(expr).
  3. Masukkan sebuah garis return result di bagian bawah fungsi.
  4. Yay - tidak lagi yield pernyataan! Baca dan cari tahu kode.
  5. Bandingkan fungsi dengan definisi asli.

Trik ini mungkin memberi Anda ide tentang logika di balik fungsi, tetapi apa yang sebenarnya terjadi dengan yield berbeda secara signifikan bahwa apa yang terjadi dalam pendekatan berdasarkan daftar. Dalam banyak kasus, pendekatan hasil akan jauh lebih efisien dan lebih cepat juga. Dalam kasus lain, trik ini akan membuat Anda terjebak dalam lingkaran tak terbatas, meskipun fungsi aslinya bekerja dengan baik. Baca terus untuk mempelajari lebih lanjut ...

Jangan membingungkan Iterables, Iterators, dan Generator Anda

Pertama, itu protokol iterator - ketika kamu menulis

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

Python melakukan dua langkah berikut:

  1. Mendapat iterator untuk mylist:

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

    [Ini adalah langkah yang kebanyakan orang lupa ceritakan pada Anda]

  2. Menggunakan iterator untuk mengulang item:

    Terus panggil next() metode pada iterator kembali dari langkah 1. Nilai kembalian dari next() ditugaskan untuk x dan badan loop dijalankan. Jika ada pengecualian StopIteration dibangkitkan dari dalam next(), artinya tidak ada lagi nilai di iterator dan loop keluar.

Kebenarannya adalah Python melakukan dua langkah di atas kapan pun ia mau putaran isi dari suatu objek - sehingga bisa menjadi loop, tetapi bisa juga kode seperti otherlist.extend(mylist) (dimana otherlist adalah daftar Python).

Sini mylist adalah iterable karena mengimplementasikan protokol iterator. Di kelas yang ditentukan pengguna, Anda dapat menerapkan __iter__() metode untuk membuat instance dari kelas Anda dapat diulang. Metode ini harus mengembalikan sebuah iterator. Sebuah iterator adalah objek dengan a next() metode. Adalah mungkin untuk menerapkan keduanya __iter__() dan next() di kelas yang sama, dan punya __iter__() kembali self. Ini akan bekerja untuk kasus-kasus sederhana, tetapi tidak ketika Anda ingin dua iterator mengulang objek yang sama pada saat yang bersamaan.

Jadi itulah protokol iterator, banyak objek mengimplementasikan protokol ini:

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

Perhatikan bahwa a for loop tidak tahu jenis objek apa yang berhubungan dengan ini - ia hanya mengikuti protokol iterator, dan dengan senang hati mendapatkan item demi item saat ia memanggil next(). Daftar built-in mengembalikan barang-barang mereka satu per satu, kamus mengembalikan kunci satu per satu, file mengembalikan garis satu per satu, dll. Dan generator kembali ... di sanalah tempatnya yield datang di:

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

Dari pada yield pernyataan, jika Anda memiliki tiga return pernyataan dalam f123() hanya yang pertama yang dieksekusi, dan fungsinya akan keluar. Tapi f123() bukan fungsi biasa. Kapan f123() disebut, itu tidak kembalikan nilai apa pun dalam laporan imbal hasil! Ini mengembalikan objek generator. Juga, fungsi tidak benar-benar keluar - itu masuk ke keadaan ditangguhkan. Ketika for loop mencoba untuk mengulang objek generator, fungsi dilanjutkan dari statusnya yang ditangguhkan pada baris berikutnya setelah yield sebelumnya kembali dari, mengeksekusi baris kode berikutnya, dalam hal ini a yield pernyataan, dan mengembalikan itu sebagai item berikutnya. Ini terjadi sampai fungsi keluar, pada saat mana generator naik StopIteration, dan loop keluar.

Jadi objek generator adalah semacam adaptor - pada satu ujungnya memamerkan protokol iterator, dengan mengekspos __iter__() dan next() metode untuk menjaga for lingkaran bahagia. Namun di sisi lain, ia menjalankan fungsi hanya cukup untuk mendapatkan nilai berikutnya dari itu, dan mengembalikannya dalam mode yang ditangguhkan.

Mengapa Menggunakan Generator?

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


1635
2017-10-25 21:22



Anggap saja seperti ini:

Sebuah iterator hanyalah istilah yang terdengar mewah untuk objek yang memiliki metode next (). Jadi fungsi hasil akhirnya menjadi sesuatu 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 oleh interpreter 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, for loop dapat ditulis ulang ke ini:

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

Apakah itu lebih masuk akal atau lebih membingungkan Anda? :)

Saya harus mencatat bahwa ini aku s penyederhanaan yang berlebihan untuk tujuan ilustratif. :)


396
2017-10-23 22:28



Itu yield kata kunci direduksi menjadi dua fakta sederhana:

  1. Jika kompiler mendeteksi yield kata kunci di mana saja di dalam fungsi, fungsi itu tidak lagi mengembalikan melalui return pernyataan. Sebagai gantinya, saya t segera mengembalikan a malas "pending list" objek disebut generator
  2. Generator dapat diulang. Apa itu iterable? Ini seperti sebuah list atau set atau range atau dict-view, dengan protokol built-in untuk mengunjungi setiap elemen dalam urutan tertentu.

Pendeknya: generator adalah daftar malas, tertunda-tertunda, dan yield pernyataan memungkinkan Anda menggunakan notasi fungsi untuk memprogram nilai daftar generator harus secara bertahap memuntahkan.

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 mendefinisikan fungsi makeRange itu seperti Python range. Panggilan makeRange(n) PENGEMBALIAN 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 memasukkannya ke dalamnya list() (seperti halnya Anda dapat mengulangi):

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

Membandingkan contoh untuk "baru saja mengembalikan daftar"

Contoh di atas dapat dianggap sebagai sekadar 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 utama; lihat bagian terakhir.


Bagaimana Anda bisa menggunakan generator

Sebuah iterable adalah bagian terakhir dari pemahaman daftar, dan semua generator adalah iterable, jadi mereka sering digunakan seperti ini:

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

Untuk mendapatkan penghasil genset yang lebih baik, Anda dapat bermain-main dengan itertools modul (pastikan untuk digunakan chain.from_iterable daripada chain saat digaransi). Misalnya, Anda bahkan dapat menggunakan generator untuk menerapkan daftar malas yang tak terbatas panjangnya itertools.count(). Anda bisa menerapkannya sendiri def enumerate(iterable): zip(count(), iterable), atau alternatif melakukannya dengan yieldkata kunci dalam while-loop.

Harap dicatat: generator sebenarnya dapat digunakan untuk lebih banyak hal, seperti mengimplementasikan coroutines 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.


Dibalik layar

Ini adalah bagaimana "protokol iterasi Python" berfungsi. Artinya, apa yang sedang terjadi saat Anda melakukannya list(makeRange(5)). Ini adalah apa yang saya gambarkan sebelumnya sebagai "daftar incaran malas".

>>> 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 objek .next() fungsi, yang merupakan bagian dari "protokol iterasi" dan ditemukan pada semua iterator. Anda dapat menggunakan secara manual next() fungsi (dan bagian lain dari protokol iterasi) untuk menerapkan 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-berbicara, sebuah iterable adalah objek apa pun yang "memahami konsep for-loop" seperti daftar [1,2,3], dan sebuah iterator adalah contoh spesifik dari yang diminta untuk loop seperti [1,2,3].__iter__(). SEBUAH generator sama persis dengan iterator apa pun, kecuali cara penulisannya (dengan sintaks fungsi).

Saat 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 sebuah iterator; artinya, ini adalah satu kali penggunaan. 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 menakutkan) dapat digunakan itertools.tee jika benar-benar diperlukan, karena iterator Python yang dapat dipertukarkan SEMANGAT proposal standar telah ditangguhkan.


346
2018-06-19 06:33



Apa yang dimaksud dengan yield kata kunci dilakukan dengan Python?

Jawaban Garis Besar / Ringkasan

  • Fungsi dengan yield, saat dipanggil, mengembalikan a Generator.
  • Generator adalah iterator karena mereka mengimplementasikan protokol iterator, sehingga Anda dapat mengulanginya.
  • Generator juga bisa mengirim informasi, membuatnya secara konseptual coroutine.
  • Dengan Python 3, Anda bisa melimpahkan dari satu generator ke yang lain di kedua arah dengan yield from.
  • (Lampiran mengkritik beberapa jawaban, termasuk yang paling atas, dan membahas penggunaan return di generator.)

Generator:

yield hanya dalam hukum definisi fungsi, dan masuknya yield dalam definisi fungsi membuatnya mengembalikan generator.

Gagasan untuk generator berasal dari bahasa lain (lihat catatan kaki 1) dengan berbagai penerapan. Pada Generator Python, eksekusi kode tersebut beku pada titik hasil. Ketika generator dipanggil (metode dibahas di bawah) eksekusi dilanjutkan dan kemudian membeku pada hasil berikutnya.

yield menyediakan sebuah cara mudah mengimplementasikan protokol iterator, ditentukan oleh dua metode berikut: __iter__ dan next (Python 2) atau __next__ (Python 3). Kedua metode tersebut membuat objek iterator yang dapat Anda ketikkan-cek dengan Iterator Basis Abstrak Kelas dari collections modul.

>>> 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 dapat mengetik seperti ini:

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

Fitur dari sebuah Iterator  adalah sekali kelelahan, Anda tidak dapat menggunakan kembali atau mengatur ulang:

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

Anda harus membuatnya lagi jika Anda 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 dalam Python 2), Anda dapat menggunakan yield from:

def func(an_iterable):
    yield from an_iterable

Namun, yield from juga memungkinkan untuk delegasi ke subgenerator, yang akan dijelaskan di bagian berikut pada delegasi koperasi dengan sub-coroutines.

Coroutines:

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

Berikut ini contohnya, catat received variabel, 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 mengantri generator dengan fungsi bawaan, next. Itu akan panggil yang sesuai next atau __next__ metode, tergantung pada versi Python yang Anda gunakan:

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

Dan sekarang kita bisa mengirim data ke generator. (Mengirim None aku s sama seperti memanggil next.):

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

Delegasi Koperasi untuk Sub-Coroutine dengan yield from

Sekarang, ingat itu yield from tersedia dalam Python 3. Ini memungkinkan kita untuk mendelegasikan coroutines 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 fungsi ke sub-generator dan itu 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 dari yield from di PEP 380.

Metode Lain: dekat dan melempar

Itu close metode menimbulkan GeneratorExit pada titik fungsi Eksekusi dibekukan. Ini juga akan dipanggil oleh __del__ jadi kamu 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 dimaksud dengan yield kata kunci dilakukan dengan Python?

Ternyata itu yield melakukan banyak hal. Saya yakin saya bisa menambahkan lebih banyak lagi contoh menyeluruh untuk ini. Jika Anda ingin lebih banyak atau memiliki beberapa kritik konstruktif, beri tahu saya dengan berkomentar di bawah.


Lampiran:

Kritik atas Jawaban / Diterima **

  • Ini membingungkan pada apa yang membuat iterable, hanya menggunakan daftar sebagai contoh. Lihat referensi saya di atas, tetapi dalam ringkasan: sebuah iterable memiliki __iter__ metode mengembalikan sebuah iterator. Sebuah iterator menyediakan sebuah .next (Python 2 atau .__next__ (Python 3) metode, yang secara implisit disebut oleh forloop hingga muncul StopIteration, dan sekali itu terjadi, ia akan terus melakukannya.
  • Ia kemudian menggunakan ekspresi generator untuk menggambarkan apa generator itu. Karena generator hanyalah cara mudah untuk membuat iterator, itu hanya membingungkan masalah, dan kita masih belum sampai ke yield bagian.
  • Di Mengontrol kelelahan generator dia menyebutnya .next metode, padahal ia harus menggunakan fungsi builtin, next. Ini akan menjadi lapisan tipuan yang tepat, karena kodenya tidak berfungsi dengan Python 3.
  • Itertools? Ini tidak relevan dengan apa yield sama sekali.
  • Tidak ada pembahasan tentang metode itu yield menyediakan bersama dengan fungsi baru yield from dengan Python 3. Jawaban atas / diterima adalah jawaban yang sangat tidak lengkap.

Kritik jawaban yang menyarankan yield dalam ekspresi atau pemahaman generator.

Tata bahasa saat ini memungkinkan ekspresi apa pun 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, ia telah disebut-sebut oleh beberapa orang sebagai menarik untuk menggunakannya dalam pemahaman atau ekspresi generator - meskipun tidak mengutip kasus penggunaan yang sangat baik.

Pengembang inti CPython adalah mendiskusikan penghentian tunjangannya. Berikut ini postingan yang relevan dari milis:

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

Pada Minggu, 29 Januari 2017 pukul 16:39 Craig Rodrigues menulis:

Saya baik-baik saja dengan pendekatan baik. Membiarkan hal-hal sebagaimana adanya dengan Python 3       tidak baik, IMHO.

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

Saya setuju itu adalah tempat yang masuk akal bagi kita untuk berakhir, seperti kode apa pun   mengandalkan perilaku saat ini benar-benar terlalu pintar   dapat dipelihara.

Dalam hal menuju ke sana, kami mungkin ingin:

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

Ceria, Nick.

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

Selanjutnya, ada sebuah masalah luar biasa (10544) yang tampaknya menunjuk ke arah ini tak pernah menjadi ide yang bagus (PyPy, implementasi Python yang ditulis dengan Python, sudah meningkatkan peringatan sintaks.)

Intinya, sampai pengembang CPython memberi tahu kami jika tidak: Jangan taruh yield dalam ekspresi atau pemahaman generator.

Itu return pernyataan dalam generator

Di Python 2:

Dalam fungsi generator, return pernyataan tidak diizinkan untuk memasukkan expression_list. Dalam konteks itu, telanjang return menunjukkan bahwa generator sudah selesai dan akan menyebabkan StopIteration dibangkitkan.

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

Di Python 3:

Dalam fungsi generator, return pernyataan menunjukkan bahwa generator dilakukan dan akan menyebabkan StopIteration dibangkitkan. Nilai yang dikembalikan (jika ada) digunakan sebagai argumen untuk membangun StopIteration dan menjadi StopIteration.valueatribut.

Catatan kaki

  1. Bahasa CLU, Sather, dan Icon direferensikan dalam proposal untuk memperkenalkan konsep generator ke Python. Gagasan umumnya adalah bahwa suatu fungsi dapat mempertahankan status internal dan menghasilkan intermediate titik data pada permintaan oleh pengguna. Ini dijanjikan akan terjadi unggul dalam kinerja ke pendekatan lain, termasuk threading Python, yang bahkan tidak tersedia pada beberapa sistem.

  2.  Ini berarti, misalnya, itu xrange benda (range dengan Python 3) tidak Iterators, meskipun mereka iterable, karena mereka dapat digunakan kembali. Seperti daftar, mereka __iter__ metode mengembalikan objek iterator.

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


253
2018-06-25 06:11



yield sama seperti return - Itu mengembalikan apa pun yang Anda kirim ke (sebagai generator). Perbedaannya adalah bahwa pada saat Anda memanggil generator, eksekusi dimulai dari panggilan terakhir ke yield pernyataan. Tidak seperti kembali, tumpukan frame tidak dibersihkan ketika hasil terjadi, namun kontrol ditransfer kembali ke pemanggil, sehingga negara akan melanjutkan fungsi berikutnya.

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

list.extend memanggil iterator sampai habis. Dalam kasus sampel kode yang Anda posting, akan lebih jelas untuk hanya mengembalikan tuple dan menambahkannya ke daftar.


230
2017-10-23 22:24



Ada satu hal ekstra untuk disebutkan: sebuah fungsi yang menghasilkan tidak benar-benar harus berakhir. Saya sudah menulis kode seperti ini:

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

Lalu saya bisa 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.


182
2017-10-24 08:44



Bagi mereka yang lebih memilih contoh kerja minimal, renungkanlah hal interaktif ini Python sidang:

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

155
2018-01-18 17:25



Hasil 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 foo menyimpan seluruh daftar dalam memori sekaligus. Ini bukan masalah besar untuk daftar dengan 5 elemen, tetapi bagaimana jika Anda menginginkan daftar 5 juta? Tidak hanya ini adalah pemakan memori yang besar, itu juga menghabiskan banyak waktu untuk membangun pada saat fungsi itu dipanggil. Dalam kasus kedua, bar hanya memberi Anda generator. Generator adalah iterable - yang berarti Anda dapat menggunakannya dalam loop, dll, tetapi setiap nilai hanya dapat diakses satu kali. Semua nilai juga tidak disimpan dalam memori pada saat yang bersamaan; objek generator "ingat" di mana itu di looping terakhir kali Anda menyebutnya - cara ini, jika Anda menggunakan iterable ke (katakanlah) menghitung hingga 50 miliar, Anda tidak harus menghitung hingga 50 miliar semua sekaligus dan simpan 50 miliar angka untuk dihitung. Sekali lagi, ini adalah contoh yang cukup dibuat, Anda mungkin akan menggunakan alat itu jika Anda benar-benar ingin menghitung hingga 50 miliar. :)

Ini adalah kasus penggunaan generator yang paling sederhana. Seperti yang Anda katakan, itu dapat digunakan untuk menulis permutasi yang efisien, menggunakan hasil untuk mendorong sesuatu melalui tumpukan panggilan daripada menggunakan semacam variabel tumpukan. Generator juga dapat digunakan untuk traversal pohon khusus, dan segala macam hal lainnya.


133
2018-01-16 06:42



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

Ada sebuah Artikel IBM yang menjelaskannya dengan cukup baik (untuk Python) sejauh yang saya bisa lihat.

Ide utamanya adalah compiler / interpreter / apa pun beberapa tipuan sehingga sejauh menyangkut pemanggil, mereka dapat terus menelepon berikutnya () dan akan terus mengembalikan nilai - seolah-olah metode generator dijeda. Sekarang jelas Anda tidak bisa benar-benar "jeda" metode, sehingga kompiler membangun mesin negara bagi Anda untuk mengingat di mana Anda saat ini dan apa variabel lokal dll terlihat seperti. Ini jauh lebih mudah daripada menulis iterator sendiri.


125
2017-10-23 22:26



Ada satu jenis jawaban yang saya rasa belum diberikan, di antara sekian banyak jawaban bagus yang menjelaskan cara menggunakan generator. Berikut adalah jawaban teori bahasa pemrograman:

Itu yield pernyataan dengan Python menghasilkan generator. Generator dalam Python adalah fungsi yang mengembalikan lanjutan (dan secara khusus jenis coroutine, tetapi kelanjutannya mewakili mekanisme yang lebih umum untuk memahami apa yang sedang terjadi).

Kelanjutan dalam teori bahasa pemrograman adalah jenis komputasi yang jauh lebih mendasar, tetapi mereka tidak sering digunakan, karena sangat sulit untuk dipikirkan dan juga sangat sulit untuk diimplementasikan. Tetapi gagasan tentang apa yang merupakan kelanjutan, adalah langsung: ini adalah keadaan perhitungan yang belum selesai. Dalam keadaan ini, nilai variabel saat ini, operasi yang belum dilakukan, dan sebagainya, disimpan. Kemudian di beberapa titik kemudian dalam program, kelanjutannya dapat dipanggil, sehingga variabel program direset ke status itu dan operasi yang disimpan dilakukan.

Kelanjutan, dalam bentuk yang lebih umum ini, dapat diimplementasikan dalam dua cara. Dalam call/cc cara, tumpukan program secara harfiah disimpan dan kemudian ketika kelanjutan dipanggil, tumpukan dikembalikan.

Dalam kelanjutan gaya passing (CPS), kelanjutan hanya fungsi normal (hanya dalam bahasa di mana fungsi adalah kelas satu) yang secara eksplisit dikelola oleh programmer dan berpindah ke subrutin. Dalam gaya ini, status program diwakili oleh penutupan (dan variabel yang kebetulan dikodekan di dalamnya) daripada variabel yang berada di suatu tempat di stack. Fungsi yang mengelola aliran kontrol menerima kelanjutan sebagai argumen (dalam beberapa variasi CPS, fungsi dapat menerima kelanjutan berganda) dan memanipulasi aliran kontrol dengan memanggilnya hanya dengan memanggil mereka dan kembali sesudahnya. Contoh kelulusan 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 ini (sangat sederhana), programmer menyimpan operasi untuk benar-benar menulis file menjadi kelanjutan (yang dapat berpotensi menjadi operasi yang sangat kompleks dengan banyak detail untuk ditulis), dan kemudian meneruskan kelanjutannya (yaitu, sebagai pertama- penutupan kelas) ke operator lain yang melakukan lebih banyak pemrosesan, dan kemudian memanggilnya jika perlu. (Saya menggunakan pola desain ini banyak dalam pemrograman GUI yang sebenarnya, baik karena menghemat baris kode atau, yang lebih penting, untuk mengelola aliran kontrol setelah memicu peristiwa GUI.)

Sisa dari posting ini akan, tanpa kehilangan keumuman, konsep lanjutan sebagai CPS, karena itu adalah neraka jauh lebih mudah untuk dipahami dan dibaca.


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

def f():
  while True:
    yield 4

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

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

Tetapi Anda dapat dengan mudah menerapkan (dan mengkonsepkan) generator sebagai kasus kelanjutan sederhana yang sederhana dan spesifik:

Kapanpun yield disebut, ia memberitahu fungsi untuk mengembalikan kelanjutan. Ketika fungsi dipanggil lagi, itu dimulai dari tempat yang ditinggalkannya. Jadi, dalam pseudo-pseudocode (mis., Bukan pseudocode, tetapi bukan kode) generator next metode pada 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

Dimana yield kata kunci sebenarnya adalah gula sintaksis untuk fungsi generator 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 aktual generator dengan Python lebih kompleks. Tetapi sebagai latihan untuk memahami apa yang sedang terjadi, cobalah untuk menggunakan kelanjutan melewati gaya untuk mengimplementasikan objek generator tanpa menggunakan yield kata kunci.


120
2018-04-04 14:56