게임으로 배우는 파이썬 Part 1 Chapter 3 :제어문
Programming Language/Python

게임으로 배우는 파이썬 Part 1 Chapter 3 :제어문

728x90

1 : 인덴트                                                                                                                   

인덴트란 문장 앞 들여쓰기로 문서를 읽기 쉽게 할 때 사용한다.

파이썬에는 인덴트를 사용해 그룹화하는 특징이 있다.

 

if 조건식:
	명령1
    명령2
명령3

if 문의 조건이 성립했을 때에는 명령1과 2가 실행되고, 그 후 명령3으로 간다. 반대로 조건이 성립하지 않을 때는 명령1과 2를 건너뛰고, 명령3이 실행된다. 다른 언어와 같이 개인의 기호에 따라 인덴트 스타일이 다르지는 않기 때문에 누가 써도 비슷하게 기술된다. 그 결과 읽기 쉬운 코드로 연결된다.

다른 언어의 경우 명령이 하나 이상일 때, { }로 감싸 여러 개의 문장을 하나로 합한다. 이때 사람의 스타일마다 인덴트를 사용하지 않기도 하고 인덴트를 사용해 가독성을 높이기도 한다.

다른 언어에 익숙한 사람에게는 괄호가 없는 것에 위화감을 느낄지도 모르지만 조금만 사용해보면 바로 그 장점을 알게 될 것이다.

 

2 : 조건식 평가                                                                                                         

문장에서 처리의 흐름을 바꿀 때 사용되는 것이 조건식이다.

조건식은 그 값이 True일 때 또는 0 이외의 값일 때 조건이 성립한다고 간주한다.

비교 연산자

비교 연산자는 두 개의 값을 비교하고 부울값을 반환한다.

연산자 의미
A == B 두 개의 값이 같을 때 True 반환
A != B 두 개의 값이 같지 않을 때 True 반환
A < B A가 B보다 작을 때 True 반환
A <= B A가 B 이하일 때 True 반환
A > B A가 B보다 클 때 True 반환
A >= B A가 B 이상일 때 True 반환
A in B A가 B(리스트와 튜플)에 포함돼 있을 때 True 반환

 

부울 연산자

비교 연산자를 사용하면 두 가지 값을 비교할 수 있다.

동시에 여러 개를 비교할 때는 부울 연산자인 and, or, not을 사용한다.

부울 연산자 의미
조건식1 and 조건식2 조건식1과 조건식2가 모두 True일 때 True를 반환
조건식1 or 조건식2 조건식1과 조건식2 중 하나라도 True일 때 True를 반환
not 조건식1 조건식1과 반대 부울값을 반환
>>> A = 3
>>> B = 5
>>> A < 10 and B < 10
True
>>> A < 0 and B < 10
False
>>> A > 0 or B > 10
True
>>> A > 10 or B > 10
False
>>> not A ==3
False
>>> not A == 5
True

하나의 변수에 and를 사용할 때는 다음과 같이 기술할 수도 있다.

>>> x = 7
>>> 0 < x < 10
True
>>> x = 11
>>> 0 < x < 10
False

 

if 문

if 문은 어떤 조건을 충족했을 때 명령을 실행할 때 사용한다.

 

1. if: else:

조건이 성립할 때, 성립하지 않을 때, 각각에서 어떤 처리를 할 때 사용한다.

if 조건식:
	조건식이 True일 때의 처리1
    ...
    조건식이 True일 때의 처리n
else:
	조건식이 False일 때의 처리1
    ...
    조건식이 False일 때의 처리n
다음 처리

 

2. if:

조건식이 False일 때의 처리가 필요 없다면 else 다음은 생략할 수 있다.

if 조건식:
	조건식이 True일 때의 처리1
    ...
    조건식이 True일 때의 처리n
다음 처리

 

3. if: elif:

여러 개의 조건을 사용해서 그것들의 조건에 따라 처리를 나눌때 사용한다.

elif는 else if를 줄인 것이다.

if 조건식1:
	조건식1이 True일 때의 처리1
    ...
    조건식1이 True일 때의 처리n
elif 조건식2:
	조건식2가 True일 때의 처리1
    ...
    조건식2가 True일 때의 처리n
else:
	위의 조건이 전부 False일 때의 처리1
    ...
    위의 조건이 전부 False일 때의 처리n
다음 처리

else 다음이 필요 없다면 생략할 수 있다.

 

3 : 부울값 이외의 값                                                                                                 

if 문에서는 조건식에 부울값(비교 연산자나 부울 연산자 등)을 지정해서 처리의 흐름을 제어할 수 있다.

조건식에는 비교 연산자 뿐만 아니라 값을 직접 지정할 수도 있다.

  조건식이 성립하지 않는다 조건식이 성립한다
