Back
Featured image of post 제어문 (2)

제어문 (2)

함수, 모듈

5. 함수

정의

def 함수명(인수 1, 인수 2, ...) :
    명령 1
    명령 2
    . . .
    명령 n
    return value

일부 요소들은 생략 가능하다.

  • 인수가 없다면 생략할 수 있다.
  • 반환값이 필요 없으면 return을 생략할 수 있다.

기본값 지정

특정 인수에 기본값을 지정해줄 수도 있다. 매개변수 선언 시 매개변수 = 기본값의 형태로 작성한다.

def plus(a = 0, b = 0) :
    return a + b

함수 호출 시 해당 매개변수의 입력을 생략하면 자동으로 기본값으로 설정된다.

중요한 점은, 기본값을 갖는 매개변수들은 무조건 뒤쪽에 배치되어야 한다.

뇌피셜 : 기본값이 지정된 매개변수가 중간에 선언된 경우, 실제 함수 호출 시 입력되는 값들이 어떤 매개변수에 전달되어야 하는지를 명확하게 알 수 없는 문제가 발생하기 때문이 아닐까?

즉, ① def sum(a = 0, b)는 불가능하고, ② def sum(a, b = 0)은 가능하다. sum(4)를 실행하는 경우 ①번은 4라는 값이 a, b 중 어디에 전달되어야 하는지 명확하게 알 수 없기 때문이다.

람다 함수

def 예약어를 사용한 함수 정의 외에도 간단하게 함수를 정의해서 사용하는 방법도 있다.

>>> lambda 인수 1, 인수 2, ... : 반환값

이름이 없으므로 직접적인 호출이 불가능하다. 따라서 변수에 대입하고 C의 함수 포인터처럼 사용할 수 있다.

>>> plus = lambda x, y : x + y
>>> plus(4, 2)
6

하지만 이렇게 사용하면 그냥 def와 별다른 차이가 없으므로, lambda 함수는 인자로 ‘함수’를 입력받는 함수들에 일회용으로 사용되곤 한다.

map()

map()함수는 리스트나 튜플의 전체 요소에 대해 특정한 처리를 하고 싶을 때 사용한다.

map(처리를 하는 함수, 처리 대상 리스트/튜플)의 방식으로 사용한다.

deflambda 2가지를 사용하여 리스트의 값을 제곱하는 예시를 살펴보자.

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

map()함수는 map 객체를 반환하므로 list()함수를 사용하여 형변환을 해 줌으로써 결과로서 리스트를 얻는다.

다음은 lambda를 사용한 경우이다.

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

map() 함수 밖에서 사용하고자 하는 간단한 기능의 함수를 정의할 필요 없이, map() 함수 호출 시에 간단하게 원하는 기능을 함수로서 만들고자 할 때 lambda 함수를 사용할 수 있다.

이처럼 lambda 함수를 사용해 인자로 함수를 입력받는 함수들의 종류는 매우 다양하다.

filter()

조건에 맞는 요소만 추출하는 함수이다.

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

sorted()

sorted()는 전달된 리스트를 직접 정렬하지 않고, 요소들이 정렬된 또 다른 리스트를 반환하는 함수이다.

labmda 함수를 인자로 전달해 key = lambda . . .형태로 정렬 기준을 지정해줄 수 있다.

>>> sorted(["watermelon", "mango", "grapefruit", "banana"])
['banana', 'grapefruit', 'mango', 'watermelon']
>>> sorted(["watermelon", "mango", "grapefruit", "banana"], key = lambda x : len(x))
['mango', 'banana', 'watermelon', 'grapefruit']

리스트 각 요소의 길이를 기준으로 정렬된 결과 리스트가 반환된 것을 볼 수 있다.

reverse=True 인자를 입력하는 경우 정렬 순서를 반대로 설정할 수 있다.

>>> sorted(["watermelon", "mango", "grapefruit", "banana"], key = lambda x : len(x), reverse=True)
['watermelon', 'grapefruit', 'banana', 'mango']

튜플의 두 번째 요소를 기준으로 정렬하는 등의 활용도 가능하다.

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

리스트 내포 표기법 (List comprehension)

리스트에 저장할 요소의 규칙 등을 직관적으로 지정하여 리스트를 생성할 수 있다.

[식 for 요소명 in 리스트]의 형태로 사용한다. 예시를 살펴보자.

>>> [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]
  • 이를 활용해 간단하게 2차원 배열을 만들 수도 있다.
