2013년 9월 14일 토요일

Python 흐름 제어

Python loop 조건식같은 완전한 흐름 제어 요소들을 제공하고 있다. 여기에서는 이들 각각을 상세하게 알아볼 것이다.

while loop
당신은 이미 while loop 적이 있을 것이다. 전체 while loop 다음과 같다.

while condition:
    body
else:
    post-code

condition true false 값을 판별하는 표현식이다. 값이 True라면 body 반복하여 실행된다. 값이 False 판별되면 while loop post-code 부분을 실행한 후에 종료될 것이다. condition false 되면 body 전혀 실행되지 않고 post-code 실행된다. body post-code 같은 수준의 들여쓰기(indentation) newline들로 나눠진 하나 이상의 Python 구문들의 순서열(sequence)들로 구성된다. Python interpreter 이들을 구분하기 위해서 level 사용한다. 중괄호{} 괄호()같은 다른 구분자(delimiter)들이 필요하지 않다.
while loop else part option이며 일반적으로 사용되지는 않는다.

while condition:
    body
else:
    post-code

body break 없다면 위의 while loop 다음의 while loop 동일하게 동작한다.

while condition:
    body
post-code

그리고 번째 while loop 이해하기에 단순하다. 만약 다른 사람이 해당 code 사용했다면 당신이 혼란스러울 있으므로 여기에서 else 절을 언급하였지만 앞으로는 이상 언급하지는 않을 것이다. 이는 몇몇 상황들에서는 유용하다.

break continue 구문
break continue, 개의 특수 구문들은 while loop body 내에서 사용될 있다. break 실행되면 즉시 while loop 종료하며 else 절의 post-code 수행되지 않는다. continue 실행되면 body 나머지를 건너뛰고 condition 다시 판별하여 loop 정상적으로 진행한다.

if-elif-else 구문
Python if-then-else 구조의 일반적인 형식은 다음과 같다.

if condition1:
    body1
elif condition2:
    body2
elif condition3:
    body3
.
.
.
elif condition(n-1):
    body(n-1)
else:
    body(n)

condition1 true이면 body1 실행되고, 아니라면 condition2 true라면 body2 실행되는 형식으로 True 판별되는 조건을 찾을 때까지 순차적으로 수행되며 True 판별되는 조건을 찾지 못하면 else 절에 해당하는 것으로 판별하여 body(n) 실행한다. while loop 마찬가지로 body 부분은 동일한 수준의 들여쓰기(indentation) newline들로 구분된 하나 이상의 Python 구문들의 순서열들이다. 만약 True 판별되는 조건이 없고 else part 없다면 아무것도 수행하지 않는다.
if 구분 후에는 body 있어야 하지만 특정 조건에 대해서 아무것도 수행하지 않으려면 body part pass 구문을 사용할 있다. (pass 구문은 Python 내에 구문이 필요한 어디라도 사용할 있다.) 구문이 필요한 곳에 pass 구문을 placeholder 제공하면 아무런 동작도 하지 않는다.

if x < 5:
    pass
else:
    x=5

Python에는 case( switch) 구문은 존재하지 않는다.

for loop
Python for loop 다른 language들의 for loop와는 다르다. C for loop에서 통상적인 pattern 반복할 때마다 변수를 증가시킨 후에 test 한다. Python에서 for loop iterate되는 object에서 반환되는 값을 통해 반복된다. 여기서 사용되는 object 순서열로 값들을 생성할 있어야 한다. 예를 들어서 for loop list, tuple, string 내의 모든 항목들에 대해서 반복할 있다. 하지만 iterate되는 object range라고 불리는 특수 함수나 생성자(generator)라고 불리는 특수한 함수 type 있다. 이는 상당히 강력하다. 일반적인 형식은 다음과 같다.

for item in sequence:
    body
else:
    post-code

body sequence 항목마다 번씩 실행된다. 처음에는 sequence 번째 항목이 변수(variable) 지정된 후에 body 수행된다. 다음에 sequence 번째 항목이 변수(variable) 지정된 후에 body 수행된다. 이렇게 sequence 각각의 항목들에 대해서 body 수행되는 형식이다.
else part option이다. while loop else part 마찬가지로 for loop else part 사용되지 않는다. break continue for loop while loop에서 동일하게 동작한다.
다음은 x 숫자의 역수를 출력하는 작은 loop이다.

