Apa yang dilakukan kata kunci hasil?

Apa gunanya kata kunci yield dalam Python? 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 adalah dialer

 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 ? Apakah daftarnya dikembalikan? Item tunggal? Apakah dipanggil lagi? Kapan panggilan tindak lanjut akan berhenti?


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

8911
diberikan oleh Alex. 24 окт. S. 24 Oktober. 2008-10-24 01:21 '08 pada 1:21 2008-10-24 01:21
@ 46 jawaban
  • 1
  • 2

Untuk memahami apa itu yield , Anda perlu memahami apa itu generator. Dan sebelum generator datang iterator.

iterated

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

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

mylist dapat diu>

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

Yang bisa Anda gunakan " for... in... " adalah iteratif; lists , strings , file ...

Iterasi ini nyaman karena Anda dapat membaca sebanyak yang Anda suka, 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 iterasi yang hanya dapat Anda u> . 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 

Ini sama, kecuali bahwa Anda menggunakan () bukannya [] . TETAPI, Anda tidak dapat melakukannya for я in mygenerator kedua kalinya, karena generator hanya dapat digunakan sekali: mereka menghitung 0, lalu lupakan dan hitung 1, dan akhirnya menghitung 4, satu demi satu.

Hasil

yield adalah kata kunci yang digunakan sebagai return , kecuali bahwa 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 bahwa fungsi Anda akan mengembalikan sejumlah besar nilai yang hanya perlu Anda baca sekali.

Untuk mengatasi yield , Anda harus memahami bahwa ketika Anda memanggil suatu fungsi, kode yang ditulis di badan fungsi tidak dimulai. Fungsi hanya mengembalikan objek generator, itu agak rumit :-)

Kemudian kode Anda akan berlanjut dari tempat itu berhenti setiap kali, for menggunakan generator.

Sekarang bagian yang paling sulit:

Pertama kali Anda memanggil for memanggil objek generator yang dibuat dari fungsi Anda, itu akan menjalankan kode dalam fungsi Anda dari awal sampai mencapai yield , dan kemudian mengembalikan nilai pertama dari loop. Kemudian, setiap panggilan berikutnya akan memulai loop yang Anda tulis ke fungsi lagi dan mengembalikan nilai berikutnya sampai nilainya dikembalikan.

Generator dianggap kosong setelah fungsi dimulai, tetapi tidak lagi yield . Ini mungkin karena fakta bahwa siklus telah berakhir, atau karena fakta bahwa Anda tidak lagi memenuhi "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 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 

Pe>

 # 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 mengandung beberapa bagian pintar:

  • Siklus diu>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 selain yang sebelumnya, karena tidak berlaku untuk node yang sama .

  • Metode extend() adalah metode objek daftar yang menunggu iterasi dan menambahkan nilainya ke daftar.

Biasanya kami memberinya daftar:

 >>> 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 semuanya disimpan dalam memori.

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

Anda bisa berhenti di sini atau membaca sedikit untuk melihat penggunaan lanjutan dari generator:

Kontrol kelelahan generator

 >>> class Bank(): # Let create a bank, building ATMs ... crisis = False ... def create_atm(self): ... while not self.crisis: ... yield "$100" >>> hsbc = Bank() # When everything 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 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__()) atau print(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 mengelola iterasi. Pernahkah Anda ingin menduplikasi generator? Rantai dua generator? Nilai grup dalam daftar bersarang dengan satu baris? Map/Zip tanpa membuat daftar lain?

Kemudian hanya import itertools .

Sebuah contoh? Mari kita lihat kemungkinan prosedur kedatangan untuk pacuan 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 iterasi internal

Iterasi adalah proses yang menyiratkan iterasi (menerapkan metode __iter__() ) dan iterator (menerapkan metode __next__() ). Iterasi adalah objek apa pun dari mana Anda bisa mendapatkan iterator. Iterator adalah objek yang memungkinkan Anda untuk mengu>

Ada lebih banyak tentang ini dalam artikel ini tentang cara kerja loop .

13022
24 окт. Jawabannya diberikan oleh e-satis 24 Oktober. 2008-10-24 01:48 '08 pada 1:48 2008-10-24 01:48

Beri label pada yield

Saat Anda melihat fungsi dengan yield , gunakan trik sederhana ini untuk memahami apa yang akan terjadi:

  1. Masukkan baris result = [] di awal fungsi.
  2. Ganti setiap yield expr dengan result.append(expr) .
  3. Masukkan hasil dari garis return result di bagian bawah fungsi.
  4. Yay - tidak ada lagi pernyataan yield ! Baca dan temukan kodenya.
  5. Bandingkan fungsinya dengan definisi asli.

