Python프로그래밍선임 파이썬 개발자

비 클래스 객체가 클래스 상속 목록에 나타날 때, 어떤 동적 치환 메커니즘을 통해 해당 객체가 참여하는 클래스를 지정하여 메서드 결정 순서 계산에 영향을 미칩니까?

Hintsage AI 어시스턴트로 면접 통과

질문에 대한 답변

질문의 역사

Python 3.7에서 PEP 560이 채택되면서, 타입 시스템은 List[int] 또는 Generic[T]와 같은 제네릭 타입을 기본 클래스로 사용할 수 있는 방법이 필요했습니다. 이 개선 이전에는, 매개변수화된 제네릭에서 상속을 시도하면 TypeError가 발생했습니다. 그 이유는 이러한 객체가 실제 클래스가 아니었기 때문이며, 개발자들은 복잡한 메타클래스 우회 방법을 사용해야 해서 라이브러리 설계를 복잡하게 만들었습니다.

문제

인터프리터가 클래스 정의를 처리할 때, C3 선형화 알고리즘을 사용하여 메서드 결정 순서(MRO)를 계산해야 합니다. 이 알고리즘은 모든 기반이 클래스여야 한다는 조건이 있습니다. 기본 객체가 클래스가 아니라 제네릭 별칭인 경우에 문제가 발생합니다. 이 경우 인터프리터는 MRO 생성을 위해 이 별칭을 대신해야 할 실제 클래스가 무엇인지 판단해야 하는 프로토콜이 필요합니다. 이는 상속 의미를 깨뜨리지 않으면서 이루어져야 합니다.

해결책

Python__mro_entries__ 프로토콜을 도입했습니다. 클래스 생성 시 이 메서드를 가진 기반을 만나면 base.__mro_entries__(original_bases)를 호출하고, 클래스의 튜플을 반환할 것으로 기대합니다. 이 클래스는 MRO 계산에서 원래 기반을 대체합니다. 예를 들어, typing.Generic은 이를 구현하여 (Generic,)을 반환하므로 기본적으로 작동할 수 있게 하면서 매개변수화된 논리는 별도로 유지됩니다.

from typing import Generic, TypeVar T = TypeVar('T') # Generic[T]는 클래스가 아니지만, __mro_entries__로 클래스처럼 작동할 수 있다. class Container(Generic[T]): pass # Container.__mro__에는 Generic이 포함되며, Generic[T]는 포함되지 않는다. print(Container.__mro__) # (<class 'Container'>, <class 'typing.Generic'>, <class 'object'>)

일상적인 상황

프레임워크 팀은 사용자들이 Model[UserType]와 같은 매개변수화된 제네릭 기반을 사용하여 데이터 모델을 정의할 수 있도록 해야 했습니다. 그들의 초기 접근법은 클래스 생성 시 메타클래스를 사용자 정의하여 타입 매개변수를 추출하는 것이었지만, 이는 프레임워크와 Django 또는 SQLAlchemy 모델을 결합할 때 메타클래스 충돌을 사용자가 수동으로 해결해야 하는 상황을 초래했습니다.

그들은 클래스 정의 후 클래스를 재작성하는 클래스 데코레이터 사용을 고려했지만, 이 방법은 타입 검사기가 소스 코드를 분석한 후 변환이 발생하기 때문에 정적 타입 검사와 IDE 자동 완성을 깨뜨렸습니다. 또 다른 대안은 __init_subclass__를 사용한 것이었지만, 이 방법은 기본 자체가 클래스가 아닌 경우를 처리할 수 없었습니다.

팀은 그들의 제네릭 팩토리 객체에서 __mro_entries__를 구현했습니다. 사용자가 class UserModel(Model[UserType])을 작성했을 때, Model[UserType] 인스턴스가 __mro_entries__ 메서드에서 (Model,)을 반환하도록 했습니다. 이를 통해 클래스는 Model로부터 올바르게 상속하였고, 팩토리는 런타임 검증을 위한 특정 타입 매개변수를 저장할 수 있었습니다. 이 해결책은 메타클래스 충돌을 제거하고, 완전한 IDE 지원을 유지하며, C3 선형화 알고리즘을 만족하는 깔끔한 상속 계층을 유지했습니다.

후보자들이 종종 놓치는 점

__mro_entries__가 런타임 타입 검사나 isinstance 동작에 영향을 미칩니까?

후보자들은 종종 MRO 생성과 인스턴스 체크를 혼동합니다. __mro_entries__는 클래스 생성 중에만 작동하여 __mro__ 튜플을 구축합니다. 이는 런타임에서의 isinstance() 또는 issubclass() 체크에는 영향을 미치지 않습니다. 이러한 작업은 기존 클래스의 __class____bases__ 속성을 기준으로 하며, 클래스 정의 단계에서 발생한 동적 치환과는 관계가 없습니다.

__mro_entries__가 단일 클래스가 아니라 튜플을 반환합니까?

튜플 반환 타입은 복잡한 다중 상속 시나리오를 수용합니다. 일반적으로 (Generic,)과 같은 단일 요소 튜플을 반환하지만, 이 프로토콜은 제네릭 매개변수가 동시에 여러 믹스인으로부터 상속하는 것을 의미할 수 있도록 합니다. Python은 이 튜플을 MRO 계산을 위한 기반 목록으로 직접 풀어서, (A, B)를 반환하면 클래스가 원래의 비 클래스 기반이 아닌 AB 모두로부터 상속하게 됩니다.

__mro_entries__가 반환하는 클래스에 대해 Python은 어떤 검증을 수행합니까?

인터프리터는 반환된 클래스가 유효한 상속 그래프를 형성하는지 엄격하게 검증합니다. 튜플에 상속 그래프를 일관성 있게 만들지 않는 클래스를 포함하면—예를 들어 C3 선형화 제약을 위반하는 다이아몬드 상속 충돌을 발생시킬 수 있는 클래스를 포함하면—Python은 클래스 생성 중에 TypeError를 발생시킵니다. 이 검증은 동적 치환이 언어의 기본 상속 일관성 규칙을 우회할 수 없도록 보장합니다.