HOWTO · Python
파이썬 클래스 팩토리
이 튜토리얼은 Python의 클래스 팩토리에 대해 설명합니다.
이 페이지의 내용
클래스 팩토리는 클래스 객체를 만들고 반환하는 호출 가능 객체입니다. 팩토리를 호출해도 인스턴스가 만들어지는 것은 아닙니다. 나중에 인스턴스화할 수 있는 클래스를 얻습니다. 이 구분은 프로그램 실행 중에만 사용할 수 있는 설정에 따라 클래스 정의가 달라질 때 유용합니다.
대부분의 Python 프로그램에서는 모듈 수준에 일반 클래스를 정의하는 편이 좋습니다. 클래스가 고정되어 있고 예측 가능한 한 가지 방식으로 사용된다면 이것이 가장 명확한 선택입니다. 선택한 기본값, 생성한 이름 또는 작은 설정에서 조립한 메서드처럼 호출자에게 맞춘 클래스가 필요할 때 팩토리를 사용하세요. 팩토리의 역할을 좁게 유지해야 합니다. 관련 없는 여러 클래스 형태를 반환하면 타입을 이해하고 테스트하기 어려워질 수 있습니다.
이 튜토리얼에서는 중첩된 class 정의와 Python의 세 인수 type() 형식을 보여 줍니다. 두 기법 모두 클래스 객체를 반환합니다. 중첩 클래스 방식은 대체로 읽기 쉽고, type()은 클래스 이름, 기본 클래스 또는 네임스페이스를 동적으로 제공해야 할 때 유용합니다.
Python에서 클래스 팩터리를 만드는 방법
클래스 팩토리를 일반적으로 사용할 때는 두 번 호출합니다. 먼저 팩토리를 호출해 반환된 클래스를 저장합니다. 다음으로 그 반환 클래스를 호출해 객체를 만듭니다. 예를 들어 Ball = create_ball_class()는 클래스에 Ball을 할당하고, Ball("green")은 Ball 인스턴스를 생성합니다.
class 키워드는 Python이 함수 본문을 실행할 때 클래스를 만듭니다. type(name, bases, namespace) 형식도 그 시점에 클래스를 만듭니다. name은 문자열이고, bases는 부모 클래스의 튜플이며, namespace는 속성 이름을 값이나 함수에 매핑합니다. 네임스페이스에 넣은 함수는 인스턴스를 통해 접근할 때 인스턴스 메서드가 됩니다.
반환된 클래스가 예상 가능하도록 이름과 생성자 인수를 선택하세요. 팩토리를 호출할 때마다 서로 다른 클래스가 만들어지면, 정의가 동일해 보여도 별도 호출에서 만든 인스턴스는 같은 클래스 정체성을 공유하지 않습니다. 이는 isinstance() 검사, 직렬화 및 디버깅에 영향을 줄 수 있습니다. 안정적인 공개 타입 하나가 필요하다면 일반 클래스 문법으로 한 번 정의하고 인스턴스에 설정을 전달하세요.
class 키워드를 사용하여 Python에서 클래스 팩토리 생성
함수 안에 클래스 정의를 넣고 클래스 이름을 반환합니다. 중첩 클래스는 팩토리 범위의 값을 사용할 수 있으므로, 이 패턴은 작은 설정 선택에 실용적입니다. 이 예제에서 팩토리는 Ball을 반환하고, 인스턴스는 생성자에 전달된 색상을 저장합니다.
def create_ball_class():
class Ball:
def __init__(self, color):
self.color = color
def get_color(self):
return self.color
return Ball
Ball = create_ball_class()
ball = Ball("green")
print(ball.get_color())
반환된 클래스는 다른 클래스와 똑같이 동작하므로, 인스턴스에서 메서드와 속성을 사용할 수 있습니다.
green
설정을 받는 팩토리라면 클래스를 반환하기 전이나 생성된 생성자에서 설정을 검증하세요. 검증을 하면 사용할 수 없는 객체가 돌아다니게 두는 대신, 잘못된 입력이 문서화된 경계에서 실패합니다. 다음 경계 사례는 빈 색상을 거부하고 예외를 처리하므로 프로그램의 출력이 결정적입니다.
def create_ball_class(default_color):
class Ball:
def __init__(self, color=default_color):
if not color:
raise ValueError("color must not be empty")
self.color = color
return Ball
Ball = create_ball_class("")
try:
Ball()
except ValueError as error:
print(error)
color must not be empty
이 팩토리는 default_color를 클로저로 캡처하므로, 반환된 각 클래스는 해당 호출에 제공된 값을 기억합니다. 이는 작고 변경 불가능한 설정에 편리합니다. 공유 상태가 의도된 경우가 아니라면 변경 가능한 공유 객체를 캡처하지 마세요. 그렇지 않으면 한 인스턴스가 다른 인스턴스에 예기치 않게 영향을 줄 수 있습니다. 설정이 모든 인스턴스에 속하지만 쉽게 확인할 수 있어야 한다면 클래스 속성으로 할당하는 방법도 있습니다.
type() 내장 함수의 세 인수 형식으로 Python에서 클래스 팩토리 생성
내장 함수 type()은 런타임에 클래스를 직접 만들 수 있습니다. 클래스 생성에 쓰는 세 인수 형식은 type(name, bases, namespace)입니다. 네임스페이스 딕셔너리에는 클래스 속성이 들어갑니다. 예를 들어 "get_color" 아래에 저장된 함수는 인스턴스 메서드로 사용됩니다. 모든 요소가 이미 데이터라면 직접 호출하는 방식은 간결하지만, 그 옆에 정의한 도우미 함수는 모듈 수준 이름으로 남습니다.
def init(self, color):
self.color = color
def get_color(self):
return self.color
Ball = type(
"Ball",
(object,),
{"__init__": init, "get_color": get_color},
)
ball = Ball("green")
print(ball.get_color())
green
도우미 함수가 구현 세부 사항이고 팩토리가 설정된 런타임 클래스를 반환해야 한다면 도우미 함수를 팩토리 안에 캡슐화하세요. 아래 코드는 관찰 가능한 동작은 같지만, init과 get_color는 모듈 네임스페이스에 추가되지 않고 팩토리 범위에 있습니다.
def create_ball_class(class_name):
def init(self, color):
self.color = color
def get_color(self):
return self.color
return type(
class_name,
(object,),
{"__init__": init, "get_color": get_color},
)
Ball = create_ball_class("Ball")
ball = Ball("green")
print(Ball.__name__)
print(ball.get_color())
Ball
green
이름, 상속 튜플 또는 속성 집합이 실제로 데이터 기반일 때 이 접근법을 사용하세요. 정적인 클래스라면 일반 class 문법이 가독성, 타입 검사기 지원 및 트레이스백 측면에서 더 낫습니다. 어떤 방식을 선택하든 팩토리가 값을 반환했는지만 확인하지 말고, 인스턴스를 생성하고 호출자에게 필요한 동작을 실행하여 반환된 클래스를 테스트하세요.