Teknik ini mungkin memberi Anda gambaran tentang logika suatu fungsi, tetapi apa yang sebenarnya terjadi dengan yield jauh 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 terjebak dalam loop tanpa akhir, bahkan jika fungsi aslinya berfungsi dengan baik. Baca terus untuk mengetahui lebih lanjut ...

Jangan bingung iterator, iterator, dan generator Anda.

Pertama, protokol iterator - saat Anda menulis

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

Python melakukan dua >

  1. Mendapat iterator untuk mylist :

    Memanggil iter(mylist) → mengembalikan objek dengan metode next() (atau __next__() dengan Python 3).

    [Ini adalah >

  2. Menggunakan iterator untuk elemen loop:

    Lanjutkan memanggil metode next() pada iterator yang dikembalikan dari >next() ditugaskan ke x dan badan loop dijalankan. Jika pengecualian StopIteration dipanggil dari dalam next() , itu berarti bahwa tidak ada lagi nilai di iterator dan siklus berakhir.

Yang benar adalah bahwa Python melakukan dua >otherlist.extend(mylist) (di mana otherlist adalah daftar Python ),

border=0

Di sini, mylist beru>__iter__() untuk membuat instance kelas Anda beru>next() . Anda dapat mengimplementasikan __iter__() dan next() di kelas yang sama dan memiliki __iter__() kembali self . Ini akan berfungsi untuk kasus-kasus sederhana, tetapi tidak ketika Anda ingin dua iterator untuk menggilir objek yang sama pada saat yang sama.

Jadi dalam protokol iterator, banyak objek mengimplementasikan protokol ini:

  1. Daftar, kamus, tupel, set, file bawaan.
  2. Kelas khusus yang mengimplementasikan __iter__() .
  3. Generator.

Perhatikan bahwa for loop tidak tahu objek yang dihadapinya - itu hanya mengikuti protokol iterator dan senang mendapatkan elemen dengan elemen saat memanggil next() . Daftar bawaan mengembalikan item mereka satu per satu, kamus mengembalikan kunci satu per satu, file mengembalikan string satu per satu, dll. Dan generator kembali ... baik, ketika yield datang:

 def f123(): yield 1 yield 2 yield 3 for item in f123(): print item 

Alih-alih yield , jika ada tiga return f123() operator di f123() hanya yang pertama, dan fungsi f123() . Tetapi f123() bukan fungsi biasa. Saat f123() , ia tidak mengembalikan nilai dalam pernyataan hasil! Mengembalikan objek generator. Selain itu, fungsi ini tidak benar-benar keluar - ia memasuki kondisi menunggu. Ketika for loop mencoba untuk mengu>yield , dan mengembalikannya sebagai item berikutnya. Ini terjadi sampai fungsi dilepaskan, dan pada saat ini generator StopIteration dan siklus StopIteration .

Dengan demikian, objek generator mirip dengan adaptor - di satu sisi ia menunjukkan protokol iterator, menyediakan __iter__() dan next() untuk mempertahankan loop for dalam kondisi baik. Namun, di ujung yang lain, ia memulai fungsi yang cukup untuk mendapatkan nilai berikutnya, dan mengembalikannya ke mode siaga.

Mengapa menggunakan generator?

Anda biasanya dapat menulis kode yang tidak menggunakan generator, tetapi mengimplementasikan logika yang sama. Salah satu opsi adalah menggunakan daftar trik sementara yang saya sebutkan sebelumnya. Ini tidak akan berfungsi dalam semua kasus, misalnya, jika Anda memiliki loop tak terbatas, atau dapat menyebabkan penggunaan memori yang tidak efisien ketika Anda memiliki daftar yang sangat panjang. Pendekatan lain adalah dengan mengimplementasikan kelas iteratif SomethingIter yang menyimpan keadaan dalam elemen instance dan melakukan >next() (atau __next__() dengan Python 3). Bergantung pada logika, kode di dalam metode next() dapat terlihat sangat rumit dan rentan terhadap kesalahan. Di sini generator memberikan solusi yang bersih dan sederhana.

1744
26 окт. jawaban diberikan oleh user28409 26 Okt 2008-10-26 00:22 '08 pada 0:22 2008-10-26 00:22

Pikirkan seperti ini:

Iterator hanyalah istilah mewah untuk objek yang memiliki metode next (). Jadi, fungsi hasil-ed pada akhirnya terlihat 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 lebih memahami apa yang terjadi di balik layar, for loop dapat ditulis u>

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

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

Saya harus menunjukkan bahwa ini adalah penyederhanaan untuk tujuan ilustrasi. :)

441
24 окт. Balas ke Jason Baker pada 24 Okt 2008-10-24 01:28 '08 pada 1:28 2008-10-24 01:28

