Meniru loop do-while di Python?


799

Saya perlu meniru loop do-while dalam program Python. Sayangnya, kode langsung berikut ini tidak berfungsi:

list_of_ints = [ 1, 2, 3 ]
iterator = list_of_ints.__iter__()
element = None

while True:
  if element:
    print element

  try:
    element = iterator.next()
  except StopIteration:
    break

print "done"

Alih-alih "1,2,3, selesai", ia mencetak output berikut:

[stdout:]1
[stdout:]2
[stdout:]3
None['Traceback (most recent call last):
', '  File "test_python.py", line 8, in <module>
    s = i.next()
', 'StopIteration
']

Apa yang bisa saya lakukan untuk menangkap pengecualian 'hentikan iterasi' dan putuskan loop sementara dengan benar?

Contoh mengapa hal seperti itu diperlukan ditunjukkan di bawah ini sebagai pseudocode.

Mesin negara:

s = ""
while True :
  if state is STATE_CODE :
    if "//" in s :
      tokens.add( TOKEN_COMMENT, s.split( "//" )[1] )
      state = STATE_COMMENT
    else :
      tokens.add( TOKEN_CODE, s )
  if state is STATE_COMMENT :
    if "//" in s :
      tokens.append( TOKEN_COMMENT, s.split( "//" )[1] )
    else
      state = STATE_CODE
      # Re-evaluate same line
      continue
  try :
    s = i.next()
  except StopIteration :
    break

4
Um ... Itu bukan "do-while" yang tepat; itu hanya "do-forever". Apa yang salah dengan "while True" dan "break"?
S.Lott

70
S. Lott: Saya cukup yakin pertanyaannya adalah tentang bagaimana mengimplementasikan do sementara di python. Jadi, saya tidak akan mengharapkan kodenya sepenuhnya benar. Juga, dia sangat dekat dengan pekerjaan sambil ... dia memeriksa kondisi pada akhir dari loop "selamanya" untuk melihat apakah dia harus keluar. Itu bukan "do-forever".
Tom

4
jadi ... kode contoh awal Anda benar-benar berfungsi untuk saya tanpa masalah dan saya tidak mendapatkan traceback itu. itu idiom yang tepat untuk do while loop di mana kondisi break adalah kelelahan iterator. biasanya, Anda akan mengatur s=i.next()daripada Tidak ada dan mungkin melakukan beberapa pekerjaan awal daripada hanya membuat melewati pertama Anda melalui loop tidak berguna sekalipun.
underrun

3
@underrun Sayangnya, postingan ini tidak ditandai dengan versi Python mana yang digunakan - cuplikan asli juga berfungsi untuk saya menggunakan 2.7, mungkin karena pembaruan ke bahasa Python itu sendiri.
Hannele

Jawaban:


985

Saya tidak yakin apa yang Anda coba lakukan. Anda dapat menerapkan loop do-while seperti ini:

while True:
  stuff()
  if fail_condition:
    break

Atau:

stuff()
while not fail_condition:
  stuff()

Apa yang Anda lakukan dengan mencoba lakukan loop sementara untuk mencetak barang-barang dalam daftar? Mengapa tidak menggunakan saja:

for i in l:
  print i
print "done"

Memperbarui:

Jadi, apakah Anda memiliki daftar garis? Dan Anda ingin terus mengulanginya? Bagaimana tentang:

for s in l: 
  while True: 
    stuff() 
    # use a "break" instead of s = i.next()

Apakah itu tampak seperti sesuatu yang dekat dengan apa yang Anda inginkan? Dengan contoh kode Anda, itu akan menjadi:

for s in some_list:
  while True:
    if state is STATE_CODE:
      if "//" in s:
        tokens.add( TOKEN_COMMENT, s.split( "//" )[1] )
        state = STATE_COMMENT
      else :
        tokens.add( TOKEN_CODE, s )
    if state is STATE_COMMENT:
      if "//" in s:
        tokens.append( TOKEN_COMMENT, s.split( "//" )[1] )
        break # get next s
      else:
        state = STATE_CODE
        # re-evaluate same line
        # continues automatically

1
saya perlu membuat mesin negara. Dalam keadaan mesin itu adalah kasus normal untuk mengevaluasi kembali pernyataan CURRENT, jadi saya perlu 'melanjutkan' tanpa mengulangi item berikutnya. Saya tidak tahu bagaimana melakukan hal seperti itu dalam 'untuk s di l:' iterasi :(. Dalam loop do-while, 'lanjutkan' akan mengevaluasi kembali item saat ini, iterasi di akhir
grigoryvp

Apakah maksud Anda Anda harus melacak tempat Anda dalam daftar? Dengan begitu ketika Anda mengembalikan kondisi yang sama, Anda dapat mengambil di mana Anda tinggalkan? Berikan sedikit lebih banyak konteks. Sepertinya Anda lebih baik menggunakan indeks ke dalam daftar.
Tom

Terima kasih, saya mengomentari kodesemu Anda ... contoh Anda tampaknya agak buruk karena Anda tampaknya menangani "//" dengan cara yang sama tidak peduli apa pun keadaan Anda. Juga, apakah kode asli ini tempat Anda memproses komentar? Bagaimana jika Anda memiliki string dengan garis miring? yaitu: cetak "blah // <- apakah itu mengacaukan Anda?"
Tom

4
Sayang sekali bahwa python tidak memiliki loop do-while. Python itu KERING, eh?
Kr0e

43
Juga lihat PEP 315 untuk sikap / pembenaran resmi: "Pengguna bahasa disarankan untuk menggunakan bentuk while-True dengan if-break dalam ketika loop do-while sudah tepat."
dtk

311

Inilah cara yang sangat sederhana untuk meniru loop do-while:

condition = True
while condition:
    # loop body here
    condition = test_loop_condition()
# end of loop

Fitur utama dari loop do-while adalah bahwa loop body selalu dieksekusi setidaknya sekali, dan bahwa kondisi dievaluasi di bagian bawah loop body. Struktur kontrol yang ditampilkan di sini menyelesaikan kedua hal ini tanpa memerlukan pengecualian atau pernyataan break. Itu memang memperkenalkan satu variabel Boolean ekstra.


11
Itu tidak selalu menambahkan variabel boolean ekstra. Seringkali ada sesuatu yang sudah ada yang keadaannya dapat diuji.
martineau

14
Alasan saya paling suka solusi ini adalah karena tidak menambahkan kondisi lain, itu masih hanya satu siklus, dan jika Anda memilih nama yang baik untuk variabel pembantu seluruh struktur cukup jelas.
Roberto

4
CATATAN: Meskipun ini menjawab pertanyaan awal, pendekatan ini kurang fleksibel daripada menggunakan break. Secara khusus, jika ada logika yang diperlukan SETELAH test_loop_condition(), yang tidak boleh dieksekusi setelah kita selesai, itu harus dibungkus if condition:. BTW, conditiontidak jelas. Lebih deskriptif: moreatau notDone.
ToolmakerSteve

7
@ToolmakerSteve saya tidak setuju. Saya jarang menggunakannya breakdalam loop dan ketika saya menemukannya dalam kode yang saya pertahankan, saya menemukan bahwa loop, paling sering, bisa ditulis tanpa itu. Solusi yang disajikan adalah, IMO, cara paling jelas untuk merepresentasikan do sambil membangun dengan python.
nonsensickle

1
Idealnya, kondisi akan dinamai sesuatu yang deskriptif, seperti has_no_errorsatau end_reached(dalam hal ini pengulangan akan dimulaiwhile not end_reached
Josiah Yoder

75

Kode saya di bawah ini mungkin merupakan implementasi yang bermanfaat, menyoroti perbedaan utama di antaranya vs. seperti yang saya mengerti.

Jadi dalam hal ini, Anda selalu melalui loop setidaknya sekali.

first_pass = True
while first_pass or condition:
    first_pass = False
    do_stuff()

2
Jawaban yang benar, saya berpendapat. Plus itu menghindari istirahat , untuk penggunaan yang aman di blok coba / kecuali.
Zv_oDD

apakah jit / optimizer menghindari pengujian ulang first_pass setelah pass pertama? jika tidak, itu akan menjadi masalah kinerja yang mengganggu, meskipun mungkin kecil,
markhahn

2
@markhahn ini benar-benar kecil tetapi jika Anda peduli tentang rincian tersebut, Anda dapat intervert 2 boolean dalam lingkaran: while condition or first_pass:. Kemudian conditionselalu dievaluasi terlebih dahulu dan keseluruhan first_passdievaluasi hanya dua kali (iterasi pertama dan terakhir). Jangan lupa untuk menginisialisasi conditionsebelum loop ke apa pun yang Anda inginkan.
pLOPeGG

Hm, menarik saya sebenarnya telah memilih sebaliknya untuk tidak harus menginisialisasi kondisi dan dengan demikian membutuhkan perubahan minimal pada kode. Yang mengatakan saya mengerti maksud Anda
evan54

33

Pengecualian akan mematahkan loop, jadi Anda mungkin juga menanganinya di luar loop.

try:
  while True:
    if s:
      print s
    s = i.next()
except StopIteration:   
  pass

Saya kira masalah dengan kode Anda adalah bahwa perilaku orang breakdalam excepttidak didefinisikan. Umumnya breaknaik hanya satu tingkat, jadi misalnya breakdi dalamtry langsung ke finally(jika ada) keluar dari try, tetapi tidak keluar dari loop.

PEP Terkait: http://www.python.org/dev/peps/pep-3136
Pertanyaan terkait: Keluar dari loop bersarang


8
Ini praktik yang baik meskipun hanya ada di dalam pernyataan percobaan apa yang Anda harapkan untuk membuang pengecualian Anda, agar Anda tidak menangkap pengecualian yang tidak diinginkan.
Paggas

7
@PiPeep: RTFM, cari EAFP.
vartec

2
@PiPeep: tidak masalah, perlu diingat, bahwa apa yang benar untuk beberapa bahasa, mungkin tidak berlaku untuk yang lain. Python dioptimalkan untuk penggunaan pengecualian yang intensif.
vartec

5
break dan continue didefinisikan dengan sangat baik dalam setiap klausa pernyataan try / kecuali / akhirnya. Mereka mengabaikannya, dan keluar atau beralih ke iterasi berikutnya dari while while atau untuk loop yang sesuai. Sebagai komponen dari konstruksi perulangan, mereka hanya relevan untuk sementara dan untuk pernyataan, dan memicu kesalahan sintaks jika mereka lari ke kelas atau pernyataan def sebelum mencapai loop paling dalam. Mereka mengabaikan jika, dengan dan mencoba pernyataan.
ncoghlan

1
.. yang merupakan kasus penting
javadba

33
do {
  stuff()
} while (condition())

->

while True:
  stuff()
  if not condition():
    break

Anda dapat melakukan fungsi:

def do_while(stuff, condition):
  while condition(stuff()):
    pass

Tapi 1) Ini jelek. 2) Kondisi harus merupakan fungsi dengan satu parameter, seharusnya diisi oleh barang (itu satu-satunya alasan untuk tidak menggunakan loop while klasik.)


5
Menulis while True: stuff(); if not condition(): breakadalah ide yang sangat bagus. Terima kasih!
Noctis Skytower

2
@ ZED, mengapa 1) jelek? Cukup ok, IMHO
Sergey Lossev

