AHRA CHO
by AHRA CHO
2 min read

Categories

Tags

프로그래밍을 처음 배우는 사람을 이해시키기 힘든 개념 중 하나가 바로 함수이다. 그도 그런 게 나 역시도 중학교 수학 시간에 함수라는 개념을 이해하지 못하고 넘어간 학생 중 하나였다. 기계적으로 문제만 맞게 풀었을 뿐 함수, Function이 어떤 의미인지 완벽하게 이해하지는 못했다.

함수는 말그대로 어떤 Function, 즉 기능을 수행하는 뭉치라고 이해할 수 있는데, 예를 들어, 수학에서 y = x라는 함수는 x가 어떤 값이 되었든 그 값을 y에게 돌려주는 기능을 한다. 프로그래밍에서의 함수도 마찬가지인데, 다만 수학에서 함수는 항상 Input이 있으면 1:1이든 다:1이든 Output을 내놓았다면, 프로그래밍에서의 함수는 Input과 Output이 있을 수도 있고, 없을 수도 있다. 그래서 프로그래밍에서의 함수는 Function을 가진 단위이다라고 이해하는 게 더 효과적이다.

보통 함수를 구성할 때는 세가지가 반드시 필요한데, 그것들은 1) 함수명 2) Parameter 3) Return 이다. 이 중 2번과 3번은 Null(없는)의 상태인 것도 포함한다. 다른 언어들에서보다 파이썬에서는 함수를 구성하기가 참 간단하다. C/C++, Java에서는 파라미터나 리턴값이 어떤 데이터형을 가지는지 일일이 명시해주어야 하는데 파이썬에는 변수에도 명시적인 데이터형을 주지 않기 때문에 함수에서도 마찬가지로 파라미터와 리턴값의 데이터형을 명시하지 않아도 문제가 되지 않는다.

위치 인자와 키워드 인자

위치인자는 함수를 정의할 때 파라미터 자리에 순서대로 이름을 매긴 것이다. 따라서 함수를 호출할 때는 정해진 위치인자 순서대로 의미에 맞는 매개변수를 전달해야 한다.

키워드인자는 각 매개변수에 맞는 이름을 붙인 것인데, 위치인자와 달리 키워드인자를 명시하여 매개변수를 전달하면 꼭 정의된 순서대로 전달하지 않아도 되는 이점이 있다.

단, 위치 인자와 키워드 인자를 섞어 사용한다면 위치인자가 먼저 나와야 한다.

def menu(wine, entree, dessert):
  return {'wine' : wine, "entree" : entree, "dessert" : dessert}

# 위치 인자를 사용한 함수 호출
menu('chardonnay', 'chicken', 'cake')

# 키워드 인자를 사용한 함수 호출
menu(wine='chardonnay', dessert='cake', entree='chicken')

args *과 kwargs **

파이썬에서 *은 포인터가 아니라 매개변수에서 __위치인자들을 튜플 하나로 묶어주는 역할__을 한다(포인터에 익숙한 나로서는 이 기능이 항상 헷갈린다).

**은 키워드인자들을 딕셔너리로 하나로 묶어준다.

마찬가지로, 위치인자를 나타낸느 args를 먼저 배치해야 한다.

함수도 객체다!

파이썬에서는 숫자, 문자열, 튜플, 리스트, 그리고 함수 할 것 없이 모두 객체이다. 이 말인즉슨, 우리가 숫자를 변수로 사용하고 함수의 인자로 사용하고, 리스트의 아이템으로 사용하는 것처럼 함수도 같은 방식으로 사용할 수 있다는 뜻이다.

def answer() :
  print("Function answer is called")
  
def run_something(func): # 함수명을 파라미터로
  func()
  
>>> run_something(answer)
# Function answer is called will be printed

그럼 아마 파이썬에서는 전역 변수와 같은 이름을 가진 함수를 사용하지 않는 게 좋겠다. 만약에 그렇다면 뒤에 정의된 것으로 덮어써질테니까 (from xxx import * 하지 않는 것과 비슷한 이유)

내부 함수

C/C++을 많이 쓴 나로서는 처음에 접했을 때 가장 놀라웠던 건 바로 이 내부 함수의 개념이었다. C에서 함수의 생명 주기는 return이 되면 끝나기 때문에 함수 안에 내부 함수가 있어도 리턴이 되면 생명이 끝난다고 생각했기 때문이다. 그런데 만약에 겉함수의 리턴값이 내부함수의 함수명인 경우 refcounter가 하나 있기 때문에 외부함수의 리턴값을 바탕으로 내부함수를 한번 더 호출할 수 있게 되는 것.

def outer() :
  def add(a, b):
    return a+b
  return inner
  
>>> func = outer()
>>> func(1, 2)
# 3이 출력

클로져(Closure)

몰랐는데 이런 동작을 하는 걸 클로져라고 부르나보다. 클로져는 다른 함수에 의해 동적으로 생성되고, 바깥 함수로부터 생성된 변수값을 변경하고, 저장할 수 있는 함수를 뜻한다.

def knights2(saying):
  def inner2():
    return "We are the knights who say : '%s'" % saying
    
>>> a = knights2("Duck") 
>>> b = knights2("Hasenpfeffer")

위 코드에서 a, b는 모두 inner2라는 내부 함수를 가리키겠지만, knights2 함수의 서로 다른 매개변수 값을 가지고 있을 것이다.

내부함수는 루프나 코드 중복을 피하기 위해 또 다른 함수 내에 어떤 복잡한 작업을 한 번 이상 수행할 때 유용하게 사용된다.

람다함수

람다함수는 간단하게 한줄로 표현되는 이름없는 함수이다.

stairs = ['thud', 'meow', 'hiss']
edit_story(stairs, lambda word : word.capitalize() + '!')

람다함수는 인자이름 : 수행할 작업의 형태로 간단하게 표현할 수 있다. 보통의 경우에는 함수를 명시적으로 작성하는 게 좋지만, 람다는 많은 작은 함수를 정의하고, 이들을 호출해서 얻은 모든 결과값을 저장해야하는 경우에 유용하다.