수치 0 0이 아닐 때
문자열 빈 문자열 ''나 "" 왼쪽 항의 조건 이외일 때
리스트 빈 리스트 [ ] 왼쪽 항의 조건 이외일 때
튜플 빈 튜플 ( ) 왼쪽 항의 조건 이외일 때

수치, 문자, 리스트를 조건식으로써 평가할 때 어떤 값이 True, False가 되는지 bool( ) 함수를 사용하면 확인이 쉽다.

 

삼항 연산자

if a > 0:
	x = 10
else:
	x = 20

다음 코드를 다음과 같이 변환 가능하다.

x = 10 if a > 0 else 20

 

while

while 문은 반복(루프) 처리를 하기 위한 명령이다.

while 조건식:
	명령1
    명령2
명령3

 

for

for 문은 반복 처리하기 위한 명령이다.

for score in subject:

for 문에서는 subject에서 값을 차례로 꺼내고, 꺼낸 값을 변수 score에 저장한다.

index를 사용하지 않기 때문에 간단하고, 하고자 하는 것이 명백하다.

in 뒤에는 어떤 요소를 차례로 반환하는 것이라면 리스트나 튜플 외에도 지정할 수 있다.

이런 것을 이터러블(iterable) 객체라고 한다.

>>> for letter in "hi!"
		print(letter)
h
i
!

 

range

range 함수는 번호를 반환하는 이터러블 객체를 반환한다.

>>> for index in range(5):
		print(index)
0
1
2
3
4

0부터 4까지의 수치가 차례대로 반환되는 것을 알 수 있다.

인수로 지정한 5는 포함되지 않는 것에 주의해야 한다.

 

0 이외의 번호부터 시작하고자 할 때도 있을 것이다.

그럴 때는 range(시작값, 최댓값)로 두 개의 수치를 인수로 지정한다.

>>> for val in range(3, 7):
		print(val)
3
4
5
6

 

하나 걸러 숫자를 구하고 싶을 때도 있을 것이다.

그럴 때는 range(시작값, 최댓값, 스텝)로 세 개의 수치를 인수로 지정한다.

>>> for val in range(1, 8, 2):
		print(val)
1
3
5
7

 

break와 continue

while이나 for 등의 반복문을 사용하면

  • 반복 도중에 루프를 벗어난다
  • 반복 도중에 루프의 맨 앞으로 되돌아간다

와 같은 처리가 필요할 때가 있다.

이런 상황을 위해 준비돼 있는 것이 break와 continue이다.

break는 루프를 벗어날 때, continue는 루프의 나머지를 건너 뛰고, 루츠의 맨 앞으로 되돌아갈 때 사용한다.

728x90

 

4 : 함수                                                                                                                       

파이썬에서는 직접 함수를 정의할 수도 있다. 복잡한 처리를 함수로 묶어서 적절한 함수명을 붙임으로써 프로그램이 현격하게 읽기 쉽게 될 때도 있다. 복잡한 현상은 추상화하면 전체를 볼 수 있게 되기 때문이다. 또, 프로그램 안에 무언가 같은 처리를 반복해서 쓰고 있네 라고 느끼면 그것은 함수를 정의하는 편이 좋다는 징후이다. 같은 코드가 여러 곳에 편재하면 유지보수가 어려워지기 때문이다.

 

파이썬에서의 함수 정의는 다음과 같이 기술한다.

def 함수명(인수 1, 인수 2, ...):
	명령 1
    ...
    명령 n
    return 반환 값

 

인수가 없다면 생략할 수 있다.

또, 반환값이 필요 없으면 return을 생략할 수도 있다.

>>> def say_hello():
		print("Hi!")
        
>>> say_hello()
Hi!

 

인수의 기본값 지정

인수를 사용하면 함수에 값을 넘겨줄 수 있다. 거의 같은 값을 사용하지만, 때로는 다른 값을 지정하고 싶을 때도 있을 것이다. 그럴 때는 기본값을 설정하면 편리하다.

파이썬에서는 함수를 정의할 때 인수=기본 값으로 적기만 하면 기본값을 설정할 수 있다.

>>> def say_hello(name="Alex"):
		print("Hi! " + name)
        
>>> say_hello()
Hi! Alex
>>> say_hello("David")
Hi! David

인수를 생략했을 때는 변수 name에 기본 값 "Alex"가 대입되는 것을 알 수 있다.

 

람다 함수

프로그래밍을 하고 있으면 함수를 정의한다(=def로 선언하고 함수명을 붙이고 인수명을 정해서 실제 처리를 기술한다) 정도는 아니지만 약간의 작업으로 함수를 사용하고 싶다고 생각할 때가 있다. 그런 요구에 맞게 람다 함수라는 구조가 있다. 서식은 다음과 같다.