@SergeyLossev Akan sulit untuk memahami logika program karena muncul sebagai infinite loop pada awalnya, jika Anda memiliki banyak kode 'stuff' di antaranya.
exic

16

Berikut ini adalah solusi gila dari pola yang berbeda - menggunakan coroutine. Kode masih sangat mirip, tetapi dengan satu perbedaan penting; tidak ada kondisi keluar sama sekali! Coroutine (rantai coroutine benar-benar) hanya berhenti ketika Anda berhenti memberinya data.

def coroutine(func):
    """Coroutine decorator

    Coroutines must be started, advanced to their first "yield" point,
    and this decorator does this automatically.
    """
    def startcr(*ar, **kw):
        cr = func(*ar, **kw)
        cr.next()
        return cr
    return startcr

@coroutine
def collector(storage):
    """Act as "sink" and collect all sent in @storage"""
    while True:
        storage.append((yield))

@coroutine      
def state_machine(sink):
    """ .send() new parts to be tokenized by the state machine,
    tokens are passed on to @sink
    """ 
    s = ""
    state = STATE_CODE
    while True: 
        if state is STATE_CODE :
            if "//" in s :
                sink.send((TOKEN_COMMENT, s.split( "//" )[1] ))
                state = STATE_COMMENT
            else :
                sink.send(( TOKEN_CODE, s ))
        if state is STATE_COMMENT :
            if "//" in s :
                sink.send(( TOKEN_COMMENT, s.split( "//" )[1] ))
            else
                state = STATE_CODE
                # re-evaluate same line
                continue
        s = (yield)

