Giả lập một vòng lặp do-while trong Python?


798

Tôi cần mô phỏng một vòng lặp do-while trong chương trình Python. Thật không may, mã đơn giản sau đây không hoạt động:

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"

Thay vì "1,2,3, xong", nó in ra kết quả sau:

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

Tôi có thể làm gì để bắt ngoại lệ 'dừng lặp' và phá vỡ vòng lặp while đúng cách?

Một ví dụ về lý do tại sao một điều như vậy có thể cần thiết được hiển thị dưới đây là mã giả.

Máy nhà nước:

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
Ừm ... Đó không phải là một "do-while" thích hợp; đó chỉ đơn giản là một "làm mãi mãi". Có gì sai với "while True" và "break"?
S.Lott

70
S. Lott: Tôi khá chắc chắn rằng câu hỏi của anh ấy là về cách thực hiện trong khi ở trăn. Vì vậy, tôi sẽ không mong đợi mã của anh ấy là hoàn toàn chính xác. Ngoài ra, anh ta rất gần với một việc trong khi ... anh ta đang kiểm tra một điều kiện ở cuối vòng lặp "mãi mãi" để xem liệu anh ta có nên thoát ra không. Đó không phải là "làm mãi mãi".
Tom

4
vì vậy ... mã ví dụ ban đầu của bạn thực sự hoạt động với tôi mà không có vấn đề gì và tôi không nhận được dấu vết đó. đó là một thành ngữ thích hợp cho một vòng lặp do trong đó điều kiện ngắt là kiệt sức lặp. thông thường, bạn sẽ thiết lập s=i.next()thay vì Không và có thể thực hiện một số công việc ban đầu thay vì chỉ thực hiện lần đầu tiên vượt qua vòng lặp vô dụng.
underrun

3
@underrun Thật không may, bài đăng không được gắn thẻ với phiên bản Python nào đang được sử dụng - đoạn mã gốc cũng hoạt động với tôi khi sử dụng 2.7, có lẽ là do cập nhật cho chính ngôn ngữ Python.
Hannele

Câu trả lời:


985

Tôi không chắc chắn những gì bạn đang cố gắng làm. Bạn có thể thực hiện một vòng lặp do-while như thế này:

while True:
  stuff()
  if fail_condition:
    break

Hoặc là:

stuff()
while not fail_condition:
  stuff()

Bạn đang làm gì để cố gắng sử dụng một vòng lặp do while để in các thứ trong danh sách? Tại sao không chỉ sử dụng:

for i in l:
  print i
print "done"

Cập nhật:

Vì vậy, bạn có một danh sách các dòng? Và bạn muốn tiếp tục lặp qua nó? Làm thế nào về:

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