x = [1.0, 2.0, 3.0]
for n in x:
    print(1 / n)

range 함수
때때로 list 내에 존재하는 값들의 위치를 사용하기 위해 명확한 index들을 통해 loop 수행할 필요가 있다. list 대해서 range len 명령어를 사용하여 for loop에서 사용될 index들의 순서열(sequence) 생성할 있다. code list 내에서 음수인 위치를 모두 출력한다.

x = [1, 3, -7, 4, 9, -5, 4]
for i in range(len(x)):
    if x[i] < 0:
        print("Found a negative number at index ", i)

n 숫자값으로 range(n) 전달하면 순서열 0, 1, 2, ..., n-2, n-1 반환한다. 그러므로 len 사용하여 list 길이를 range 전달하면 list 항목들의 index들의 순서열을 생성한다. range 함수는 정수로 Python list 생성하지는 않는다. 단지 그렇게 보일 뿐이다. 대신 요청하는 정수들을 생성하는 range object 생성한다. 이는 상당한 크기를 가진 list 반복자(iterator) 사용하여 for loop 사용하려고 유용하다. 예를 들어 1000 개의 항목을 가진 list 사용하면 상당한 memory 사용하지만, range(10000000) 사용하면 memory 적게 사용하면서 for loop 필요한 0부터 10000000까지의 정수들의 순서열(sequence) 생성할 있다.

range에서 생성되는 값들의 시작과 증가폭을 제어
생성되는 순서열(sequence) 대한 제어를 위해 range 함수를 가지 방식으로 다르게 사용할 있다. range 숫자 argument 사용한다면, 번째 argument 결과 순서열(sequence) 시작 숫자이며 번째 argument 포함되지 않는 최대값으로 사용된다. 다음은 이에 대한 가지 예제이다.

>>> list(range(3, 7))
[3, 4, 5, 6]
>>> list(range(2, 10))
[2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(5, 3))
[]

list() range 생성할 항목들을 list 형식으로 표현하기 위해서만 사용되었다. 이는 실제 code에서는 일반적으로 사용되지 않는다.
방식에서는 거꾸로 count하는 것이 허용되지는 않는다. range(5, 3) 때문에 list 반환한다. range 번째 option argument 사용하여 거꾸로 count하거나 1외의 다른 값으로 count 있다. 번째 argument에는 count 하는 간격 값을 입력한다.

>>> list(range(0, 10, 2))
[0, 2, 4, 6, 8]
>>> list(range(5, 0, -1))
[5, 4, 3, 2, 1]

range 의해서 반환되는 순서열(sequence) 항상 번째 argument 받는 시작 값을 포함하고 번째 argument 받는 종료 값을 포함하지 않는다.

for loop에서 break continue 사용
break continue 특수 구문도 또한 for loop body 내에 사용될 있다. break 실행되면 즉시 for loop 종료하며 (만약 else 절이 있다면) post-code 수행되지 않는다. for loop에서 continue 실행되면 body 나머지 부분을 skip하며 다음 항목에 대해서 정상적으로 loop 수행된다.

for loop tuple unpacking
for loop 명확한 표현을 위해서 tuple unpacking 사용할 수도 있다. 다음 code 항목으로 tuple들의 list 사용하여 tuple 내의 숫자의 합을 연산한다.

somelist = [(1, 2), (3, 7), (9, 5)]
result = 0
for t in somelist:
    result = result + (t[0] * t[1])

다음 code 위의 code 동일하게 동작하지만 명확하다.

somelist = [(1, 2), (3, 7), (9, 5)]
result = 0
for x, y in somelist:
    result = result + (x * y)

위의 code에서는 for 예약어 다음에 일반적인 단일 변수가 아니라 tuple x, y 바로 사용하였다. 각각의 for loop 반복(iteration)마다 x list 현재 tuple 0 번째 항목을 가지며 y 1 번째 항목을 가지게 된다. Python 편의성으로 이와 같은 방식으로 tuple 사용할 있으며, list 각각의 tuple 항목이 for 이후에 오는 변수명들을 적절한 크기로 unpack 있다는 것을 보여준다.