>>> lambda x: x % 2 == 0
<function <lambda> at 0x10dd70598>
>>> lambda name: print("Hi!" + name)
<function <lambda> at 0x10db98598>

다만 이대로는 함수에 이름이 없어서 호출할 수 없다.

람다 함수를 변수에 대입하여 ( )를 붙이면 호출할 수 있다.

>>> is_even = lambda x: x % 2 == 0
>>> is_even(2)
True
>>> is_even(3)
False

>>> say_hi = lambda name: print("Hi!" + name)
>>> say_hi("Ken")
Hi! Ken

람다 함수도 def로 정의한 함수와 같이 호출할 수 있다는 것을 알 수 있다. 그러나 이같은 사용법은 람다 함수에게 그다지 일반적이지는 않다. 이러한 용도라면 def를 사용해서 보통의 함수를 정의하면 좋기 때문이다. 람다 함수는 일회용이 보통이다.

 

1. map

map은 리스트나 튜플의 요소 전체에 대해서 어떠한 처리를 할 때 사용한다. 리스트나 튜플의 요소가 차례로 처리를 하는 함수에 인수로써 전달된다. 함수에서는 그런 요소에 어떤 처리를  할지를 기술한다.

>>> def make_double(x):
		return x * 2
        
>>> list(map(make_double, [1, 2, 3]))
[2, 4, 6]

map( ) 함수는 map 객체를 반환한다. map 객체로부터 리스트를 만들려면 list( ) 함수를 사용한다.

 

단순히 값을 두 배로 할 뿐이므로 일부러 def를 사용해서 정의할 필요까지는 없다고 생각할 수도 있다. 이를 람다식을 사용해서 다시 작성한다.

>>> list(map(lambda x: x*2, [1, 2, 3]))
[2, 4, 6]

한 줄로 같은 결과를 얻게 되었다. 람다 함수의 인수 x에 리스트 [1, 2, 3]의 요소가 차례로 전달된다. 람사 함수에서는 그 값을 두 배로 만든다. 이렇게 함으로써 모든 요소에 특정 처리를 적용할 수 있다.

 

2. filter

filter는 이름대로, 조건에 합치한 요소만 추출할 때 이용한다.

어떤 요소를 고를지 함수로 지정하는데, 그 부분에 람다식이 적합하다.

>>> list(filter(lambda a: a%2==0, [0, 1, 2, 3, 4, 5]))
[0, 2, 4]

 

3. sorted

sorted를 사용하면 수치면 오름차순, 문자열은 알파벳순으로 정렬된다. reverse=True 파라미터를 지정하면 역순으로 정렬할 수 있다. 그럼 문자열이 긴 순으로 나열하려면 어떻게 하면 좋을까? 어떤 규칙으로 나열할지를 sorted 함수에 전해야 한다. 여기도 람다 함수가 쓰인다. key 파라미터에 무엇을 기준으로 나열할지를 지정하는 함수를 기술한다.

>>> sorted(["bread", "rice", "spaghetti"], key=lambda x: len(x))
['rice', 'bread', 'spaghetti']
>>> sorted(["bread", "rice", "spaghetti"], key=lambda x: len(x), reverse=True)
['spaghetti', 'bread', 'rice']

위의 예에서는 요소의 길이를 기준으로 하겠다고 지정한다. reverse=True를 지정하면 역순이 된다.

 

또 다른 한 가지 예로 튜플의 리스트를 정렬해보자, key를 지정하지 않으면 0 -> 2 -> 5 로 튜플의 맨 앞의 요소로 정렬이 이루어진다.

>>> sorted([(0, 1), (5, 3), (2, 4)])
[(0, 1), (2, 4), (5, 3)]
>>> sorted([(0, 1), (5, 3), (2, 4)], key=lambda x: x[1])
[(0, 1), (5, 3), (2, 4)]

후자에서는 key를 사용해 정렬하는 기준을 튜플의 두 번째 요소 x[1]로 지정한다. 이로써 1 -> 3 -> 4 로 정렬 순서가 바뀌었다.

 

리스트 내포 표기

map도 filter도 리스트의 요소에 대해서 특정한 처리를 하는 것이었다. 파이썬에서는 리스트 내포 표기(List conprehension)라는 기술로도 같은 처리를 할 수 있다.

>>> [x*2 for x in [1, 2, 3, 4]]
[2, 4, 6, 8]
>>> [x*x for x in range(5)]
[0, 1, 4, 9, 16]

다차원 배열은 리스트의 리스트로써 표현할 수 있다. 결론 부분에 리스트를 기술하면 리스트의 리스트도 작성할 수 있다.

>>> [[x,x+1,x+2] for x in [1, 2, 3]]
[[1, 2, 3], [2, 3, 4], [3, 4, 5]]