Điều đó có vẻ giống như một cái gì đó gần với những gì bạn muốn? Với ví dụ mã của bạn, nó sẽ là:

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
tôi cần phải tạo ra một máy trạng thái. Trong máy trạng thái, đó là trường hợp bình thường để đánh giá lại câu lệnh HIỆN TẠI, vì vậy tôi cần 'tiếp tục' mà không lặp lại mục tiếp theo. Tôi không biết làm thế nào để làm điều đó trong 'for s in l:' iteration :(. Trong vòng lặp do-while, 'continue' sẽ đánh giá lại mục hiện tại, lặp lại ở cuối
grigoryvp

Bạn có nghĩa là bạn cần theo dõi vị trí của bạn trong danh sách? Bằng cách đó khi bạn trở lại trạng thái tương tự, bạn có thể chọn nơi bạn rời đi? Cho thêm một chút bối cảnh. Có vẻ như bạn có thể tốt hơn khi sử dụng một chỉ mục vào danh sách.
Tom

Cảm ơn, tôi đã nhận xét về mã giả của bạn ... ví dụ của bạn có vẻ khá tệ vì bạn dường như xử lý "//" giống như bất kể bạn đang ở trạng thái nào. Ngoài ra, đây có phải là mã thực sự nơi bạn đang xử lý nhận xét không? Điều gì nếu bạn có chuỗi với dấu gạch chéo? tức là: in "blah // <- điều đó có làm bạn bối rối không?"
Tom

4
Thật xấu hổ khi trăn không có vòng lặp do-while. Python là KHÔ, hả?
Kr0e

43
Đồng thời xem PEP 315 để biết lập trường / biện minh chính thức: "Người dùng ngôn ngữ được khuyên nên sử dụng dạng while-True với if-break bên trong khi vòng lặp do-while sẽ phù hợp."
dtk

311

Đây là một cách rất đơn giản để mô phỏng vòng lặp do-while:

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

Các tính năng chính của vòng lặp do-while là thân vòng lặp luôn thực thi ít nhất một lần và điều kiện được đánh giá ở dưới cùng của thân vòng lặp. Cấu trúc điều khiển hiển thị ở đây hoàn thành cả hai điều này mà không cần ngoại lệ hoặc phá vỡ các câu lệnh. Nó không giới thiệu thêm một biến Boolean.


11
Nó không luôn luôn thêm một biến boolean thêm. Thường thì có một cái gì đó đã tồn tại mà trạng thái của nó có thể được kiểm tra.
martineau

14
Lý do tôi thích giải pháp này nhất là vì nó không thêm một điều kiện nào nữa, nó vẫn chỉ là một chu kỳ và nếu bạn chọn một tên hay cho biến trợ giúp thì toàn bộ cấu trúc khá rõ ràng.
Roberto

4
LƯU Ý: Mặc dù điều này không giải quyết được câu hỏi ban đầu, cách tiếp cận này kém linh hoạt hơn so với sử dụng break. Cụ thể, nếu có logic cần thiết SAU test_loop_condition(), điều đó không nên được thực thi một khi chúng ta đã hoàn thành, nó phải được bọc lại if condition:. BTW, conditionlà mơ hồ. Mô tả thêm: morehoặc notDone.
ToolmakerSteve 15/12/13

7
@ToolmakerSteve Tôi không đồng ý. Tôi hiếm khi sử dụng breaktrong các vòng lặp và khi tôi gặp nó trong mã mà tôi duy trì tôi thấy rằng vòng lặp, thường xuyên nhất, có thể đã được viết mà không có nó. Giải pháp được trình bày là, IMO, cách rõ ràng nhất để biểu diễn một do trong khi xây dựng trong python.
vô nghĩa

1
Lý tưởng nhất, điều kiện sẽ được đặt tên một cái gì đó mô tả, như has_no_errorshoặc end_reached(trong trường hợp đó vòng lặp sẽ bắt đầuwhile not end_reached
Josiah Yoder

75

Mã của tôi dưới đây có thể là một triển khai hữu ích, nêu bật sự khác biệt chính giữa đấu với như tôi hiểu nó.

Vì vậy, trong trường hợp này, bạn luôn luôn đi qua vòng lặp ít nhất một lần.

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

2
Câu trả lời đúng, tôi tranh luận. Thêm vào đó, nó tránh được phá vỡ , để sử dụng an toàn trong thử / ngoại trừ các khối.
Zv_oDD

jit / trình tối ưu hóa có tránh kiểm tra lại First_pass sau lần đầu tiên không? nếu không, nó sẽ là một vấn đề khó chịu, mặc dù có thể là nhỏ,
markhahn

2
@markhahn điều này thực sự nhỏ nhưng nếu bạn quan tâm đến các chi tiết như vậy, bạn có thể chuyển đổi 2 booleans trong vòng lặp : while condition or first_pass:. Sau đó conditionluôn được đánh giá đầu tiên và tổng thể chỉ first_passđược đánh giá hai lần (lần lặp đầu tiên và lần lặp cuối cùng). Đừng quên khởi tạo conditiontrước vòng lặp đến bất cứ điều gì bạn muốn.
pLOPeGG

Thật thú vị, tôi thực sự đã chọn cách khác một cách có chủ đích để không phải khởi tạo điều kiện và do đó yêu cầu thay đổi tối thiểu đối với mã. Điều đó nói rằng tôi thấy quan điểm của bạn
evan54

33

Ngoại lệ sẽ phá vỡ vòng lặp, vì vậy bạn cũng có thể xử lý nó bên ngoài vòng lặp.

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

Tôi đoán rằng vấn đề với mã của bạn là hành vi breakbên trong exceptkhông được xác định. Nói chung breakchỉ đi lên một cấp, vì vậy, ví dụ breakbên trong tryđi thẳng vào finally(nếu nó tồn tại) một bên ngoài try, nhưng không ra khỏi vòng lặp.

PEP liên quan: http://www.python.org/dev/peps/pep-3136
Câu hỏi liên quan: Thoát ra khỏi các vòng lặp lồng nhau


8
Đó là một thực hành tốt mặc dù chỉ có bên trong tuyên bố thử những gì bạn mong đợi để ném ngoại lệ của mình, kẻo bạn sẽ gặp phải những ngoại lệ không mong muốn.
Paggas

7
@PiPeep: RTFM, tìm kiếm EAFP.
vartec

2
@PiPeep: không vấn đề gì, chỉ cần lưu ý rằng những gì đúng với một số ngôn ngữ, có thể không đúng với những ngôn ngữ khác. Python được tối ưu hóa để sử dụng nhiều ngoại lệ.
vartec

5
phá vỡ và tiếp tục được xác định hoàn hảo trong bất kỳ mệnh đề nào của câu lệnh try / trừ / cuối cùng. Họ chỉ cần bỏ qua chúng, và thoát ra hoặc chuyển sang lần lặp tiếp theo của vòng lặp while hoặc cho vòng lặp khi thích hợp. Là các thành phần của cấu trúc vòng lặp, chúng chỉ liên quan đến các câu lệnh while và for và gây ra lỗi cú pháp nếu chúng chạy vào một câu lệnh class hoặc def trước khi đến vòng lặp trong cùng. Họ bỏ qua nếu, với và thử tuyên bố.
ncoghlan

1
.. đó là một trường hợp quan trọng
javadba

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

->

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

Bạn có thể thực hiện một chức năng:

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

Nhưng 1) Thật xấu xí. 2) Điều kiện phải là một hàm có một tham số, được cho là được điền bởi công cụ (đó là lý do duy nhất để không sử dụng vòng lặp while cổ điển.)