tokens = []
sm = state_machine(collector(tokens))
for piece in i:
    sm.send(piece)

Kode di atas mengumpulkan semua token sebagai tupel tokensdan saya menganggap tidak ada perbedaan antara .append()dan .add()dalam kode asli.


4
Bagaimana Anda menulis ini dengan Python 3.x hari ini?
Noctis Skytower

14

Cara saya melakukan ini adalah sebagai berikut ...

condition = True
while condition:
     do_stuff()
     condition = (<something that evaluates to True or False>)

Bagi saya ini adalah solusi sederhana, saya terkejut saya belum melihatnya di sini. Ini jelas juga dapat dibalik

while not condition:

dll.


Anda berkata "Saya terkejut saya belum melihatnya di sini" - tetapi saya tidak melihat perbedaan dari, katakanlah, solusi powderflask dari 2010. Ini persis sama. ("condition = True while condition: # loop body here condition = test_loop_condition () # end of loop")
cslotty

10

untuk loop do - while berisi pernyataan try

loop = True
while loop:
    generic_stuff()
    try:
        questionable_stuff()
#       to break from successful completion
#       loop = False  
    except:
        optional_stuff()
#       to break from unsuccessful completion - 
#       the case referenced in the OP's question
        loop = False
   finally:
        more_generic_stuff()

