문제의 역사:
파이썬에서 모든 것은 객체이며, 모든 함수 또한 객체입니다. 클래스의 인스턴스가 함수처럼 동작할 수 있도록 하는 메커니즘이 구현되었습니다. 이를 위해 특별한 마법 메소드인 __call__이 도입되어 객체를 호출 가능하게 만듭니다.
문제:
때때로 호출할 수 있는 동작을 수행하는 객체, 예를 들어 상태를 가지는 함수(상태 함수), 클로저, 이벤트 핸들러 객체 등을 전달해야 할 필요가 있습니다. 아키텍처 개발은 이러한 기능성을 올바르게 구현하는 방법을 이해해야 합니다.
해결책:
클래스에서 call 메소드를 구현하면 인스턴스를 "함수처럼" 호출 가능하게 만들 수 있습니다. 이는 클래스의 기능(상태 캡슐화, 상속, 메소드)과 함수의 기능(호출 가능성)을 결합할 수 있게 합니다. 이러한 접근 방식은 명령 객체, 복잡한 핸들러, 래퍼 등을 생성하는 데 사용됩니다.
코드 예시:
class Adder: def __init__(self, x): self.x = x def __call__(self, y): return self.x + y add5 = Adder(5) print(add5(10)) # 15 출력
주요 특징:
call 메소드는 일반 함수의 속성(예: name 및 doc)을 상속합니까?
아니요, call 메소드를 가진 객체는 name 속성이 없거나 클래스에서 가져옵니다. 함수의 메타데이터는 유지되지 않습니다.
__call__이 구현된 객체는 진정한 함수인가요?
아니요, 이는 클래스의 인스턴스이지 함수가 아닙니다. 이는 단지 "호출 가능" 동작을 구현할 뿐입니다. 예를 들어, isinstance(obj, types.FunctionType)로 함수와 비교하면 False가 반환됩니다.
__call__을 가진 객체에 함수를 위한 데코레이터를 적용할 수 있습니까?
보통 그러한 데코레이터는 바로 함수를 기대하며 객체는 아닙니다(예: functools.lru_cache). 사용하면 오류가 발생하거나 전혀 작동하지 않을 수 있습니다.
장점:
부정적인 케이스: 프로젝트에서 __call__이 있는 클래스를 통해 설정(레벨, 파일 이름)을 저장하는 로거를 구현했습니다. 그러나 신호 핸들러 함수가 실제로 함수를 기대한다는 것을 잊고 핸들러 등록 시 오류(object is not a function)를 받았습니다.
장점: 로거의 유연한 설정. 단점: 예상되는 인터페이스와의 불일치.
긍정적인 케이스: 다른 프로젝트에서 __call__이 있는 클래스를 사용해 복잡한 데코레이터 기능을 생성하여 매개변수를 저장함으로써 테스트를 쉽게 했습니다.
장점: 확장성, 편리함. 단점: 함수나 람다에 비해 코드가 더 많습니다.