enumerate 함수
tuple unpacking enumerate 함수를 결합하여 이들의 항목들과 index 모두 loop 있다. 이는 range 사용하는 것과 유사하지만 code 명확하게 표현할 있으며 이해하기 쉽게 해준다는 장점을 가지고 있다. 이전의 예제와 같이 다음 code list 모든 음수들의 위치들을 출력한다.

x = [1, 3, -7, 4, 9, -5, 4]
for i, n in enumerate(x):
    if n < 0:
        print("Found a negative number at index ", i)

enumerate 함수는 (index, item) tuple 반환한다. index 없이 item 접근할 있으며 index 접근이 가능하다.

zip 함수
looping하기 전에 이상의 iterable들을 결합하는 것은 때때로 유용하다. zip 함수는 가장 짧은 iterable 끝날 때까지 하나 이상의 iterable들에서 각각에 해당하는 항목들을 tuple 결합해준다.

>>> x = [1, 2, 3, 4] # x 4개의 항목들을 가지고
>>> y = ['a', 'b', 'c'] # y 3개의 항목들을 가지지만
>>> z = zip(x, y) # z 3개의 항목들만 가지게 된다.
>>> list(z)
[(1, 'a'), (2, 'b'), (3, 'c')]

list comprehension dictionary comprehension
list iterate하여 개별 항목들을 수정하거나 선택하여 새로운 list dictionary 생성하는 for loop pattern 매우 일반적이다. 이와 같은 loop 다음과 유사한 형태를 종종 사용한다.

>>> x = [1, 2, 3, 4]
>>> x_squared = []
>>> for item in x:
...      x_squared.append(item * item)
...
>>> x_squared
[1, 4, 9, 16]

이런 종류의 상황은 매우 일반적이라 Python 이런 연산을 위해서 comprehension이라고 부르는 특수 편의기능을 제공한다. list comprehension이나 dictionary comprehension 다른 list에서 새로운 list dictionary 생성하는 줄짜리 for loop 생각할 있다. 다음은 list comprehension pattern이다.

new_list = [expression for variable in old_list if expression]

그리고 다음은 dictionary comprehension pattern이다.

new_dict = {expression:expression for variable in list if expression}

가지 경우에서 표현식의 핵심은 for loop(for variable in list) 시작 부분과 새로운 key 또는 value 생성하기 위한 변수(variable) 사용하는 표현식 부분, 변수(variable) value 새로운 list dictionary 내에 포함할 지를 선택하기 위한 option 조건식 부분이다. 예를 들면 다음 code 이전 예제 code 동일하게 동작하지만 list comprehension 사용한다.

>>> x = [1, 2, 3, 4]
>>> x_squared = [item * item for item in x]
>>> x_squared
[1, 4, 9, 16]

list에서 항목들을 선택하기 위해서 if 구문을 사용하는 것도 가능하다.

>>> x = [1, 2, 3, 4]
>>> x_squared = [item * item for item in x if item > 2]
>>> x_squared
[9, 16]

dictionary comprehension 유사하지만 key value 모두 제공해야 한다. 만약 이전의 예제와 유사하게 동작하며 숫자값을 key 숫자값의 제곱을 value 하여 dictionary 담으려면 dictionary comprehension 다음과 같이 사용할 있다.

>>> x = [1, 2, 3, 4]
>>> x_squared_dict = {item: item * item for item in x}
>>> x_squared_dict
{1: 1, 2: 4, 3: 9, 4: 16}

list comprehension dictionary comprehension 매우 유연하고 강력하다. 이들을 이용하면 list-processing 연산들을 매우 단순하게 있다. list 항목들을 처리하기 위해서 for loop 작성한다면 이들을 실험하여 사용하는 것을 권장하고 싶다.

구문, block, 들여쓰기(indentation)
여기에서 우리가 접한 흐름 제어 구조(control flow construct)들은 처음으로 block들과 들여쓰기(indentation) 사용한 것이므로 주제를 다시금 되새기는 좋은 기회가 것이다.
Python 흐름 제어 구조(control flow construct)에서 block 또는 body들을 구분하기 위해서 구문에 들여쓰기(indentation) 사용한다. block 일반적으로 newline들로 구분되는 하나 이상의 구문들로 구성된다. Python 구문들의 예제들은 구문 할당, 함수 호출, print 함수, placeholder pass 구문, del 구문이다. 흐름 제어 구조(control flow construct : if-elif-else, while loop, for loop) 구문들의 복합체이다.