sebagai alternatif, ketika tidak perlu untuk klausa 'akhirnya'

while True:
    generic_stuff()
    try:
        questionable_stuff()
#       to break from successful completion
#       break  
    except:
        optional_stuff()
#       to break from unsuccessful completion - 
#       the case referenced in the OP's question
        break

7
while condition is True: 
  stuff()
else:
  stuff()

8
Ew. Itu tampak jauh lebih jelek daripada menggunakan jeda.
mattdm

5
Itu pintar, tetapi itu membutuhkan stufffungsi atau untuk kode tubuh diulang.
Noctis Skytower

12
Semua yang dibutuhkan adalah while condition:karena is Truetersirat.
martineau

2
ini gagal jika conditiontergantung pada beberapa variabel dalam stuff(), karena variabel itu tidak didefinisikan pada saat itu.
yo

5
Bukan logika yang sama, karena pada iterasi terakhir ketika kondisi! = Benar: Ini menyebut kode waktu terakhir. Where as a Do While , memanggil kode sekali dulu, lalu memeriksa kondisi sebelum kembali berjalan. Do While: jalankan blok sekali; kemudian periksa dan jalankan kembali , jawaban ini: periksa dan jalankan kembali; kemudian jalankan blok kode sekali . Perbedaan besar!
Zv_oDD

7

Retas cepat:

def dowhile(func = None, condition = None):
    if not func or not condition:
        return
    else:
        func()
        while condition():
            func()

Gunakan seperti ini:

>>> x = 10
>>> def f():
...     global x
...     x = x - 1
>>> def c():
        global x
        return x > 0
>>> dowhile(f, c)
>>> print x
0

3

Kenapa tidak lakukan saja

for s in l :
    print s
print "done"

?


1
saya perlu membuat mesin negara. Dalam keadaan mesin itu adalah kasus normal untuk mengevaluasi kembali pernyataan CURRENT, jadi saya perlu 'melanjutkan' tanpa mengulangi item berikutnya. Saya tidak tahu bagaimana melakukan hal seperti dalam 'untuk s di l:' iterasi :(. Dalam do-while loop, 'lanjutkan' akan mengevaluasi kembali item saat ini, iterasi di akhir.
grigoryvp

kemudian, dapatkah Anda mendefinisikan beberapa kode pseudo untuk mesin negara Anda, jadi kami dapat memberi Anda petunjuk tentang solusi pythonic terbaik? Saya tidak tahu banyak tentang mesin negara (dan mungkin bukan satu-satunya), jadi jika Anda memberi tahu kami sedikit tentang algoritma Anda, ini akan lebih mudah bagi kami untuk membantu Anda.
Martin

Untuk loop tidak bekerja untuk hal-hal seperti: a = fun () sementara a == 'zxc': sleep (10) a = fun ()
harry

Ini sepenuhnya merindukan titik memeriksa kondisi boolean
javadba

1

Lihat apakah ini membantu:

Atur bendera di dalam handler pengecualian dan periksa sebelum bekerja di s.

flagBreak = false;
while True :

    if flagBreak : break

    if s :
        print s
    try :
        s = i.next()
    except StopIteration :
        flagBreak = true

print "done"

3
Dapat disederhanakan dengan menggunakan while not flagBreak:dan menghapus if (flagBreak) : break.
martineau

1
Saya menghindari variabel bernama flag- Saya tidak dapat menyimpulkan apa arti nilai Benar atau nilai Salah. Sebaliknya, gunakan doneatau endOfIteration. Kode berubah menjadi while not done: ....
IceArdor

1

Jika Anda berada dalam skenario di mana Anda mengulang sementara sumber daya tidak dapat dihindari atau sesuatu yang serupa yang melempar pengecualian, Anda bisa menggunakan sesuatu seperti

import time

while True:
    try:
       f = open('some/path', 'r')
    except IOError:
       print('File could not be read. Retrying in 5 seconds')   
       time.sleep(5)
    else:
       break

0

Bagi saya loop sementara yang khas akan menjadi sesuatu seperti ini:

xBool = True
# A counter to force a condition (eg. yCount = some integer value)

while xBool:
    # set up the condition (eg. if yCount > 0):
        (Do something)
        yCount = yCount - 1
    else:
        # (condition is not met, set xBool False)
        xBool = False

Saya bisa memasukkan for..loop dalam loop sementara juga, jika situasinya menjamin, untuk looping melalui serangkaian kondisi lain.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.