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 문이 포함되지 않은 경우에도 오류로 처리해야 합니다.
정당화 및 예제
이전의 “best possible” 형식은:
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>
여기서 중요한 차이점은 루프 제어 키워드가 코드 줄에서 first로 나타난다는 점입니다. 특히 색상이 적용된 코드를 읽을 때, 이를 통해 루프의 제어 흐름을 한눈에 더 쉽게 파악할 수 있습니다.
예를 들어, 다음은 이 경우 tarfile 모듈에서 가져온 일반적인 코드 패턴입니다.:
while True:
buf = self._read(self.bufsize)
if not buf:
break
t.append(buf)
이를 읽을 때, break가 if 아래에 들여쓰기되어 있으므로 break가 적용되는 while이 어디에 있는지 생각해야 할 수도 있고, break를 트리거하는 조건을 읽기 위해 거꾸로 추적해야 할 수도 있습니다. 또는 조건을 먼저 읽은 후에야 이 조건이 루프의 흐름을 변경한다는 사실을 알게 됩니다.
새로운 구문을 사용하면 다음과 같이 됩니다.:
while:
buf = self._read(self.bufsize)
break if not buf
t.append(buf)
이를 읽을 때 먼저 break를 확인할 수 있습니다. 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.