>>> [[x, x * 2, x * 3] for x in [1, 2, 3]]
[[1, 2, 3], [2, 4, 6], [3, 6, 9]]
  • 특정 값으로 초기화되는 다차원 리스트의 생성도 range()와 함께 활용하여 간단하게 해결할 수 있다.
>>> [[0 for x in range(3)] for y in range(4)]
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
  • 바로 위의 예시에서는 xy가 큰 의미가 없지만, 이를 살짝 변형하여 1씩 증가하는 값을 저장할 수도 있다.

y를 통해 만든 4칸의 리스트의 각 요소에 x를 통해 만든 3칸의 리스트를 각각 넣고 있다. 따라서 y 순번에 3을 곱한 후, x의 순번을 더해 0부터 11까지 값이 차례대로 증가하는 다차원 리스트를 만들 수 있다.

>>> [[x + y * 3 for x in range(3)] for y in range(4)]
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
  • 이미 만들어진 2차원 배열에 접근하여 값을 변경하는 것도 가능하다.
>>> data = [[x + y * 3 for x in range(3)] for y in range(4)]
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
>>> [[x * x for x in row] for row in data]
[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81, 100, 121]]
  • 조건식을 추가하여 filter()처럼 사용할 수도 있다.

[식 for 요소명 in 리스트 if 조건식]의 형태로 사용한다. 조건을 충족한 요소에 대해서만 처음 식이 실행된다.

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

[0, 1, 2, 3, 4, 5] 중에서 x % 2 == 0을 충족시키는 값만 x를 통해 리스트에 저장되는 것을 볼 수 있다.

6. 모듈

파이썬의 모듈은 C의 라이브러리와 같은 역할을 한다. 필요한 모든 함수나 자료구조를 전부 만들어 쓰는 것은 생산적이지 못하기 때문에, 기존에 정의된 모듈 혹은 사용자 정의 모듈을 가져와서(import) 사용할 수 있다.

임포트(Import)

특정 모듈을 사용하려면 해당 모듈을 가져와야, 즉, 임포트(Import)해야 한다. 예를 들어, 난수를 생성하려면 random 모듈을 임포트(Import)해야 한다.

import random
print(random.randint(0, 5))

위 예시의 randint() 함수는 random 모듈을 임포트하지 않으면 사용할 수 없다.

수많은 모듈과 각 모듈에 포함된 다양한 함수가 있지만, 이 단락에서 모두 소개하지 않겠고, 할 수도 없다. (그리고 뇌피셜이지만, 그때그때 필요하면 찾아보고 쓰는 게 코딩을 잘 하는 게 아닐까?)

필요한 것만 임포트하기

전체 모듈을 임포트하는 대신, 해당 모듈에서 필요한 함수만 임포트할 수도 있다.

from 모듈명 import 함수명의 형태로 임포트한다.

from random import randint
print(random.randint(0, 5))

당연하겠지만 이 경우 randint()를 제외한 random 모듈 내의 다른 함수들은 사용이 불가능하다.

__name__

모듈은 누군가 작성한 파일을 내가 빌려 와서 사용하는 형태이다. 따라서 내가 작성한 파일도 모듈이 될 수 있다.

__name__ 변수를 이용하면 어떤 파일이 실행될 때 내가 임포트를 했는지, 아니면 임포트를 당했는지를 알 수 있다.

__name__ 변수는 다음 값을 갖는다.

  • 프로그램이 시작된 파일에서는 __name__ 변수의 값이 __main__으로 설정된다.
  • 모듈로서 임포트된 파일에서는 __name__변수의 값이 해당 모듈명으로 설정된다.

따라서 모듈로서 호출된 경우를 구분하기 위해 주로 다음과 같은 형식의 코드를 주로 사용한다.

import ...
초기화 코드
함수  클래스 정의, 전역 변수 선언 
. . .
if __name__ == '__main__' :
    . . .

__name__변수에 저장된 값이 __main__인 경우 해당 파일에서 프로그램이 시작된 것이므로 특별한 기능을 수행하도록 설정할 수 있다.

여기까지 함수와, 함수를 모아 둔 파일인 모듈을 살펴보았다.

앞서 언급했든 모듈엔 수많은 종류가 있으므로, 추후 다른 내용을 학습하며 새로운 모듈이 나오면 그 때 해당 모듈에 포함된 함수들을 살펴보도록 하겠다.

다음 글에서는 게임 개발에 필요한 PyGame 모듈에 대해 다뤄볼 예정이다.

(다음 글에서 계속됩니다.)

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus