함수를 객체 처럼 다루는 법
Python에서 함수는 일급 객체(first-class objects)로 다룰 수 있다. 이는 함수를 변수에 할당하거나, 다른 함수의 인수로 전달하거나, 다른 함수에서 반환값으로 사용할 수 있다는 의미한다. 이러한 특성을 활용하면 프로그래밍이 훨씬 유연하고 강력해질 수 있다.
함수를 객체로 다루는 몇 가지 방법:
- 함수 할당하기: 함수를 다른 변수에 할당하여 그 변수를 통해 함수를 호출할 수 있다.
- 함수를 인수로 전달하기: 함수를 다른 함수의 매개변수로 전달할 수 있다. 이는 고차 함수(higher-order functions)를 만드는 데 사용된다.
- 함수에서 함수 반환하기: 함수에서 다른 함수를 반환할 수 있다. 이 방법은 클로저(closures) 또는 팩토리 함수(factory functions)를 생성할 때 유용하다.
# 함수를 변수에 할당
def greet(name):
return f"Hello, {name}!"
greet_someone = greet # 함수를 다른 변수에 할당
print(greet_someone("Alice")) # 'Hello, Alice!'
# 함수를 인수로 전달
def greet(name):
return f"Hello, {name}!"
def loud_greeting(func, name):
return func(name).upper()
print(loud_greeting(greet, "Bob")) # 'HELLO, BOB!'
# 함수에서 함수 반환
def make_greeter(greeting):
def greeter(name):
return f"{greeting}, {name}!"
return greeter
hello_greeter = make_greeter("Hello")
print(hello_greeter("Charlie")) # 'Hello, Charlie!'
map()써서 다루는 법
Python의 map() 함수는 함수를 시퀀스의 모든 항목에 적용하고, 결과를 반환하는 데 사용된다. 이를 통해 각 항목에 동일한 연산을 적용할 수 있으며, 코드를 간결하고 효율적으로 만들 수 있다. map() 함수는 첫 번째 인자로 함수를, 두 번째 인자로 함수를 적용할 반복 가능한(iterable) 데이터(예: 리스트, 튜플)를 받는다.
map() 사용법
- 함수 정의: 적용할 함수를 정의한다. 이 함수는 각 요소에 대해 실행된다.
- map() 호출: map()에 함수와 반복 가능한 데이터를 전달한다.
- 결과 처리: map()은 map 객체를 반환한다. 이를 리스트나 다른 데이터 구조로 변환하여 결과를 사용할 수 있다.
# 예제 1: 숫자 리스트의 각 요소를 제곱
numbers = [1, 2, 3, 4, 5]
squared = map(lambda x: x ** 2, numbers)
print(list(squared)) # 결과: [1, 4, 9, 16, 25]
# 예제 2: 문자열 리스트를 대문자로 변환
names = ["alice", "bob", "charlie"]
upper_names = map(str.upper, names)
print(list(upper_names)) # 결과: ['ALICE', 'BOB', 'CHARLIE']
# 예제 3: 각 튜플의 첫 번째 요소에 연산 적용
data = [(1, 'apple'), (2, 'banana'), (3, 'cherry')]
incremented_data = map(lambda x: (x[0] + 1, x[1]), data)
print(list(incremented_data)) # 결과: [(2, 'apple'), (3, 'banana'), (4, 'cherry')]
주의 사항
- map() 함수는 파이썬 3에서 지연 계산(lazy evaluation)을 수행하는 map 객체를 반환한다. 실제 결과를 보려면 list()나 tuple() 같은 함수를 사용하여 명시적으로 변환해야 한다.
- map()은 함수를 각 요소에 독립적으로 적용하기 때문에, 원소 간의 관계를 고려해야 하는 작업에는 적합하지 않을 수 있다.
map()은 특히 데이터 처리나 변환 작업에서 유용하게 사용될 수 있으며, 람다 함수와 함께 사용할 때 그 효과가 극대화된다.
고위 함수
고위 함수(Higher-Order Function, HOF)는 함수형 프로그래밍의 중요한 개념 중 하나로, 하나 이상의 함수를 인수로 받거나 함수를 결과로 반환하는 함수를 말한다. 이 개념은 프로그램의 유연성을 높이고 코드의 재사용성을 개선하는 데 도움이 된다.
고위 함수의 특징
- 함수를 인수로 받기: 고위 함수는 다른 함수를 매개변수로 받아, 이 함수를 내부 로직에서 호출할 수 있다. 이를 통해 동적으로 함수의 동작을 변경할 수 있다.
- 함수를 반환하기: 고위 함수는 실행 결과로 새로운 함수를 반환할 수 있다. 이 방법은 클로저를 생성하거나, 특정 상황에 맞게 동작을 커스터마이징할 수 있게 해준다.
고위 함수의 사용 예
- map(): 리스트나 다른 순차 데이터의 각 요소에 함수를 적용합니다.
- filter(): 조건에 맞는 요소만을 추출하여 새 리스트를 생성합니다.
- reduce(): 리스트의 요소를 함수를 사용하여 축소(reduce)하여 하나의 값으로 만듭니다.
Python에서의 고위 함수 예시
# 함수를 인수로 받는 고위 함수 예제
def apply_twice(func, value):
return func(func(value))
def add_five(x):
return x + 5
result = apply_twice(add_five, 10) # 20
# 함수를 반환하는 고위 함수 예제
def make_multiplier(x):
def multiplier(n):
return n * x
return multiplier
double = make_multiplier(2) # 2배를 하는 함수 생성
print(double(5)) # 결과: 10
고위 함수의 장점
- 모듈성: 복잡한 문제를 작은 함수 단위로 나누어 해결할 수 있다.
- 재사용성: 일반적인 동작을 수행하는 함수를 다양한 상황에서 재사용할 수 있다.
- 추상화: 고위 함수를 사용하면, 다양한 고수준 작업을 더 간단하게 표현할 수 있다.
고위 함수는 특히 데이터 처리, 이벤트 처리, 비동기 프로그래밍 등에서 유용하게 활용될 수 있으며, Python의 람다 함수와 결합하여 강력한 프로그래밍 패턴을 구현할 수 있다.
map(), filter(), reduce()
Python의 함수형 프로그래밍 함수들
map(), filter(), reduce()는 Python에서 함수형 프로그래밍 패러다임을 지원하기 위한 대표적인 고위 함수들이다. 이들은 모두 다양한 데이터 처리와 변환 작업에 매우 유용하게 사용된다.
1. map()
map() 함수는 주어진 함수를 시퀀스의 각 요소에 적용하고, 결과를 반복 가능한(map 객체) 형태로 반환한다. 이를 통해 각 요소에 동일한 연산을 간편하게 적용할 수 있다.
- 사용법: map(function, iterable)
numbers = [1, 2, 3, 4]
squared = map(lambda x: x ** 2, numbers)
print(list(squared)) # [1, 4, 9, 16]
2. filter()
filter() 함수는 주어진 함수의 조건을 만족하는 요소들만을 추출하여 반환한다. 이 함수는 조건에 부합하는 데이터만을 선택적으로 다루고 싶을 때 유용하다.
- 사용법: filter(function, iterable)
numbers = [1, 2, 3, 4, 5]
even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers)) # [2, 4]
3. reduce()
reduce() 함수는 시퀀스의 요소들을 함수를 이용하여 단일 값으로 축소한다. 이 함수는 functools 모듈에서 제공된다.
- 사용법: reduce(function, iterable[, initializer])
from functools import reduce
numbers = [1, 2, 3, 4]
total = reduce(lambda x, y: x + y, numbers)
print(total) # 10
대안적 접근 방법
map(), filter(), reduce() 외에도 Python에서는 리스트 컴프리헨션(list comprehension)과 제너레이터 표현식(generator expressions)을 사용하여 비슷한 결과를 보다 간결하고 Pythonic하게 달성할 수 있다.
리스트 컴프리헨션
리스트 컴프리헨션은 map()과 filter()의 기능을 하나의 간결한 구문으로 통합할 수 있다.
numbers = [1, 2, 3, 4]
# map 대체
squared = [x ** 2 for x in numbers]
# filter 대체
even_numbers = [x for x in numbers if x % 2 == 0]
제너레이터 표현식
제너레이터 표현식은 리스트 컴프리헨션과 유사하지만, 값을 메모리에 한번에 로딩하지 않고 필요할 때마다 하나씩 생성한다. 이는 큰 데이터를 다룰 때 메모리 효율성을 높인다.
numbers = range(1, 1000) # 큰 범위
# map 대체
squared = (x ** 2 for x in numbers)
# filter 대체
even_numbers = (x for x in numbers if x % 2 == 0)
이러한 대안적 접근 방법들은 Python에서 좀 더 일반적으로 권장되는 스타일이며, 특히 리스트 컴프리헨션은 가독성과 성능 모두에서 좋은 평가를 받는다.
리덕선(reduction)
리덕션(Reduction)이라는 용어는 프로그래밍과 컴퓨터 과학에서 일련의 값들을 하나의 값으로 결합하는 과정을 의미한다. 이러한 과정은 특정 연산을 사용하여 여러 개의 입력을 단일 출력으로 "축소"하는 것이다. 리덕션은 다양한 문맥에서 사용되지만, 일반적으로는 함수형 프로그래밍 또는 병렬 컴퓨팅에서 많이 사용된다.
리덕션의 예
- 수학적 합계: 숫자 리스트의 총합을 계산하는 것은 리덕션의 한 예이다.
- 논리 연산: 여러 불리언 값에 대한 AND 또는 OR 연산을 수행하여 하나의 불리언 값으로 결합하는 것도 리덕션이다.
- 문자열 연결: 여러 문자열을 하나로 결합하는 것도 리덕션 과정이다.
Python에서의 리덕션 예제
Python에서는 functools 모듈 내의 reduce() 함수를 통해 리덕션을 구현할 수 있다. 이 함수는 두 개의 인자를 취하는 함수와 반복 가능한 데이터를 인자로 받아, 데이터를 순차적으로 축소하여 하나의 값으로 만든다.
from functools import reduce
numbers = [1, 2, 3, 4, 5]
total_sum = reduce(lambda x, y: x + y, numbers)
print(total_sum) # 15
위 예제에서 reduce() 함수는 리스트 내의 모든 숫자를 더하여 총합을 계산한다.
리덕션의 중요성
- 함수형 프로그래밍: 리덕션은 함수형 프로그래밍에서 중요한 개념 중 하나로, 함수를 사용해 데이터를 변형하고 축소하는 데 중점을 둔다.
- 병렬 처리와 분산 시스템: 대규모 데이터 처리 작업에서 병렬로 실행된 결과를 하나의 결과로 합치기 위해 리덕션을 사용한다. 예를 들어, 맵리듀스(MapReduce) 프로그래밍 모델은 이러한 리덕션을 이용하여 대용량 데이터 처리 작업을 효과적으로 처리할 수 있도록 돕는다.
리덕션은 복잡한 데이터 집합을 단순화하고, 요약 정보를 생성하는 데 매우 유용하며, 다양한 프로그래밍 패러다임과 응용 프로그램에서 핵심적인 역할을 한다.
리덕션 함수
리덕션 함수는 데이터의 집합을 한 개의 요약된 결과로 축소하는데 사용되는 함수이다. Python에서는 여러 가지 내장 함수들이 리덕션의 개념을 제공하고 있으며, 가장 대표적인 것은 reduce() 함수이다. 또한, 일부 일반적인 리덕션 작업을 위한 다른 내장 함수들도 제공된다.
Python의 대표적인 리덕션 함수들
1. reduce()
- reduce() 함수는 functools 모듈에 포함되어 있으며, 두 개의 인자를 받는 함수와 리스트 또는 다른 반복 가능한 객체를 인자로 받아, 리스트를 단일 값으로 축소한다.
- 사용법: reduce(function, iterable[, initializer])
from functools import reduce
def add(x, y):
return x + y
numbers = [1, 2, 3, 4, 5]
result = reduce(add, numbers) # 15
2. sum()
- 리스트나 튜플 등의 반복 가능한 숫자 데이터의 전체 합을 반환한다.
- 사용법: sum(iterable[, start])
numbers = [1, 2, 3, 4, 5]
total = sum(numbers) # 15
3. min()와 max()
- min()과 max() 함수는 반복 가능한 데이터 중 최소값과 최대값을 반환한다.
- 사용법: min(iterable, *[, key, default]) 및 max(iterable, *[, key, default])
numbers = [5, 3, 9, 1, 10]
minimum = min(numbers) # 1
maximum = max(numbers) # 10
4. all()와 any()
- all() 함수는 주어진 반복 가능한 데이터의 모든 요소가 참(True)일 때만 True를 반환한다.
- any() 함수는 데이터 중 하나라도 참(True)이면 True를 반환한다.
- 사용법: all(iterable) 및 any(iterable)
conditions = [True, False, True]
all_true = all(conditions) # False
any_true = any(conditions) # True
리덕션 함수 사용 시 고려 사항
- 효율성: 큰 데이터를 처리할 때는 리덕션 함수의 선택과 사용 방법이 성능에 큰 영향을 미칠 수 있다. 예를 들어, sum()은 내부적으로 최적화가 잘 되어 있어서 많은 숫자의 합을 계산할 때 reduce()보다 더 효율적일 수 있다.
- 가독성: 간단한 리덕션 작업은 가능하면 Python의 내장 함수를 사용하는 것이 가독성과 유지 보수 측면에서 좋다. 복잡한 리덕션 로직이 필요한 경우에는 reduce()를 사용할 수 있다.
리덕션 함수는 데이터를 분석하거나, 결과를 요약하는 등 다양한 프로그래밍 상황에서 유용하게 활용된다. 각 함수의 특성과 사용법을 이해하고 적절히 사용하는 것이 중요하다.
익명 함수
익명 함수, 또는 람다 함수(lambda function)는 이름이 없는 한 줄짜리 함수를 의미한다. Python에서 람다 함수는 lambda 키워드를 사용하여 생성하며, 간단한 연산이나 작은 함수가 필요할 때 유용하게 사용된다. 람다 함수는 일반적으로 map(), filter(), reduce()와 같은 함수와 함께 사용되어 코드를 더 간결하게 만들 수 있다.
람다 함수의 기본 구조
lambda arguments: expression
- arguments: 함수의 입력값, 콤마(,)로 구분된 매개변수 목록을 받는다.
- expression: 매개변수를 사용하는 간단한 표현식이며, 이 표현식의 결과값이 함수의 반환값이 된다.
람다 함수 사용 예시
# 간단한 연산
# 두 수를 더하는 람다 함수
add = lambda x, y: x + y
print(add(5, 3)) # 8
# 리스트 정렬
# 이름을 성으로 정렬하기
names = ['John Doe', 'Jane Smith', 'Alice Johnson']
sorted_names = sorted(names, key=lambda name: name.split()[-1])
print(sorted_names) # ['John Doe', 'Alice Johnson', 'Jane Smith']
# map() 함수 사용
# 각 숫자의 제곱 계산
numbers = [1, 2, 3, 4, 5]
squared = map(lambda x: x ** 2, numbers)
print(list(squared)) # [1, 4, 9, 16, 25]
# filter() 함수 사용
# 홀수만 필터링
numbers = [1, 2, 3, 4, 5]
odds = filter(lambda x: x % 2 != 0, numbers)
print(list(odds)) # [1, 3, 5]
# reduce() 함수 사용
from functools import reduce
numbers = [1, 2, 3, 4]
product = reduce(lambda x, y: x * y, numbers)
print(product) # 24
람다 함수의 장단점
장점:
- 코드를 간결하게 만들어 주어서 작은 함수를 한 줄로 표현할 수 있다.
- 함수를 정의할 필요 없이 바로 사용할 수 있어 편리하다.
단점:
- 복잡하거나 긴 로직에는 적합하지 않는다. 람다는 표현식이 한 줄로 제한되기 때문이다.
- 가독성 문제가 생길 수 있으며, 특히 여러 람다가 중첩되는 경우 코드를 이해하기 어려워질 수 있다.
람다 함수는 간단한 기능을 구현할 때 유용하게 사용할 수 있으며, 특히 Python의 여러 내장 고위 함수와 함께 사용될 때 그 진가를 발휘한다.
callable() 객체
Python에서 callable() 함수는 객체가 "호출 가능(callable)"인지 즉, 함수처럼 호출될 수 있는지 확인하는 데 사용된다. 호출 가능한 객체는 함수 뿐만 아니라 클래스나 특정 클래스 인스턴스(클래스 내에 __call__() 메소드가 구현되어 있는 경우) 등도 포함된다.
callable()의 사용법
callable() 함수는 단일 매개변수를 받으며, 이 매개변수가 호출 가능한지 여부에 따라 True 또는 False를 반환한다.
def function():
return "Hello World!"
class MyClass:
def __call__(self):
return "Instance is callable!"
# 함수 테스트
print(callable(function)) # True
# 람다 테스트
lambda_func = lambda x: x * 2
print(callable(lambda_func)) # True
# 클래스 테스트
print(callable(MyClass)) # True
# 인스턴스 테스트
my_instance = MyClass()
print(callable(my_instance)) # True (인스턴스가 __call__ 메서드를 가지고 있기 때문)
# 일반 객체 테스트
class NotCallable:
pass
not_callable_instance = NotCallable()
print(callable(not_callable_instance)) # False
callable()의 사용 시나리오
- 동적 기능 확인: 프로그램에서 객체의 유형을 사전에 알 수 없을 때, 해당 객체가 호출 가능한지 여부를 검사하여 조건적으로 호출할 수 있다.
- 플러그인 시스템 또는 콜백: 사용자 정의 콜백이나 플러그인이 올바르게 구현되어 호출 가능한지 검사할 수 있다.
- 고급 API 디자인: API를 설계할 때, 다양한 종류의 객체를 입력으로 받고 이들이 호출 가능한지 확인하여 다른 방식으로 처리할 수 있다.
callable()의 중요성
callable()은 Python에서 다형성을 활용하거나, 함수나 메소드가 아닌 다른 객체에 대한 호출 가능 여부를 확인할 때 유용하다. 이는 특히 함수형 프로그래밍이나 객체 지향 프로그래밍에서 동적으로 기능을 확장하거나 변경할 필요가 있을 때 매우 유용하게 사용된다.
사용자 정의 콜러블형
Python에서 사용자 정의 callable 객체를 만드는 방법 중 하나는 클래스 내에 __call__() 매직 메서드를 구현하는 것이다. 이 방법을 사용하면 객체의 인스턴스를 함수처럼 호출할 수 있게 되어, 객체에 함수적 특성을 부여할 수 있다.
__call__() 메서드
__call__() 메서드는 객체를 함수처럼 호출할 때 실행되는 메서드이다. 이 메서드를 정의함으로써, 해당 클래스의 인스턴스는 호출 가능한 객체가 된다.
사용자 정의 callable 객체 만들기
class Adder:
def __init__(self, value):
self.value = value
def __call__(self, x):
return self.value + x
# Adder 인스턴스 생성
adder = Adder(5)
# 인스턴스를 함수처럼 사용
print(adder(10)) # 15
위 예제에서 Adder 클래스는 __call__() 메서드를 통해 callable 하게 구성되어 있다. Adder(5)로 인스턴스를 생성한 후, adder(10)과 같이 함수처럼 호출할 수 있으며, 이 때 __call__() 메서드가 실행된다.
사용자 정의 callable 객체의 활용
사용자 정의 callable 객체는 다양한 상황에서 유용하게 사용될 수 있다:
- 상태 보존: callable 객체는 내부 상태를 가질 수 있다. 위의 Adder 예제처럼, 객체는 생성 시 받은 초기 값(value)을 내부 상태로 저장하고 이를 사용하여 연산을 수행할 수 있다.
- 객체 지향 콜백: 이벤트 처리나 콜백을 객체 지향적으로 처리할 때 유용하다. 객체의 메서드 대신에 callable 객체를 전달하여, 내부 상태에 따라 다르게 반응하도록 할 수 있다.
- 함수 대체: 특정 함수를 객체로 대체하여 보다 복잡한 동작을 구현할 수 있다. 이 방법은 코드의 가독성과 유지 관리를 향상시킬 수 있다.
__call__() 메서드를 구현함으로써, Python의 클래스를 이용해 매우 유연하고 강력한 callable 객체를 만들 수 있다. 이러한 객체들은 상태를 유지하는 함수로서 동작하거나, 복잡한 조건에 따라 다르게 반응하는 콜백으로 사용될 수 있으며, Python의 객체 지향적인 특성을 활용하여 설계의 폭을 넓힐 수 있다.
함수 인트로스펙션 (Function Introspection)
함수 인트로스펙션(Function Introspection)은 프로그램이 실행 중인 자신의 내부 구조, 즉 객체들에 대한 정보를 검사하고 그 타입이나 속성 등을 파악하는 과정을 의미한다. Python에서는 다양한 내장 함수와 모듈을 사용하여 함수의 세부 사항을 조사할 수 있으며, 이를 통해 런타임에 함수에 대한 많은 정보를 얻을 수 있다.
Python에서 함수 인트로스펙션 사용하기
Python에서는 다음과 같은 방법과 도구를 사용하여 함수의 메타데이터를 조회할 수 있다.
1. dir()
- 함수의 모든 속성과 메서드 목록을 알아낼 수 있다.
def sample_function(param1, param2):
return param1 + param2
print(dir(sample_function))
2. type()
- 객체의 타입을 확인한다.
print(type(sample_function))
3. id()
- 객체의 "identity"를 반환하여, 메모리 상의 위치를 파악할 수 있다.
print(id(sample_function))
4. inspect 모듈
- Python의 inspect 모듈은 함수의 코드, 인자, 주석, 소스 코드 등을 포함한 많은 정보를 제공한다.
- inspect.getsource(): 함수의 소스 코드를 문자열로 가져온다.
- inspect.getdoc(): 함수의 독스트링(documentation string)을 가져온다.
- inspect.signature(): 함수의 시그니처를 조회하여 매개변수 정보를 얻는다.
import inspect
def sample_function(param1, param2="default"):
"""This is a sample function to demonstrate introspection."""
return param1 + param2
print("Documentation:", inspect.getdoc(sample_function))
print("Source Code:\\n", inspect.getsource(sample_function))
print("Signature:", inspect.signature(sample_function))
5. __doc__
- 함수의 독스트링을 직접 조회한다.
print(sample_function.__doc__)
6. __name__ 및 __module__
- __name__: 함수의 이름을 문자열로 반환한다.
- __module__: 함수가 정의된 모듈의 이름을 반환한다.
print(sample_function.__name__)
print(sample_function.__module__)
함수 인트로스펙션의 활용
함수 인트로스펙션은 다음과 같은 경우에 유용하게 사용될 수 있다:
- 디버깅: 함수의 동작을 이해하고 문제를 해결할 때 필요한 상세 정보를 제공한다.
- 자동화된 문서 생성: 소프트웨어의 문서를 자동으로 생성하기 위해 함수의 독스트링과 메타데이터를 추출한다.
- API 검사: 외부 라이브러리나 프레임워크의 API를 프로그래밍적으로 조사하여 사용 방법을 파악한다.
- 동적 기능 추가: 런타임에 함수의 특성을 파악하고, 조건에 따라 다른 함수를 호출하거나 기능을 추가한다.
Python의 강력한 인트로스펙션 기능은 프로그래밍의 투명성과 유연성을 높이는 데 크게 기여한다.
- total code example -
import inspect
# 함수 정의
def sample_function(param1, param2="default"):
"""This is a sample function to demonstrate introspection."""
return param1 + param2
# dir()을 사용하여 속성 및 메서드 목록 조회
print("Attributes and Methods:", dir(sample_function))
# Attributes and Methods: ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
# type()과 id()를 사용하여 함수의 타입과 메모리 주소 출력
print("Type of the function:", type(sample_function))
# Type of the function: <class 'function'>
print("Memory address of the function:", id(sample_function))
# Memory address of the function: 140456780318400 # 메모리 주소는 실행할 때마다 달라질 수 있음.
# inspect 모듈을 사용하여 독스트링, 소스 코드, 시그니처 조회
print("Documentation:", inspect.getdoc(sample_function))
# Documentation: This is a sample function to demonstrate introspection.
print("Source Code:\\n", inspect.getsource(sample_function))
# Source Code:
# def sample_function(param1, param2="default"):
# """This is a sample function to demonstrate introspection."""
# return param1 + param2
print("Signature:", inspect.signature(sample_function))
# Signature: (param1, param2='default')
# __doc__, __name__, __module__ 속성 조회
print("Docstring:", sample_function.__doc__)
# Docstring: This is a sample function to demonstrate introspection.
print("Function Name:", sample_function.__name__)
# Function Name: sample_function
print("Module Name:", sample_function.__module__)
# Module Name: __main__
위치 매개변수 & 키워드 전용 매개변수
Python 함수에서 매개변수를 정의하는 방법은 크게 위치 매개변수(position arguments), 키워드 매개변수(keyword arguments), 그리고 그 둘의 조합 등을 포함한다. Python 3에서는 위치 매개변수와 키워드 전용 매개변수(keyword-only arguments)를 더 명확하게 구분할 수 있는 문법을 제공하고 있다.
1. 위치 매개변수 (Positional Arguments)
위치 매개변수는 가장 일반적인 매개변수 타입으로, 함수를 호출할 때 전달되는 인수가 함수 정의에서 지정된 순서대로 할당된다.
def print_values(a, b, c):
print(f"a = {a}, b = {b}, c = {c}")
print_values(1, 2, 3)
# 출력: a = 1, b = 2, c = 3
2. 키워드 매개변수 (Keyword Arguments)
키워드 매개변수는 인수를 전달할 때 매개변수의 이름을 명시적으로 지정하여 값을 할당한다. 이 방식은 인수의 순서를 자유롭게 하고, 코드의 가독성을 높인다.
def print_values(a, b, c):
print(f"a = {a}, b = {b}, c = {c}")
print_values(c=3, a=1, b=2)
# 출력: a = 1, b = 2, c = 3
3. 기본값이 있는 매개변수 (Default Arguments)
함수 매개변수에 기본값을 제공할 수 있다. 호출 시 해당 매개변수에 대한 인수가 제공되지 않으면 기본값이 사용된다.
def print_values(a, b, c=3):
print(f"a = {a}, b = {b}, c = {c}")
print_values(1, 2)
# 출력: a = 1, b = 2, c = 3
4. 가변 매개변수 (Variable-length Arguments)
- args: 위치에 따라 여러 개의 인수를 튜플로 받는다.
- *kwargs: 키워드에 따라 여러 개의 인수를 사전으로 받는다.
def print_values(*args, **kwargs):
print(args) # 튜플
print(kwargs) # 딕셔너리
print_values(1, 2, 3, d=4, e=5)
# 출력: (1, 2, 3)
# 출력: {'d': 4, 'e': 5}
5. 키워드 전용 매개변수 (Keyword-only Arguments)
키워드 전용 매개변수는 반드시 키워드 인자로만 전달될 수 있다. 이들은 * 또는 *args 다음에 정의된다.
def print_values(a, *, b, c):
print(f"a = {a}, b = {b}, c = {c}")
print_values(1, b=2, c=3)
# 출력: a = 1, b = 2, c = 3
키워드 전용 매개변수는 함수의 인터페이스를 더욱 명확하게 하며, 나중에 함수에 매개변수를 추가할 때 호환성 문제를 최소화하는 데 도움을 준다. 이러한 다양한 매개변수 정의 방법은 Python 함수의 유연성을 크게 향상시키며, 다양한 상황에 맞추어 함수의 동작을 정의하는 데 매우 유용하다.
매개 변수에 대한 정보 읽기
Python에서 함수의 매개변수에 대한 정보를 읽는 것은 함수 인트로스펙션의 한 부분이다. 이를 통해 함수의 시그니처, 즉 매개변수의 이름, 기본값, 타입 힌트 등을 조사할 수 있다. 이를 위해 Python의 inspect 모듈을 사용할 수 있다. inspect 모듈은 프로그램 실행 중에 객체에 대한 정보를 추출하는 데 사용된다.
inspect.signature() 함수
inspect.signature() 함수는 함수, 메서드, 클래스, 또는 콜러블 객체의 시그니처를 반환한다. 이 시그니처에는 매개변수의 이름, 기본값, 주석 등이 포함될 수 있다.
기본 사용법
import inspect
def sample_function(a, b=2, *args, c=3, **kwargs):
pass
sig = inspect.signature(sample_function)
print(sig)
for name, param in sig.parameters.items():
print(f"Name: {name}")
print(f" Kind: {param.kind}")
print(f" Default: {param.default}")
print(f" Annotation: {param.annotation}")
매개변수 종류
inspect.Parameter 객체는 매개변수의 다양한 속성을 제공한다:
- name: 매개변수의 이름이다.
- default: 매개변수의 기본값이다. 기본값이 없을 경우 inspect.Parameter.empty를 반환한다.
- kind: 매개변수의 종류를 나타냅니다. 여기에는 다음이 포함된다:
- POSITIONAL_ONLY: 위치 전용 매개변수이다.
- POSITIONAL_OR_KEYWORD: 위치 또는 키워드 매개변수이다.
- VAR_POSITIONAL: 위치 가변 매개변수(args 같은)이다.
- KEYWORD_ONLY: 키워드 전용 매개변수이다.
- VAR_KEYWORD: 키워드 가변 매개변수(*kwargs 같은)이다.
import inspect
def foo(a, b: int = 42, *args, c: float = 3.14, d, **kwargs):
pass
sig = inspect.signature(foo)
print("Function signature:", sig)
# Function signature: (a, b: int = 42, *args, c: float = 3.14, d, **kwargs)
for name, param in sig.parameters.items():
print(f"Parameter Name: {name}")
# Parameter Name: a
# Parameter Name: b
print(f" Type: {param.annotation}")
# Type: <class 'inspect._empty'>
# Type: <class 'int'>
print(f" Kind: {param.kind}")
# Kind: POSITIONAL_OR_KEYWORD
# Kind: POSITIONAL_OR_KEYWORD
print(f" Default: {param.default if param.default is not inspect.Parameter.empty else 'No default'}")
# Default: No default
# Default: 42
이러한 정보를 통해 함수를 호출하는 방법, 필요한 매개변수, 옵션 매개변수 등을 파악할 수 있다. 함수의 인터페이스를 정확하게 이해하는 데 중요한 역할을 한다.
inspect.signature().bind()
inspect.signature().bind() 메소드는 함수 시그니처에 대해 인수를 바인딩하는 데 사용되며, 이를 통해 주어진 인수가 해당 함수의 매개변수 정의에 맞는지 검사할 수 있다. 이 방법은 주로 함수 호출을 시뮬레이션하거나 인수가 올바른지 확인할 때 유용하다.
inspect.signature().bind()의 사용법
inspect.signature().bind()는 함수 시그니처 객체에서 제공되며, 인수를 시그니처의 매개변수에 매핑한다. 이 메소드는 다음을 수행한다:
- 인수 검증: 제공된 인수가 함수 매개변수에 적합한지 확인한다. 인수의 수나 타입이 맞지 않으면 오류를 발생시킨다.
- 인수 매핑: 위치적 또는 키워드 인수를 시그니처의 매개변수에 매핑한다.
- 결과 반환: 바인딩된 인수를 포함하는 BoundArguments 객체를 반환한다.
import inspect
def sample_function(a, b, c=3, *args, d, e=5, **kwargs):
pass
# 함수의 시그니처 가져오기
signature = inspect.signature(sample_function)
# 시그니처에 인수 바인딩 시도
bound_args = signature.bind(1, 2, 3, d=4)
# 바인딩 결과 출력
print("Arguments:") # Arguments:
for name, value in bound_args.arguments.items():
print(f"{name}: {value}")
# a: 1
# b: 2
# c: 3
# d: 4
# 바인딩된 인수로 함수 호출 가능성 확인
sample_function(*bound_args.args, **bound_args.kwargs)
bind()의 활용
- 유효성 검사: 함수를 호출하기 전에 인수가 올바른지 검사하여 런타임 에러를 예방할 수 있다.
- API 테스트: 자동화된 테스트에서 함수 호출을 시뮬레이션하고 예상된 바인딩이 정확히 일어나는지 확인할 수 있다.
- 동적 함수 호출: 다양한 인수 세트를 동적으로 생성하고 이를 함수에 바인딩하여 호출하는 고급 기술에 사용할 수 있다.
inspect.signature().bind()는 함수 인트로스펙션을 통해 더 안정적이고 오류가 적은 코드를 작성하는 데 도움을 줄 수 있는 강력한 도구이다.
함수 애너테이션
Python에서 함수 애너테이션은 함수의 매개변수와 반환 값에 대한 메타데이터를 제공하는 방법이다. 함수 애너테이션은 주로 타입 힌트를 제공하는 데 사용되며, 이를 통해 개발자는 코드의 의도를 명확하게 표현하고, IDE나 린터(linter)가 코드의 정확성을 검사하는 데 도움을 받을 수 있다. 애너테이션은 Python 3.0에서 도입되었고, 3.5 버전부터 타입 힌팅(type hinting)에 활용되기 시작했다.
함수 애너테이션의 기본 구문
함수 애너테이션은 매개변수 뒤에 콜론(:)과 함께 타입을 지정하고, 필요한 경우 기본값을 할당할 수 있다. 함수의 반환 타입은 화살표(->) 뒤에 지정한다.
def add(x: int, y: int) -> int:
return x + y
함수 애너테이션의 접근
함수 객체의 __annotations__ 속성을 통해 애너테이션을 조회할 수 있다. 이 속성은 매개변수 이름을 키로 하고, 애너테이션을 값으로 하는 딕셔너리이다.
def greet(name: str) -> str:
return f"Hello, {name}!"
print(greet.__annotations__)
# {'name': <class 'str'>, 'return': <class 'str'>}
함수 애너테이션의 활용
함수 애너테이션은 주로 다음과 같은 목적으로 사용된다:
- 타입 체킹: 도구와 라이브러리가 코드에 대한 정적 분석을 수행할 때 사용된다. 예를 들어, mypy와 같은 타입 체커는 애너테이션을 기반으로 타입 오류를 식별할 수 있다.
- 문서화: 애너테이션은 함수의 인터페이스에 대한 문서화를 자동화하는 데 사용할 수 있다. API 문서에서 매개변수의 타입 정보를 자동으로 추출할 수 있다.
- IDE 지원: 개발 환경에서 함수에 전달되어야 하는 인수의 타입을 사용자에게 알려줌으로써, 보다 정확한 자동 완성과 코드 분석을 제공한다.
주의사항
함수 애너테이션은 Python의 실행 시 타입 강제를 하지 않는다. 즉, 애너테이션이 타입 미스매치를 방지하거나 런타임에서 타입을 체크하지 않는다. 애너테이션은 단지 메타데이터를 제공하며, 타입 체크는 개발자나 별도의 도구가 수행해야 한다.
Python에서 함수 애너테이션을 사용함으로써 코드의 명확성과 유지 보수성을 향상시킬 수 있으며, 특히 복잡하거나 큰 코드베이스에서 그 이점이 더욱 두드러진다.
operator 모듈
Python의 operator 모듈은 Python에서 기본적으로 제공되는 함수형 프로그래밍을 지원하며, 표준 연산자에 해당하는 함수를 제공한다. 이 모듈은 일반적인 수학, 비교, 논리 연산자를 함수로 구현하여, 다른 함수의 매개변수로 전달하거나 고차 함수에서 사용할 수 있도록 해준다. 이를 통해 코드를 더 간결하고 읽기 쉽게 만들 수 있다.
주요 기능
1. 산술 연산자
operator 모듈은 일반적인 산술 연산자를 함수로 제공한다. 예를 들어, operator.add(x, y)는 x + y와 같고, operator.mul(x, y)는 x * y와 같다.
import operator
result_add = operator.add(10, 5) # 15
result_mul = operator.mul(10, 5) # 50
2. 비교 연산자
비교 연산도 함수로 제공된다. 예를 들어, **operator.lt(x, y)**는 x < y와 같다.
result_lt = operator.lt(10, 20) # True
result_eq = operator.eq(10, 10) # True
3. 논리 연산자
논리 연산자도 포함되어 있다. operator.and_()는 두 인자의 논리적 AND 연산을 수행한다.
result_and = operator.and_(True, False) # False
4. 시퀀스 연산자
시퀀스를 다루는 함수도 있다. 예를 들어, operator.getitem(obj, index)는 obj[index]와 같다.
lst = [1, 2, 3, 4]
result_item = operator.getitem(lst, 2) # 3
5. 속성과 아이템 설정 및 삭제
속성 접근 및 수정에 관련된 함수도 제공된다. operator.attrgetter('name')은 객체에서 name 속성을 가져오는 함수를 생성한다.
import operator
class Person:
def __init__(self, name):
self.name = name
person = Person("John")
name = operator.attrgetter('name')(person) # 'John'
operator 모듈의 장점
- 코드 간결성: 람다 함수나 복잡한 함수 정의 없이 기본 연산자를 함수로 사용할 수 있어 코드가 간결해진다.
- 함수 인터페이스 통일: 연산자를 함수로 사용함으로써 다른 함수형 프로그래밍 도구와의 일관성을 유지할 수 있다.
- 성능 최적화: operator 모듈의 함수들은 내부적으로 C로 구현되어 있어 파이썬 내장 연산자와 동일한 성능을 제공한다.
operator 모듈은 특히 고차 함수와 함께 사용될 때 그 효용성이 크게 증가하며, 함수형 프로그래밍 패러다임에서 유용하게 사용될 수 있다.
itemgetter()
operator 모듈의 itemgetter() 함수는 특정 인덱스 또는 키에 대한 아이템을 추출하는 호출 가능한 객체를 생성한다. 이 함수는 특히 시퀀스, 딕셔너리, 또는 어떤 종류의 객체에서도 속성이나 아이템을 가져올 때 유용하게 사용된다. itemgetter()는 주로 정렬이나 그룹화와 같은 작업에서 다중 키를 기준으로 아이템을 추출할 때 사용된다.
기본 사용법
itemgetter()는 인자로 하나 이상의 인덱스나 키를 받는다. 반환된 객체는 호출 가능하며, 이 객체에 컨테이너 타입(리스트, 튜플, 딕셔너리 등)을 전달하면 해당 인덱스나 키의 값을 추출한다.
# **리스트에서 아이템 가져오기**
from operator import itemgetter
data = [5, 1, 2, 9, 8]
get_third_item = itemgetter(2)
print(get_third_item(data)) # 출력: 2
# **딕셔너리에서 여러 키의 값 가져오기**
record = {'name': 'John', 'age': 45, 'job': 'Engineer'}
get_name_and_job = itemgetter('name', 'job')
print(get_name_and_job(record)) # 출력: ('John', 'Engineer')
# **고급 사용법: 여러 인덱스 접근
# itemgetter()**에 여러 인덱스를 넘겨주면, 해당 인덱스들의 아이템을 튜플 형태로 반환함.
****data = [5, 1, 2, 9, 8]
get_multiple_items = itemgetter(0, 3, 4)
print(get_multiple_items(data)) # 출력: (5, 9, 8)
# **객체 리스트를 특정 속성으로 정렬하기**
records = [
{'name': 'John', 'score': 90},
{'name': 'Jane', 'score': 88},
{'name': 'Dave', 'score': 92}
]
# 점수(score)를 기준으로 정렬
sorted_records = sorted(records, key=itemgetter('score'))
print(sorted_records) # 점수가 낮은 순으로 정렬된 리스트 출력
itemgetter()를 사용하면 코드의 가독성을 높일 수 있으며, 함수적 접근 방식으로 처리할 수 있는 간단하고 효율적인 방법을 제공합니다. 이를 통해 보다 세련된 데이터 처리 코드를 작성할 수 있습니다.
attrgetter()
operator 모듈의 attrgetter() 함수는 객체의 하나 이상의 속성을 가져오는 호출 가능한 객체를 생성하는 함수이다. 이 함수는 주로 객체가 가진 속성을 기준으로 정렬, 필터링, 최대값 및 최소값 찾기와 같은 작업을 할 때 유용하게 사용된다.
기본 사용법
attrgetter()는 인자로 하나 이상의 속성 이름을 받는다. 반환된 객체는 호출 가능하며, 이 객체에 다른 객체를 전달하면 지정된 속성의 값을 추출한다.
# **객체의 속성 가져오기**
from operator import attrgetter
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("John", 30)
get_name = attrgetter('name')
print(get_name(person)) # 출력: John
# **고급 사용법: 여러 속성 접근**
person = Person("Alice", 25)
get_name_and_age = attrgetter('name', 'age')
print(get_name_and_age(person)) # 출력: ('Alice', 25)
# **객체 리스트를 특정 속성으로 정렬하기**
people = [
Person("John", 30),
Person("Jane", 25),
Person("Dave", 35)
]
# 나이(age)를 기준으로 정렬
sorted_people = sorted(people, key=attrgetter('age'))
print([(p.name, p.age) for p in sorted_people]) # 나이가 낮은 순으로 출력
# **중첩 속성 접근
# attrgetter()**는 또한 중첩된 속성에 접근할 수 있는 기능을 제공한다. 예를 들어, **attrgetter('a.b.c')**는 **obj.a.b.c**와 동일하게 작동한다.
class Department:
def __init__(self, name):
self.name = name
class Employee:
def __init__(self, name, department):
self.name = name
self.department = department
dept = Department("Finance")
emp = Employee("John", dept)
get_dept_name = attrgetter('department.name')
print(get_dept_name(emp)) # 출력: Finance
attrgetter()를 사용함으로써 복잡한 객체 구조에서도 원하는 속성에 쉽게 접근할 수 있으며, 이를 통해 강력하고 유연한 데이터 처리 코드를 작성할 수 있다.
methodcaller()
operator 모듈의 methodcaller() 함수는 특정 메소드를 호출하는 호출 가능한 객체를 생성한다. 이 함수는 주로 객체의 메소드를 인자와 함께 호출할 때 사용되며, 고차 함수에서 메소드 호출을 추상화하고 싶을 때 유용하다.
기본 사용법
methodcaller()는 첫 번째 인자로 메소드 이름을, 그 이후에는 해당 메소드에 전달할 인자들을 받는다. 반환된 객체는 호출 가능하며, 이 객체에 대상 객체를 전달하면 지정된 메소드가 해당 인자들과 함께 호출된다.
# **객체의 메소드 호출하기**
from operator import methodcaller
class Person:
def __init__(self, name):
self.name = name
def greet(self, greeting):
return f"{greeting}, {self.name}!"
person = Person("John")
say_hello = methodcaller('greet', 'Hello')
print(say_hello(person)) # 출력: Hello, John!
# **고급 사용법: 여러 인자 전달**
class Calculator:
def multiply(self, a, b):
return a * b
calc = Calculator()
get_product = methodcaller('multiply', 10, 5)
print(get_product(calc)) # 출력: 50
# **리스트의 객체들에 대해 메소드 호출하기**
people = [Person("Alice"), Person("Bob"), Person("Charlie")]
greetings = list(map(methodcaller('greet', 'Hi'), people))
print(greetings) # 출력: ['Hi, Alice!', 'Hi, Bob!', 'Hi, Charlie!']
# **중첩 메소드 호출**
# 이 예제는 `methodcaller`의 단순 사용 예시이며, 실제 체인 호출을 위해서는 추가적인 로직이 필요할 수 있다.
getter = methodcaller('get', 'key')
print(getter({"key": "value"})) # 출력: value
methodcaller()를 사용함으로써 코드의 가독성을 높이고, 표준 라이브러리 함수와 함께 조합하여 강력한 한 줄짜리 함수 호출을 구성할 수 있다. 이 기능은 특히 반복적인 메소드 호출을 간결하게 처리할 때 매우 유용하다.
functools.partial()
Python의 functools.partial() 함수는 함수의 일부 인자를 미리 채워넣어 새로운 함수를 생성하는 데 사용된다. 이 기능을 이용하면, 함수 호출 시 반복적으로 사용되는 인자를 미리 설정해둘 수 있으며, 이로써 더 간결하고 명확한 코드 작성이 가능해진다.
기본 사용법
functools.partial() 함수는 첫 번째 인자로 원본 함수를 받고, 그 뒤에는 원본 함수에 전달될 인자들을 지정한다. 반환값은 새로운 함수이며, 이 함수를 호출할 때는 나머지 인자들만 추가로 제공하면 된다.
# **함수 인자 일부 적용**
from functools import partial
def multiply(x, y):
return x * y
# 첫 번째 인자 x를 2로 고정
double = partial(multiply, 2)
# double 함수 사용
print(double(5)) # 10
# **고급 사용법: 키워드 인자 사용**
def greet(greeting, name):
return f"{greeting}, {name}!"
# 인사말을 'Hello'로 고정
say_hello = partial(greet, greeting='Hello')
# say_hello 함수 사용
print(say_hello(name="Alice")) # Hello, Alice!
# **고차 함수와 함께 사용**
# 여러 숫자에 대해 특정 숫자를 더하는 함수
add_numbers = partial(sum, [10, 20, 30])
# 추가 인자 없이 사용
print(add_numbers()) # 60
사용 시나리오
- 콜백 함수 설정: 콜백 함수에 인자를 사전에 설정할 때 유용하다.
- 함수 인터페이스 조정: 다른 API나 고차 함수에서 요구하는 인자 구조와 원본 함수의 인자 구조가 다를 때, partial()을 사용하여 인터페이스를 조정할 수 있다.
- 복잡한 함수 간소화: 복잡한 함수에 미리 일부 인자를 설정하여 간소화된 버전의 함수를 만들 수 있다.
partial() 함수는 특히 매개변수가 많은 함수를 여러 다른 상황에서 재사용할 때 매우 유용하다. 인자의 일부만 변경해서 여러 다른 기능을 하는 새로운 함수를 쉽게 생성할 수 있기 때문에, 코드의 재사용성을 높이고 중복을 줄일 수 있다.
모든 코드는 github에 저장되어 있습니다.
'Python study' 카테고리의 다른 글
함수 데커레이터(Decorator) & 클로저(Closure) (0) | 2024.05.10 |
---|---|
일급 함수 디자인 패턴 (0) | 2024.05.08 |
텍스트(text)와 바이트(byte) (0) | 2024.05.04 |
딕셔너리(dictionary)와 집합(set) (0) | 2024.04.29 |
시퀀스(sequence) - 슬라이싱(slicing), bisect, 다른 시퀀스 (2) | 2024.04.25 |