compound statement clause:
    block
compound statement clause:
    block

구문 복합체(compound statement) 들여쓰기 block 다음에 오는 하나 이상의 절들을 포함한다. 구문 복합체(compound statement)들은 block 내에서 다른 구문과 같이 표시될 있다. 이들이 실행되면 중첩(nested) block들을 생성한다.
당신은 쌍의 특수 case들도 접할 있다. 다중 구문(multiple statement)들은 semicolon(;)들로 구분하여 동일한 line 상에 위치할 있다. 단일 line 포함하는 block 구문 복합체(compound statement) 다음의 semicolon(;) 후에 오도록 하여 동일 line 상에 위치하도록 있다.

>>> x = 1; y = 0; z = 0
>>> if x > 0: y = 1; z = 10
...     else: y = -1
...
>>> print(x, y, z)
1 1 10

부적절하게 들여쓰기 (indented) code exception 발생시킬 것이다. 이는 가지 형태로 접할 있다. 번째는 다음과 같다.

>>>
>>>      x = 1
File "<stdin>", line 1
x = 1
^
IndentationError: unexpected indent
>>>

위의 예제에서는 들여쓰기 하지 않아야 부분에서 line 들여 썼다. basic mode에서 carat(^) 문제가 발생한 곳을 나타내준다.
exception 발생할 있는 하나의 상황은 헷갈릴 있을 것이다. tab 입력하면 개의 space들로 표시되는 편집기를 사용한다고 가정하자. line 개의 space들로 들여 쓰고 다음 line tab으로 들여쓰기를 하여 code 작성하였다. 이들 line들은 동일한 level 들여쓰기(indentation)으로 보일 것이다. 하지만 이는 위의 exception 발생될 것이다. 이는 Python tab 여덟 개의 space mapping하고 있기 때문이다. 문제를 방지하기 위한 가장 좋은 방법은 Python code 내에 space들만 사용하는 것이다. 만약 반드시 들여쓰기(indentation) tab 사용해야 하거나 tab들을 사용한 code 다뤄야 하는 상황이라면 space들과 섞어서 사용하지 않도록 해야 한다.
기본 대화식 mode에서는 들여쓰기(indentation) 가장 바깥쪽 level 후에 추가 line 필요하다는 것을 주의하라.

>>> x = 1
>>> if x == 1:
...      y = 2
...      if v > 0:
...          z = 2
...          v = 0
...
>>> x = 2

z = 2 line 이후는 line 필요하지 않지만 v = 0 line 이후에는 하나의 line 필요하다. code file 내의 module 작성한다면 line 필요하지 않다.
exception 번째 형태는 block 내의 구문을 정해진 크기보다 적게 들여 썼을 발생한다.

>>> x = 1
>>> if x == 1:
...          y = 2
...      z = 2
File "<stdin>", line 3
z = 2
^
IndentationError: unindent does not match any outer indentation level

여기에서 y = 2 포함하는 line 아래의 z = 2 포함하는 line 적절하게 배치되지 않았다. 이런 형태는 드물지만 유사한 상황에서는 혼란을 있으므로 주의하기 바란다.
Python 얼마만큼 들여쓰기(indentation) 하던지 상관하지 않으며 단일 block 내에서 일관성이 있다면 다르게 들여쓰기(indentation) 사용해도 허용된다. 이런 부적절한 이점을 이용하지 않고 표준적으로 level 들여쓰기(indentation)에는 개의 space들을 사용하는 것을 권장한다.
들여쓰기에 대한 설명을 마치기 전에 구문을 여러 line들로 나누는 것을 설명할 것이다. 이는 들여쓰기(indentation) level 증가할 종종 사용된다. backslash(\) 문자를 사용하여 line 명확하게 나눌 있다. (), {}, [] 구분자들 내의 구문은 암시적으로 나눌 있다. , list 내의 값들, tuple, dictionary, 또는 함수 호출 내의 argument 같은 괄호 내의 모든 표현식들로 형식은 이에 해당된다. 연속되는 line 구문을 당신이 원하는 level 들여 있는 것이다.

