일급 함수(First-Class)와 고위 함수

박태정·2026년 5월 17일

Python Deep Dive

목록 보기
7/9

저번 챕터에서는 시퀀스 자료구조의 내부 동작 방식과 최적화까지 정리했었다.

오늘(5월 1일)부터는 챕터05인 일급 함수에 대해 공부했다. 솔직히 이 챕터가 이번 강의에서 가장 중요하다고 느꼈다. 클로저, 코루틴, 데코레이터를 배운다고 하는데 데코레이터 뺴고 다 처음 듣기도하고, 파이썬을 다 안다고 생각했는데 내가 모르는게 이렇게 많다는 걸 이번 공부를 하면서 처음 느꼈다.


일급 객체(First-Class Object)란?

파이썬에서 함수는 단순히 "실행되는 코드 덩어리"가 아니다. 함수 자체가 하나의 객체다. 이걸 일급 객체라고 한다.

소프트웨어 언어에서 일급 객체라고 말할 수 있으려면 아래 4가지 조건을 만족해야 한다.

  • 런타임 초기화: 프로그램 실행 중에 함수가 정의될 수 있음
  • 변수에 할당 가능: 함수 자체를 변수에 담을 수 있음
  • 인수로 전달 가능: 다른 함수의 파라미터로 전달할 수 있음
  • 반환값으로 반환 가능: 함수가 함수를 반환할 수 있음

파이썬 함수는 이 4가지를 전부 만족한다.

def factorial(n):
    """Factorial Function -> n : int"""
    if n == 1:
        return 1
    return n * factorial(n-1)

print(type(factorial))  # <class 'function'>

type()으로 찍어보면 <class 'function'>이 나온다. 함수인데 클래스 인스턴스라는 거다. 그리고 dir(factorial)로 내부를 들여다보면 __call__, __closure__, __globals__ 같은 매직 메서드들이 나온다. 함수도 객체 취급을 받는다는 증거라고 한다. (아직은 정확히 모르겠다)

from pprint import pprint

# 함수만 가진 고유한 속성 확인 (클래스에는 없는 것들)
pprint(set(sorted(dir(factorial))) - set(sorted(dir(A))))
# {'__builtins__', '__call__', '__closure__', '__globals__', ...}

클래스와 비교해서 함수만 가진 속성을 빼보면 __closure__, __call__ 같은 것들이 보인다. 이 두 가지는 나중에 클로저와 데코레이터에서 굉장히 중요하게 다시 등장한다고 한다.

아직 뭔 말인지, 왜 중요하다는 건지 모르곘다.


변수 할당과 고위 함수

일급 객체이기 때문에 함수를 변수에 그대로 담을 수 있다.

var_func = factorial  # 괄호 없이! -> 실행이 아니라 할당

print(var_func)       # <function factorial at 0x102bf1440>
print(var_func(5))    # 120

factorial()처럼 괄호를 붙이면 함수를 실행하는 거고, 괄호 없이 factorial만 쓰면 함수 자체를 참조하는 거다. 이 차이가 헷갈리면 이후 내용이 전부 꼬이니까 확실히 짚고 넘어가야 한다.

  • 진짜 중요한 부분이다. ()를 쓰고 안쓰고

함수를 변수에 담을 수 있으면, 당연히 다른 함수의 인자로도 넘길 수 있다. 이렇게 함수를 인수로 받거나 반환하는 함수고위 함수(Higher-order Function) 라고 한다. map, filter, reduce가 대표적인 예시다.

# map: 컨테이너의 각 요소에 함수를 하나씩 적용
print(list(map(var_func, range(1, 6))))
# [1, 2, 6, 24, 120]

# map + filter 조합: 홀수에만 factorial 적용
print(list(map(var_func, filter(lambda x: x % 2, range(1, 6)))))

# 리스트 컴프리헨션으로 동일하게 표현 가능
print([var_func(i) for i in range(1, 6) if i % 2])

mapfilter의 패턴은 (함수, 컨테이너) 형태다. 컨테이너(Sequence에서 배운거) 안의 요소들에 함수를 하나씩 적용하는 구조다. 리스트 컴프리헨션으로도 똑같이 표현할 수 있는데, 파이썬으로 코드를 짤 때는 가독성 측면에서 컴프리헨션을 더 권장한다고 한다.

reduce

reduce는 컨테이너의 요소들을 하나씩 누적 연산해서 단일 값으로 만든다.

from functools import reduce
from operator import add

print(reduce(add, range(1, 11)))  # 55
print(sum(range(1, 11)))          # 55

결과는 똑같이 55가 나온다. 다만 속도는 sum이 더 빠르다. reduce는 코드 가독성 측면에서 이점이 있는 경우에 사용하면 될 것 같다.

굳이? 안써도 될거 같고, 간혹 사용하는 경우에 어떤 느낌으로 썼는지 이해하는 정도로만 알자.

익명 함수 (Lambda)

lambda는 이름 없이 간단한 함수를 한 줄로 표현하는 방법이다.

print(reduce(lambda x, y: x + y, range(1, 11)))  # 55

다만 강의에서는 가급적 람다보다는 이름 있는 함수(def)로 리팩토링하는 걸 권장했다. 짧고 편리하지만 복잡해지면 가독성이 확 떨어지기 때문이다.


callable: 호출 가능 여부 확인

print(callable(str), callable(list), callable(var_func), callable(3.14))
# True True True False

callable()은 해당 객체를 함수처럼 ()로 호출할 수 있는지 확인한다. 3.143.14()처럼 호출할 수 없으니 False가 나온다.


partial: 인수 고정

partial은 오늘 배운 것 중에서 실용적으로 제일 많이 쓸 것 같다는 느낌이 들었다.

기존에 있는 함수의 특정 인수를 미리 고정해서 새로운 함수를 만드는 방식이다.

from operator import mul
from functools import partial

# mul(5, ?) 형태로 고정
five = partial(mul, 5)
print(five(10))  # 50

# mul(5, 6) 완전 고정
six = partial(five, 6)
print(six())  # 30

print([five(i) for i in range(1, 11)])
# [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]

five = partial(mul, 5)를 하면 five는 "5를 곱하는 함수"가 된다. 나머지 인자 하나만 받으면 바로 실행된다. 콜백 함수 패턴에서 특정 인자를 미리 고정해두고 나머지만 나중에 채우는 방식으로 자주 활용된다.


이번 챕터를 공부하면서 파이썬이 함수를 객체로 취급한다는 게 코드로 직접 확인했다. dir()로 내부를 뜯어보고, 변수에 담아보고, 인자로 넘겨보니까 확실히 와닿았다. 다음에는 이 일급 함수 개념을 기반으로 스코프 문제와 클로저로 넘어간다.

0개의 댓글