1. 함수 이름은 변수
파이썬에서 함수 이름은 단순한 식별자가 아닌 변수입니다. 이는 함수를 일급 객체(first-class object)로 취급하는 Python의 특성 때문입니다. 함수 이름이 변수라는 것은 함수를 다른 변수에 할당하거나, 함수를 인자로 전달하거나, 함수에서 반환할 수 있다는 것을 의미합니다.
def greet(name):
return f"Hello, {name}!"
# 함수를 변수에 할당
say_hello = greet
say = print
print(say_hello("Alice")) # 출력: Hello, Alice!
print(say("Alice")) # 출력: Alice
# 함수를 리스트에 저장
func_list = [greet, str.upper, str.lower]
print(func_list[0]("Bob")) # 출력: Hello, Bob!
print(func_list[1]("python")) # 출력: PYTHON
print(func_list[2]("PYTHON")) # 출력: python
# 함수를 딕셔너리의 값으로 사용
func_dict = {"greet": greet, "shout": str.upper}
print(func_dict["greet"]("Charlie")) # 출력: Hello, Charlie!
print(func_dict["shout"]("hello")) # 출력: HELLO
이러한 특성은 Python에서 고차 함수(higher-order functions)를 구현할 수 있게 해주며, 함수형 프로그래밍의 핵심 개념을 지원합니다.
2. 함수는 인스턴스
Python에서 모든 것은 객체입니다. 함수 역시 예외가 아닙니다. 함수는 function
클래스의 인스턴스입니다. 이는 함수가 객체지향 프로그래밍의 모든 특성을 가지고 있다는 것을 의미합니다.
def example_function():
pass
print(type(example_function)) # 출력: <class 'function'>
# 함수 객체의 속성 확인
print(dir(example_function))
# 함수에 새로운 속성 추가
example_function.custom_attribute = "커스텀"
print(example_function.custom_attribute) # 출력: 커스텀
# 함수의 __class__ 속성을 통해 function 클래스 확인
print(example_function.__class__) # 출력: <class 'function'>
# function 클래스의 메서드 확인
print(dir(function))
함수가 인스턴스라는 특성을 활용하면, 함수에 속성을 추가하거나 메서드를 호출할 수 있습니다. 이는 메타프로그래밍이나 데코레이터를 구현할 때 유용하게 사용될 수 있습니다.
3. def 키워드 의미
def
키워드는 파이썬에서 함수를 정의하는 데 사용되는 키워드입니다. 그러나 def
의 역할은 단순히 함수를 선언하는 것 이상입니다. def
는 실행 가능한 문장으로, 함수 객체를 생성하고 해당 객체에 이름을 바인딩합니다.
def
키워드가 실행되면 다음과 같은 과정이 일어납니다.
- 새로운 함수 객체가 생성됩니다.
- 함수의 코드 객체가 생성되어 함수 객체에 연결됩니다.
- 함수 객체가 현재 네임스페이스에 바인딩됩니다.
이를 더 자세히 살펴보겠습니다.
import dis
def example_function(x):
return x * 2
# 함수 객체 확인
print(example_function) # 출력: <function example_function at 0x...>
# 함수의 코드 객체 확인
print(example_function.__code__) # 출력: <code object example_function at 0x...>
# 현재 네임스페이스에 함수 객체 바인딩 확인
print(locals()["example_function"]) # 출력: <function example_function at 0x...>
# 함수의 바이트코드 확인
dis.dis(example_function)
여기서 example_function.__code__
는 함수의 코드 객체를 반환합니다. 코드 객체는 함수의 바이트코드를 포함하고 있으며, 함수의 실행에 필요한 정보를 담고 있습니다. 함수를 실행하면 코드 객체가 실행되어 함수의 동작을 수행합니다. dis.dis(example_function)
은 함수의 바이트코드를 사람이 이해하기 좋도록 출력합니다. 이를 통해 함수가 어떻게 동작하는지, 함수의 내부 동작을 살펴볼 수 있습니다.
4. 클래스를 통해 함수 정의
파이썬에서는 클래스를 사용하여 함수를 정의할 수 있습니다.
4.1 __call__
메서드를 이용한 호출 가능 객체
클래스에 __call__
메서드를 정의하면, 그 클래스의 인스턴스를 함수처럼 호출할 수 있습니다. 이를 "호출 가능 객체(callable object)"라고 합니다.
class Adder:
def __init__(self, n):
self.n = n
def __call__(self, x):
return self.n + x
add_5 = Adder(5)
print(add_5(10)) # 출력: 15
print(add_5(20)) # 출력: 25
# 호출 가능 여부 확인
print(callable(add_5)) # 출력: True
4.2 함수의 속성과 메서드
클래스를 통해 함수를 정의하면, 함수에 속성과 메서드를 추가할 수 있습니다. 이는 함수의 동작을 확장하거나 메타데이터를 저장하는 데 유용합니다.
class Function:
def __init__(self, func):
self.func = func
self.call_count = 0
def __call__(self, *args, **kwargs):
self.call_count += 1
return self.func(*args, **kwargs)
def reset_count(self):
self.call_count = 0
@Function
def add(a, b):
return a + b
print(add(3, 4)) # 출력: 7
print(add(5, 6)) # 출력: 11
print(add.call_count) # 출력: 2
add.reset_count()
print(add.call_count) # 출력: 0
이 예제에서 Function
클래스는 데코레이터로 사용되어 add
함수를 감싸고 있습니다. 이렇게 하면 add
함수는 호출 횟수를 추적하는 call_count
속성과 이를 초기화하는 reset_count
메서드를 갖게 됩니다.
4.3 클로저와 클래스의 비교
함수를 정의하는 데 클래스를 사용하는 것은 클로저를 사용하는 것과 유사한 점이 있습니다. 그러나 클래스를 사용하면 더 복잡한 상태 관리와 추가적인 메서드를 정의할 수 있어 더 강력합니다.
# 클로저를 사용한 방식
def make_adder(n):
def adder(x):
return x + n
return adder
# 클래스를 사용한 방식
class Adder:
def __init__(self, n):
self.n = n
def __call__(self, x):
return x + self.n
def change_n(self, new_n):
self.n = new_n
# 클로저 사용
add_5_closure = make_adder(5)
print(add_5_closure(10)) # 출력: 15
# 클래스 사용
add_5_class = Adder(5)
print(add_5_class(10)) # 출력: 15
add_5_class.change_n(7)
print(add_5_class(10)) # 출력: 17
이 예제에서 클로저와 클래스 모두 유사한 기능을 제공합니다. 그러나 클래스를 사용하면 change_n
메서드처럼 상태를 변경하는 추가적인 메서드를 쉽게 정의할 수 있습니다.
클래스를 통해 함수를 정의하는 방식은 복잡한 함수형 객체를 만들거나, 함수에 풍부한 기능을 추가해야 할 때 특히 유용합니다. 이는 Python의 객체 지향적 특성과 함수형 프로그래밍 패러다임을 결합하는 강력한 도구입니다.