5
Viết while True: stuff(); if not condition(): breaklà một ý tưởng rất tốt. Cảm ơn bạn!
Noctis Skytower

2
@ZeD, tại sao 1) xấu xí? Nó khá Ok, IMHO
Sergey Lossev

@SergeyLossev Sẽ rất khó để nắm bắt logic của chương trình vì ban đầu nó xuất hiện dưới dạng một vòng lặp vô hạn, nếu bạn có rất nhiều mã 'thứ' ở giữa.
exic

16

Đây là một giải pháp điên rồ hơn của một mô hình khác - sử dụng coroutines. Mã vẫn rất giống nhau, nhưng với một sự khác biệt quan trọng; không có điều kiện thoát nào cả! Các coroutine (chuỗi coroutines thực sự) chỉ dừng lại khi bạn ngừng cho nó ăn với dữ liệu.

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)

Đoạn mã trên thu thập tất cả các mã thông báo dưới dạng bộ dữ liệu tokensvà tôi cho rằng không có sự khác biệt giữa .append().add()trong mã gốc.


4
Làm thế nào bạn sẽ viết điều này trong Python 3.x ngày hôm nay?
Noctis Skytower

14

Cách tôi đã làm điều này như sau ...

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

Đây dường như là một giải pháp đơn giản, tôi ngạc nhiên vì tôi chưa thấy nó ở đây. Điều này rõ ràng cũng có thể được đảo ngược để

while not condition:

Vân vân.