>>> print('string1', 'string2', 'string3' \
...      , 'string4', 'string5')
string1 string2 string3 string4 string5
>>> x = 100 + 200 + 300 \
...      + 400 + 500
>>> x
1500
>>> v = [100, 300, 500, 700, 900,
...      1100, 1300]
>>> v
[100, 300, 500, 700, 900, 1100, 1300]
>>> max(1000, 300, 500,
...      800, 1200)
1200
>>> x = (100 + 200 + 300
...      + 400 + 500)
>>> x
1500

문자열도 마찬가지로 backslash(\) 나눌 있다. 하지만 들여쓰기(indentation) tab이나 space들은 문자열의 일부가 것이고 line 반드시 backslash(\) 끝나야 한다. 이를 방지하기 위해서는 공백(whitespace) 나눠지는 string literal들의 집합은 자동으로 연결된다는 점을 이용할 있다.

>>> "strings separated by whitespace " \
...      """are automatically""" ' concatenated'
'strings separated by whitespace are automatically concatenated'
>>> x = 1
>>> if x > 0:
...      string1 = "this string broken by a backslash will end up \
...      with the indentation tabs in it"
...
>>> string1
'this string broken by a backslash will end up \t\t\twith the indentation tabs in it'
>>> if x > 0:
...      string1 = "this can be easily avoided by splitting the " \
...          "string in this way"
...
>>> string1
'this can be easily avoided by splitting the string in this way'

Boolean value 표현식(expression)
이전의 흐름 제어(control flow) 예제들은 상당히 명확한 방식으로 조건 test 사용했지만 Python에서 true false 되는 것이나 조건 test 필요한 곳에 사용될 있는 표현식(expression)들이 무엇인지를 실제로 설명하지는 않았다. 여기에서는 Python 이런 측면들을 설명할 것이다.
Python True False 설정될 있는 Boolean object type 가지고 있다. Boolean 연산 표현식은 True False 반환한다.

대부분의 Python object들은 Boolean으로 사용할 있다.
추가적으로 Python C 유사하게 Boolean 값들을 반영한다. C에서는 정수 0 거짓(false) 의미하며 다른 정수는 진실(true) 의미한다. Python에서는 개념을 활용하여 0이나 값은 False 다른 값들은 True 인식한다. 실질적인 측면에서 이는 다음을 의미한다.

숫자 0, 0.0, 0+0j False이며 다른 숫자는 True이다.
문자열 "" False이며 다른 문자열은 True이다.
list [] False이며 다른 list True이다.
집합인 set() False이며 다른 집합은 True이다.
Python 특수 값인 None 언제나 False이다.

우리가 아직 보지 못한 Python 자료 구조(data structure)들도 있지만 일반적으로 동일한 규칙을 적용 받는다. 만약 자료 구조(data structure) 비었거나 0이면 Boolean 문맥 상으로 거짓으로 간주되며, 아니라면 진실로 간주된다. file object code object 같은 가지 object들은 0이나 항목을 다른 값으로 정의하지 않으며 boolean 문맥 내에 사용되어서도 않된다.

비교와 Boolean 연산자들
object들은 <, <=, >, >=, 등의 일반 연산자들을 사용하여 비교할 있다. == 같음을 test하는 연산자이며, != <> 같지 않음을 test하는데 사용된다. 순서열(list, tuple, string, dictionary) 내의 존재 유무를 test하는 in not in 연산자들도 있으며 마찬가지로 object들이 동일한 지를 test하는 is is not 연산자들도 존재한다.
Boolean 값을 반환하는 표현식들은 and, or, not 연산자들로 결합하여 복합 표현식을 생성할 있다. 다음 code 조각은 변수가 특정 범위 내에 속하는 지를 검사한다.

if 0 < x and x < 10:
...

Python 특정 형태의 구문 복합체(compound statement) 위해 간편한 shorthand 제공한다. 당신은 이를 수학 식을 통해 작성 있다.

if 0 < x < 10:
...