Kata kunci yield turun ke dua fakta sederhana:

  1. Jika kompiler mendeteksi kata kunci yield mana saja di dalam suatu fungsi, fungsi ini tidak lagi dikembalikan melalui return . Sebagai gantinya, ia segera mengembalikan objek daftar tunggu malas, yang disebut generator.
  2. Generator diu>range set list atau tampilan dikt dengan protokol tertanam untuk mengunjungi setiap item dalam urutan tertentu.

Singkatnya: generator adalah malas, daftar meningkat secara bertahap , dan yield memungkinkan Anda untuk menggunakan fungsi notasi untuk memprogram nilai-nilai daftar bahwa generator harus secara bertahap menghasilkan.

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

sebuah contoh

Mari kita mendefinisikan fungsi makeRange yang mirip dengan range Python. makeRange(n) memanggil makeRange(n) 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 membuat generator segera mengembalikan nilai yang tertunda, Anda dapat meneruskannya ke list() (seperti yang beru>

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

Membandingkan contoh dengan "hanya mengembalikan daftar"

Contoh di atas dapat dilihat hanya dengan 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 signifikan; Lihat bagian terakhir.


Bagaimana Anda bisa menggunakan generator

Iterated adalah bagian terakhir dari memahami daftar, dan semua generator beru>

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

Untuk lebih memahami generator, Anda bisa bermain itertools main dengan modul itertools (pastikan untuk menggunakan chain.from_iterable dan tidak chain dengan garansi untuk chain.from_iterable ). Misalnya, Anda bahkan dapat menggunakan generator untuk mengimplementasikan daftar malas yang sangat panjang, seperti itertools.count() . Anda dapat mengimplementasikan def enumerate(iterable): zip(count(), iterable) Anda sendiri def enumerate(iterable): zip(count(), iterable) atau, sebagai alternatif, lakukan ini menggunakan kata kunci yield dalam loop sementara.

Perhatikan bahwa generator dapat digunakan untuk banyak tujuan lain, seperti menerapkan coroutine, pemrograman non-deterministik, atau hal-hal elegan lainnya. Namun, tampilan "daftar malas", yang saya wakili di sini, adalah area penggunaan paling umum yang akan Anda temukan.


Di belakang layar

Ini adalah cara kerja Protokol Iterasi Python. Itulah yang terjadi ketika Anda membuat 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 objek .next() .next() , yang merupakan bagian dari "protokol iterasi" dan terjadi pada semua iterator. Anda dapat secara manual menggunakan fungsi next() (dan bagian lain dari protokol iterasi) untuk mengimplementasikan hal-hal yang tidak biasa, biasanya dengan mengorbankan keterbacaan, jadi cobalah untuk tidak melakukan ini ...


hal-hal kecil

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

Dalam Python, iteratif adalah objek apa pun yang "memahami konsep loop for", misalnya, daftar [1,2,3] , dan iterator adalah contoh spesifik dari loop yang diminta, misalnya [1,2,3].__iter__() . 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.

Jadi jika Anda tidak dapat melakukan hal seperti itu ...

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

... maka ingatlah bahwa generator adalah iterator; yaitu penggunaan satu kali. Jika Anda ingin menggunakannya kembali, Anda harus myRange(...) memanggil myRange(...) . 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 hacker sangat) dapat menggunakan itertools.tee jika benar-benar diperlukan, karena standar Python PEP yang ditawarkan untuk iterator telah ditunda.

378
19 июня '11 в 9:33 2011-06-19 09:33 jawabannya diberikan ninjagecko 19 Juni '11 di 9:33 2011-06-19 09:33

Apa yang dilakukan kata kunci yield dengan python?

Skema Respons / Ringkasan

  • Fungsi yield pada panggilan mengembalikan generator .
  • Generator adalah iterator karena mereka mengimplementasikan protokol iterator , sehingga Anda dapat menggunakannya lagi.
  • Генератору также может быть отправлена информация , что делает его концептуально сопрограммой .
  • В Python 3 вы можете делегировать от одного генератора другому в обоих направлениях с помощью yield from .
  • (Приложение критикует пару @, включая верхний, и обсуждает использование return в генераторе.)

Генераторы:

yield допустим только внутри определения функции, и включение yield в определение функции заставляет его возвращать генератор.

Идея для генераторов исходит из других языков (см. Сноску 1) с различными реализациями. В Python Generators выполнение кода заморожено в точке выхода. Когда вызывается генератор (методы обсуждаются ниже), выполнение возобновляется, а затем останавливается при следующем выходе.

yield предоставляет простой способ реализации протокола итератора , который определяется следующими двумя методами: __iter__ и next (Python 2) или __next__ (Python 3). Оба эти метода делают объект итератором, который можно проверить типом с помощью абстрактного базового класса Iterator из модуля collections .

 >>> 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 an iterable True >>> hasattr(gen, 'next') # and with .next (.__next__ in Python 3) True # implements the iterator protocol.