Bạn nói "Tôi ngạc nhiên tôi chưa thấy nó ở đây" - nhưng tôi không thấy bất kỳ sự khác biệt nào, giả sử, giải pháp của Powderflask từ năm 2010. Nó hoàn toàn giống nhau. ("condition = True while condition: # loop body here condition = test_loop_condition () # end of loop")
cslotty

10

cho một vòng lặp do - while chứa các câu lệnh 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()

cách khác, khi không cần mệnh đề 'cuối cùng'

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. Điều đó có vẻ xấu hơn đáng kể so với việc sử dụng nghỉ ngơi.
mattdm

5
Đó là thông minh, nhưng nó đòi hỏi stuffphải là một hàm hoặc cho phần thân mã được lặp lại.
Noctis Skytower

12
Tất cả những gì cần thiết là while condition:bởi vì is Trueđược ngụ ý.
martineau

2
điều này thất bại nếu conditionphụ thuộc vào một số biến số bên trong stuff(), bởi vì biến đó không được xác định tại thời điểm đó.
yo '

5
Không cùng logic, bởi vì ở lần lặp cuối cùng khi điều kiện! = True: Nó gọi mã là lần cuối cùng. Khi làm Do While , hãy gọi mã một lần đầu tiên, sau đó kiểm tra điều kiện trước khi chạy lại. Do While: thực thi khối một lần; sau đó kiểm tra và chạy lại , câu trả lời này: kiểm tra và chạy lại; sau đó thực thi khối mã một lần . Sự khác biệt lớn!
Zv_oDD

7

Hack nhanh:

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

Sử dụng như vậy:

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

3

Tại sao bạn không làm

for s in l :
    print s
print "done"

?


1
tôi cần phải tạo ra một máy trạng thái. Trong máy trạng thái, đó là trường hợp bình thường để đánh giá lại câu lệnh HIỆN TẠI, vì vậy tôi cần 'tiếp tục' mà không lặp lại mục tiếp theo. Tôi không biết làm thế nào để thực hiện điều đó trong 'for s in l:' iteration :(. Trong vòng lặp do-while, 'continue' sẽ đánh giá lại mục hiện tại, lặp lại ở cuối.
grigoryvp

sau đó, bạn có thể xác định một số mã giả cho máy trạng thái của mình không, vì vậy chúng tôi có thể gợi ý cho bạn hướng tới giải pháp pythonic tốt nhất? Tôi không biết nhiều về các máy trạng thái (và có lẽ không phải là máy duy nhất), vì vậy nếu bạn cho chúng tôi biết một chút về thuật toán của bạn, chúng tôi sẽ giúp bạn dễ dàng hơn.
Martin

Đối với vòng lặp không hoạt động cho những thứ như: a = fun () trong khi a == 'zxc': ngủ (10) a = fun ()
harry

Điều này hoàn toàn bỏ lỡ điểm kiểm tra một điều kiện boolean
javadba

1

Xem nếu điều này giúp:

Đặt cờ bên trong trình xử lý ngoại lệ và kiểm tra nó trước khi làm việc trên s.

flagBreak = false;
while True :

    if flagBreak : break

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

print "done"

3
Có thể được đơn giản hóa bằng cách sử dụng while not flagBreak:và loại bỏ if (flagBreak) : break.
martineau

1
Tôi tránh các biến có tên - flagTôi không thể suy ra giá trị True hoặc giá trị Sai nghĩa là gì. Thay vào đó, sử dụng donehoặc endOfIteration. Mã biến thành while not done: ....
IceArdor

1

Nếu bạn đang ở trong một kịch bản mà bạn đang lặp trong khi tài nguyên không thể thực hiện được hoặc một cái gì đó tương tự ném ngoại lệ, bạn có thể sử dụng một cái gì đó như

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

Đối với tôi một vòng lặp while điển hình sẽ là một cái gì đó như thế này:

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

Tôi cũng có thể bao gồm một for..loop trong vòng lặp while, nếu tình huống như vậy đảm bảo, cho việc lặp qua một tập hợp điều kiện khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.