>>> [[0 for x in range(3)] for y in range(4)]
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

>>> data = [[x+y*3 for x in range(3)] for y in range(4)]
>>> data
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
>>> [[x*2 for x in row] for row in data]
[[0, 2, 4], [6, 8, 10], [12, 14, 16], [18, 20, 22]]

 

내부 표기에서는 조건식을 추가함으로써 filter와 같은 표현도 할 수 있다.

>>> [x for x in [0,1,2,3,4,5] if x%2==0]
[0, 2, 4]

>>> [x*3 for x in range(6) if x%2==0]
[0, 6, 12]

리스트 내포 표기는 영어권 특유의 기법이므로, 익숙해질 때까지는 조금 시간이 걸릴지도 모른다.

 

5 : 모듈                                                                                                                       

모듈의 임포트

외부 모듈을 넣으려면 import 명령을 사용한다.

모듈을 임포트하면 그 모듈 안에 정의돼 있는 함수를 이용할 수 있다. 아래 예에서는 random모듈을 임포트하고, random.randint( ) 함수로 난수를 생선한다.

import random

for _ in range(5):
	print(random.randint(0, 5))
print("done")

--출력--
5
4
4
3
5
done
randint(x, y) x부터 y까지의 랜덤인 int 값을 구한다

단순한 randint( )가 아니라 random.randint( )라고 모듈명이 앞에 붙는 것에 주의해야 한다. 이것은 함수명 충돌을 막기 위한 것이다. 예를 들면, 게임용 모듈 A에 get_position( )이라는 함수가 있고, 지도용 모듈 B에도 get_position( )이라는 함수가 있다고 하자. 모듈 A와 B 양쪽을 임포트해서 get_position( )함수를 실행하면 어느 쪽이 실행되는 것일까? 이런 애매함을 배제하기 위해서 모듈명을 함수의 앞에 붙이는 것이다. A.get_position( )과 B.get_position( )이라면 어느 쪽의 함수를 호출할지 명확히 구별할 수 있다.

 

필요한 대상만 임포트

특정 함수만 사용한다면 다음 명령을 이용할 수 있다.

from random import randint

for _ in range(5):
	print(randint(0, 5))
print("done")

--출력--
0
3
2
4
4
done

명시적으로 사용하는 함수를 지정하고 있기 때문에 모듈명을 붙이지 않아도 함수를 호출하고 있음에 주의해야 한다.

참고로, for와 in 사이에 _을 지정하고 있다. 보통은 리스트의 요소를 저장하는 변수를 지정한다. 이번에는 루프 안에서 변수를 이용하지 않는다. 보통의 변수를 지정해도 아무 문제는 없지만 변수를 사용하지 않는다라는 의도를 명시하기 위해서 _를 사용했다.

 

main__

파이썬 파일은 기본적으로 위부터 차례로 실행한다. 이때, 함수 정의는 실행과는 다른 것임에 주의해야 한다. 다음과 같은 파일이 있었다고 하자.

명령 1
def add(a, b):
	return a + b
명령 2
def sub(a, b):
	return a - b
명령 3

[명령 1 -> add 함수 정의  -> 명령 2 -> sub 함수 정의 -> 명령 3]으로 처리되어 가는데, add나 sub는 함수를 정의하고 있을 뿐 실행은 되지 않는다. 함수가 실행되는 것은 누군가가 함수를 호출한 타이밍이다. 즉, add(2, 3), sub(5, 2)처럼 호출하지 않으면 함수는 실행되지 않는다.

그런데 파이썬에서는 import 명령을 사용해서 다른 모듈을 읽어 들인다. 즉, 호출하는 쪽과 불리는 쪽, 두 종류가 있다. 내가 작성한 파일이 어떤 때는 호출하는 쪽, 어떤 때는 불리는 쪽이 될지도 모른다. 어떤 상황에서 실행되고 있는지 알아보기 위해서 __name__ 이라는 변수가 준비돼 있다. 자신이 프로그램을 시작한 파일이면 __name__에는  __main__ 이라는 값이 설정된다. 즉, 이 값을 보면 호출 쪽인지 불린 쪽인지 판단할 수 있다.

import ....
초기화 코드
	함수-클래스 정의, 광역 변수 선언 등
def main():
	... 메인 루틴
if __name__ == '__main__':
	main()

__name__=='__main__'이 True일 때, 이 파일부터 실행이 시작된다. 그 경우는 함수 main( )을 실행해서 처리를 시작한다. 만일 다른 파일로부터 import되었다면, 이 조건식은 False가 되기 때문에 main( )은 실행되지 않는다. 참고로 main( )은 많은 언어에서 프로그램 실행의 기점이 되는 함수이다. 위의 예는 관습을 따르는 것뿐 함수명은 main( )으로 한정되는 것은 아니다.

728x90