다양한 규칙들은 우선 순위에 의해서 적용된다. 의심스럽다면 당신이 원하는 대로 Python 표현식을 해석하게 하기 위해서 괄호를 사용할 있다. 이는 필요성에 관계없이 복잡한 표현식에 적용하기 좋은 idea이다. 해당 code 유지보수하는 사람에게 무엇이 일어나고 있는지 명확하게 해주기 때문이다. 우선 순위 규칙은 공식문서(http://docs.python.org/2/reference/expressions.html) 참조하라.
글의 나머지에서는 상세한 고급 정보를 제공할 것이다.
and or 연산자들은 object 반환한다. and 연산자는 표현식이 false 판명되면 번째 false object 반환하고 아니라면 마지막 object 반환한다. 이와 유사하게 or 연산자는 표현식이 true 판명되면 번째 true object 반환하고 아니라면 last object 반환한다. 다른 language들처럼 or 연산자에서는 true 표현식을 찾는다면 바로 논리 연산이 정지되며, and 연산자에서는 false 표현식을 찾는다면 바로 논리 연산이 정지된다.

>>> [2] and [3, 4] [3, 4]
>>> [] and 5
[]
>>> [2] or [3, 4]
[2]
>>> [] or 5
5
>>>

== != 연산자들은 피연산자들이 동일한 값들의 포함 여부를 판별한다. 이들은 많은 상황에서 사용된다. is is not 연산자들은 피연산자들이 동일한 object인지를 판별한다.

>>> x = [0]
>>> y = [x, 1]
>>> x is y[0] # 이들은 동일한 object 참조한다.
True
>>> x = [0] # x 다른 object 할당한다.
>>> x is y[0]
False
>>> x == y[0]
True

위의 예제의 논리에 관련해서는 내포된(nested) list 깊은 복사(deep copy) 참조하기 바란다.

text file 분석하는 단순한 program 작성
Python program 동작에 대한 이해를 돕기 위해 예제 program 살펴보자. program 대략 UNIX wc utility 기능들을 복제한 것으로 file 내의 line, 단어, 문자들의 숫자를 보고한다. 다음 예제는 Python 새로 배우는 programmer들을 위해 가능한 단순하고 명확하게 작성되었다.

word_count.py

#!/usr/bin/env python3
""" file 읽어서 line, 단어, 문자들의 숫자를 반환한다.
- UNIX wc utility 유사하다.
"""
infile = open('word_count.txt') # file open한다.
lines = infile.read().split("\n") # file 읽고 line들로 나눈다.
line_count = len(lines) # len()으로 line들의 숫자를 구한다.
word_count = 0
char_count = 0 # 다른 count들은 초기화한다.
for line in lines: # lines iterate 시킨다.
    words = line.split() # words 나눈다.
    word_count += len(words)
    char_count += len(line) # 문자들의 숫자를 반환한다.
print("File has {0} lines, {1} words, {2} characters".format(count, word_count, char_count))

test 위해서 다음 내용을 담은 text file 사용하여 program 실행한다.

word_count.txt

Python provides a complete set of control flow elements,
including while and for loops, and conditionals.
Python uses the level of indentation to group blocks
of code with control elements.

word_count.py 수행하면 다음과 같은 출력 결과를 얻게 것이다.

vern@mac:~/quickpythonbook/code $ python3.1 word_count.py
File has 4 lines, 30 words, 189 characters

code 당신에게 Python program 동작에 대한 개념을 알려줄 것이다. code 양이 많지 않고 for loop 내의 line code에서 대부분의 연산이 이루어진다. 대부분의 Python 추종자들은 간결성을 Python 가장 강점의 하나로 여긴다.

요약
Python while loop, for loop, 조건식들을 포함하는 완전한 흐름 제어(control-flow) 항목들을 제공한다. Python 제어 항목들에서 code block 묶는데 들여쓰기(indentation) level 사용한다.
Python Boolean 값인 True False 가지고 있다. 이는 변수(variable)들에 의해서 참조될 있지만 0이나 값은 거짓(false) 되고 0 아니거나 값이 아니라면 진실(true) 판별되는 특징도 있다.
흐름 제어(control flow) programming에서 중요한 부분을 차지한다. 이와 마찬가지로 code block들을 package 하고 재사용하는 것도 programming에서 매우 중요하다. 후에는 함수(function) 시작으로 Python에서 code package화와 재사용에 대해서 알아볼 있는 기회를 가질 것이다.