HOWTO · Python
Python에서 생성자 오버로드
이 자습서는 Python에서 생성자를 오버로드하는 방법을 보여줍니다.
이 페이지의 내용
Python은 매개변수를 비교해 여러 __init__ 메서드 중 하나를 선택하지 않습니다. 클래스에서 __init__를 두 번 이상 정의하면 나중 정의가 앞선 정의를 대체하므로, 인스턴스를 만들 때 사용할 수 있는 정의는 하나뿐입니다. 객체를 여러 방식으로 초기화하려면 하나의 초기화 메서드 안에서 입력을 분기하거나, 이름이 있는 팩터리 메서드를 제공하거나, 초기화 매개변수에 적절한 기본값을 지정하세요.
Python에서 생성자 오버로딩이란
시그니처 기반 생성자 오버로딩을 지원하는 언어에서는 런타임이 매개변수의 유형이나 개수에 따라 생성자를 선택할 수 있습니다. Python에서 __init__를 여러 번 정의해도 그렇게 동작하지 않습니다. 하나의 __init__가 다양한 인수를 받아 의도적으로 선택하도록 만들 수는 있지만, 이는 메서드 하나 안에서 분기하는 것이지 언어가 제공하는 오버로딩은 아닙니다. 호출자가 클래스에서 지원하는 값의 형태를 알 수 있도록 허용하는 입력을 명확히 하세요.
*args로 인수 유형이나 개수에 따라 분기하기
*args 매개변수는 위치 인수를 튜플로 모읍니다. 튜플의 길이나 값의 유형을 확인한 다음 그에 맞게 인스턴스를 초기화할 수 있습니다. 값을 읽기 전에 인수가 있는지 확인하고, 지원하지 않는 입력 형식을 받았을 때 클래스가 어떻게 동작할지도 정하세요. 다음 실행 가능한 예제는 정수 하나 또는 문자열 하나를 받아 선택된 값을 표시합니다.
이 예제를 실행하세요:
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
class Greeting:
def __init__(self, *args):
if len(args) != 1:
self.message = "Pass one integer or string"
else:
value = args[0]
if isinstance(value, int):
self.message = str(value)
elif isinstance(value, str):
self.message = "Hello from " + value
else:
self.message = "Pass an integer or string"
print(Greeting("Delft").message)
args[0]에 접근하기 전에 인수 개수를 확인하면 위치 인수가 없는 호출도 안전하게 처리할 수 있습니다. 개수 검사는 인수가 너무 많이 전달된 경우도 거릅니다. Python에서 bool은 int의 하위 클래스이므로 isinstance(True, int)는 참입니다. 불리언을 정수로 받아들이지 않으려면 정확한 유형을 검사하세요.
위치 인수 개수마다 의미가 분명할 때는 개수에 따라 분기하는 방식이 유용합니다. 이 초기화 메서드는 인수를 정확히 0개, 1개 또는 2개 받습니다. 인수가 없으면 기본 범위를 선택하고, 하나면 끝값을 지정하며, 두 개면 시작값과 끝값을 지정합니다. 그 밖의 개수에는 명확한 메시지를 표시합니다. 각 분기에서 args의 값을 읽기 전에 개수를 확인하므로, 인수가 없거나 지원하지 않는 개수여도 존재하지 않는 값을 가져오지 않습니다.
이 예제를 실행하세요:
class RangeSpec:
def __init__(self, *args):
if len(args) == 0:
self.start, self.stop = 0, 10
elif len(args) == 1:
self.start, self.stop = 0, args[0]
elif len(args) == 2:
self.start, self.stop = args
else:
self.start, self.stop = None, None
self.message = "Pass zero, one, or two values"
def describe(self):
if self.start is None:
return self.message
return f"Range from {self.start} to {self.stop}"
for spec in (RangeSpec(), RangeSpec(5), RangeSpec(2, 5), RangeSpec(1, 2, 3)):
print(spec.describe())
네 번의 호출은 지원되는 모든 인수 개수와 지원 범위를 벗어난 개수 하나를 다룹니다. 입력 형태가 더 많다면 호출자가 위치 인수 개수마다 어떤 의미인지 기억하지 않아도 되도록 이름이 있는 매개변수나 팩터리 메서드를 사용하는 편이 좋습니다.
@classmethod 팩터리 메서드를 대체 생성자로 사용하기
@classmethod 메서드는 첫 번째 인수로 클래스를 받으며, 이 인수의 관례적인 이름은 cls입니다. 팩터리 메서드는 다른 표현 형식의 값을 일반 초기화 메서드에 필요한 값으로 변환한 뒤 cls(...)를 반환할 수 있습니다. 이렇게 하면 인스턴스가 만들어집니다. cls가 메서드를 호출한 클래스를 가리키므로 하위 클래스에서도 자연스럽게 동작합니다. 팩터리 메서드는 이름이 붙은 생성 경로를 제공하는 방식이며, __init__에 오버로드를 추가하지는 않습니다.
이 예제를 실행하세요:
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def from_pair(cls, pair):
return cls(pair[0], pair[1])
point = Point.from_pair((3, 4))
print(point.x, point.y)
입력 형식에 의미 있는 이름이 있다면 from_file, from_row, from_coordinates처럼 형식별로 별도의 팩터리 메서드를 사용하세요. 변환 과정에서 데이터 검증이나 정규화가 필요하다면 팩터리 메서드에서 처리할 수 있습니다. 객체가 갖춰야 할 필수 상태를 설정하는 역할은 기본 초기화 메서드 한 곳에 두세요.
선택적 초기화 패턴에 기본 인수 사용하기
매개변수가 실제로 선택 사항이고 그 의미도 명확하다면 기본값을 사용하는 것이 좋습니다. 호출자는 분기 로직을 추가하지 않고도 값을 생략할 수 있습니다. 숫자, 문자열 또는 None처럼 변경할 수 없는 값을 기본값으로 사용하세요. 리스트처럼 변경 가능한 값은 인스턴스끼리 실수로 공유하지 않도록 보통 메서드 안에서 생성해야 합니다.
이 예제를 실행하세요:
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
class Profile:
def __init__(self, name="Guest", score=0):
self.name = name
self.score = score
for profile in (Profile(), Profile("Mina"), Profile("Mina", 7)):
print(profile.name, profile.score)
여기서는 선택적 값 둘 다 생략하거나, 이름만 지정하거나, 두 값을 모두 지정할 수 있습니다. 생략이 여러 가지 의미로 해석되거나 매개변수끼리 의존성이 있거나 위치 인수 조합이 많아져 기억하기 어려워진다면 기본값만으로 처리하기는 적합하지 않습니다. 이런 경우 키워드 전용 매개변수나 이름이 있는 팩터리 메서드를 사용하면 호출이 더 명확해집니다.
적절한 생성자 패턴 선택하기
일반적인 선택값이 몇 개뿐이라면 기본값을 사용하세요. 다른 표현 형식에서 객체를 만들거나 생성 경로에 설명적인 이름을 붙이고 싶다면 팩터리 메서드를 사용하세요. 의도한 API에 간결하고 안정적인 위치 인수 형태가 포함된 경우에만 *args 분기를 사용하고, 값을 활용하기 전에 인수 개수와 유형을 검증하세요. 복잡한 검증에서는 영리한 유형 처리 기법보다 명시적인 분기와 유용한 오류 메시지를 유지하기가 쉽습니다.
이 기법들은 함께 사용할 수도 있습니다. 팩터리 메서드에서 값을 검증한 뒤 같은 초기화 메서드를 호출하고, 기본값으로 선택적 필드를 처리할 수 있습니다. 유효한 호출은 분명하고 지원하지 않는 호출은 예측 가능하도록 가장 단순한 인터페이스를 선택하세요. 핵심은 Python에서 클래스마다 실질적으로 사용되는 __init__ 정의가 하나라는 점입니다. 유연성은 그 정의 주변에 제공하는 로직과 이름이 있는 대안에서 나옵니다.