PEP 548 – 더 유연한 루프 제어
- Author:
- R David Murray
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 05-Sep-2017
- Python-Version:
- 3.7
- Post-History:
- 05-Aug-2017
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
거부 기록
Guido의 거부: https://mail.python.org/pipermail/python-dev/2017-September/149232.html
초록
이 PEP는 break와 continue 문에 실행 여부를 제어하는 선택적 불리언 표현식을 추가하여 이 문들을 개선할 것을 제안합니다. 이를 통해 루프의 제어 흐름을 더 명확하고 간결하게 표현할 수 있습니다.
동기
거부된 PEP 315에서 인용합니다:
while 루프 조건을 평가하기 전마다 어떤 코드를 실행해야 하는 경우가 자주 있습니다. 이 코드는 루프에 들어가기 전에 한 번 실행되는 설정 코드로서 루프 밖에 중복되는 경우가 많습니다:<setup code> while <condition>: <loop body> <setup code>
그 PEP는 다음 형태보다 우수한 문법을 찾지 못했기 때문에 거부되었습니다:
while True:
<setup code>
if not <condition>:
break
<loop body>
이 PEP는 더 우수한 형태를 제안하며, 이 형태는 for 루프에도 적용할 수 있습니다. 이 형태가 더 우수한 이유는 Python의 들여쓰기 미학을 유지하면서도 루프의 제어 흐름을 더 명시적으로 만들기 때문입니다.
문법
break 문과 continue 문의 문법은 다음과 같이 확장됩니다:
break_stmt : "break" ["if" expression]
continue_stmt : "continue" ["if" expression]
또한 while 문의 문법은 다음과 같이 수정됩니다:
while_stmt : while1_stmt|while2_stmt
while1_stmt : "while" expression ":" suite
["else" ":" suite]
while2_stmt : "while" ":" suite
의미론
break if 또는 continue if는 expression이 참으로 평가될 때, 그리고 그때에만 실행됩니다.
표현식이 없는 while 문은 마치 while True 문인 것처럼 break나 return이 실행될 때까지(또는 오류가 발생할 때까지) 반복합니다. 이 루프는 else 스위트를 실행시키지 않는 방식으로만 종료될 수 있으므로, 표현식이 없는 형태에서는 else 스위트를 허용하지 않습니다. 가능하다면, 표현식이 없는 while의 본문에 break 또는 return 문이 하나 이상 포함되어 있지 않은 경우에도 오류로 처리해야 합니다.
근거와 예제
이전의 “가능한 최선의” 형태는:
while True:
<setup code>
if not <condition>:
break
<loop body>
다음과 같이 서식을 지정할 수 있습니다:
while True:
<setup code>
if not <condition>: break
<loop body>
이는 겉보기에는 이 PEP가 제안하는 형태와 거의 동일합니다:
while:
<setup code>
break if not <condition>
<loop body>
여기서 중요한 차이점은 루프 흐름 제어 키워드가 코드 줄의 맨 앞에 나타난다는 것입니다. 이 덕분에 특히 색상이 입혀진 코드를 읽을 때 루프의 제어 흐름을 한눈에 파악하기가 더 쉬워집니다.
예를 들어, 다음은 흔한 코드 패턴으로, 이 경우에는 tarfile 모듈에서 가져왔습니다:
while True:
buf = self._read(self.bufsize)
if not buf:
break
t.append(buf)
이 코드를 읽을 때, 우리는 먼저 break를 보게 되면 break가 if 아래에 들여쓰기되어 있으므로 이것이 어느 while에 적용되는지 생각해야 할 수도 있고, 그다음에는 거슬러 올라가 break를 일으키는 조건을 읽어야 합니다. 아니면 조건을 먼저 읽고 나서야 비로소 이 조건이 루프의 흐름을 바꾼다는 것을 알게 됩니다.
새 문법을 사용하면 이는 다음과 같이 됩니다:
while:
buf = self._read(self.bufsize)
break if not buf
t.append(buf)
이 코드를 읽을 때 우리는 먼저 break를 보게 되는데, 이것은 루프 본문과 같은 들여쓰기 수준에 있으므로 while에 적용된다는 것이 명백하며, 그다음에 제어 흐름을 바꾸는 조건을 읽게 됩니다.
더 나아가, sre_parse에서 가져온 더 복잡한 예를 살펴봅시다:
while True:
c = self.next
self.__next()
if c is None:
if not result:
raise self.error("missing group name")
raise self.error("missing %s, unterminated name" % terminator,
len(result))
if c == terminator:
if not result:
raise self.error("missing group name", 1)
break
result += c
return result
현재 Python의 루프 제어 문법에서는 이것이 이 코드를 작성하는 자연스러운 방법입니다. 그러나 break if가 있다면 다음과 같이 작성하는 것이 더 자연스러울 것입니다:
while:
c = self.next
self.__next()
break if c is None or c == terminator
result += c
if not result:
raise self.error("missing group name")
elif c is None:
raise self.error("missing %s, unterminated name" % terminator,
len(result))
return result
이 형태는 오류 처리를 루프 본문 밖으로 옮겨서 루프 로직을 훨씬 더 이해하기 쉽게 만듭니다. 현재 문법으로도 코드를 이렇게 작성하는 것이 물론 가능하지만, 제안된 문법을 사용하면 더 명확한 형태로 작성하는 것이 더 자연스러워집니다.
제안된 문법은 또한 다른 언어에서 볼 수 있는 고전적인 repeat ... until <expression> 구문을 자연스럽고 Python다운 방식으로 표기하는 방법을 제공하는데, 이 구문에 대해서는 지금까지 Python에 적합한 좋은 문법을 찾지 못했습니다:
while:
...
break if <expression>
예를 들어 tarfile 모듈에는 다음과 같은 “read until” 루프가 몇 개 있습니다:
while True:
s = self.__read(1)
if not s or s == NUL:
break
새 문법을 사용하면 이 코드를 더 명확하게 읽을 수 있습니다:
while:
s = self.__read(1)
break if not s or s == NUL
이 문법을 continue로 확장해야 한다는 근거는 그보다 약하지만, 일관성이라는 가치가 이를 뒷받침합니다.
continue 문은 zipfile의 다음 예제처럼 여러 줄로 된 if 스위트의 끝에 오는 경우가 훨씬 더 흔합니다
while True:
try:
self.fp = io.open(file, filemode)
except OSError:
if filemode in modeDict:
filemode = modeDict[filemode]
continue
raise
break
이 루프에 대해 새 문법이 제공할 수 있는 개선점은 True 토큰을 생략할 수 있다는 것뿐입니다.
반면에 uuid.py의 다음 예제를 살펴보십시오:
for i in range(adapters.length):
ncb.Reset()
ncb.Command = netbios.NCBRESET
ncb.Lana_num = ord(adapters.lana[i])
if win32wnet.Netbios(ncb) != 0:
continue
ncb.Reset()
ncb.Command = netbios.NCBASTAT
ncb.Lana_num = ord(adapters.lana[i])
ncb.Callname = '*'.ljust(16)
ncb.Buffer = status = netbios.ADAPTER_STATUS()
if win32wnet.Netbios(ncb) != 0:
continue
status._unpack()
bytes = status.adapter_address[:6]
if len(bytes) != 6:
continue
return int.from_bytes(bytes, 'big')
이는 다음과 같이 됩니다:
for i in range(adapters.length):
ncb.Reset()
ncb.Command = netbios.NCBRESET
ncb.Lana_num = ord(adapters.lana[i])
continue if win32wnet.Netbios(ncb) != 0
ncb.Reset()
ncb.Command = netbios.NCBASTAT
ncb.Lana_num = ord(adapters.lana[i])
ncb.Callname = '*'.ljust(16)
ncb.Buffer = status = netbios.ADAPTER_STATUS()
continue if win32wnet.Netbios(ncb) != 0
status._unpack()
bytes = status.adapter_address[:6]
continue if len(bytes) != 6
return int.from_bytes(bytes, 'big')
이 예제는 continue if도 루프 코드의 가독성을 높이는, 결코 사소하지 않은 사용 사례가 있다는 것을 보여줍니다.
이 PEP를 위해 선택된 예제는 모두 표준 라이브러리에서 while True와 continue를 grep으로 검색하여 찾은 것이며, 관련 예제는 처음 살펴본 네 개의 모듈에서 발견되었다는 점은 아마도 주목할 만할 것입니다.
Copyright
This document is placed in the public domain.