의존성 주입을 위해 Python의 Method Resolution Order를 사용하는 것은 좋지 않습니까?


11

나는 Raymond Hettinger의 Pycon 강연 "슈퍼 고려 슈퍼"를보고 결정적인 방법으로 클래스 "부모"클래스를 선형화하는 Python의 MRO (Method Resolution Order)에 대해 조금 배웠습니다. 우리는 이것을 아래 코드와 같이 의존성 주입을 위해 우리의 이점으로 사용할 수 있습니다. 이제 자연스럽게 super모든 것에 사용하고 싶습니다 !

아래의 예에서 User클래스에서 모두 상속하여 그것의 종속성을 선언 LoggingService하고 UserService. 이것은 특별히 특별하지 않습니다. 흥미로운 부분은 Method Resolution Order를 사용하여 단위 테스트 중에 종속성을 모을 수 있다는 것입니다. 아래 코드 MockUserService는를 상속 UserService하고 우리가 조롱하려는 메소드의 구현을 제공합니다. 아래 예에서는의 구현을 제공합니다 validate_credentials. 이하기 위해 MockUserService어떤 통화를 처리 validate_credentials우리가 전에 배치 할 필요가 UserServiceMRO한다. 이 래퍼 클래스를 생성하여 수행 User전화를 MockUser하고에서를 상속 가지고 UserMockUserService.

이제 우리가 할 때 메소드 MockUser.authenticate호출 순서에 super().validate_credentials() MockUserService앞서 호출 이 이루어 UserService지며, validate_credentials이 구현 의 구체적인 구현을 제공 하므로 사용됩니다. 예, UserService단위 테스트에서 성공적으로 조롱했습니다 . UserService고가의 네트워크 또는 데이터베이스 호출을 수행 할 수 있다는 점을 고려하십시오 . 대기 시간 요소를 제거했습니다. UserService라이브 / 프로드 데이터 를 만질 위험도 없습니다 .

class LoggingService(object):
    """
    Just a contrived logging class for demonstration purposes
    """
    def log_error(self, error):
        pass


class UserService(object):
    """
    Provide a method to authenticate the user by performing some expensive DB or network operation.
    """
    def validate_credentials(self, username, password):
        print('> UserService::validate_credentials')
        return username == 'iainjames88' and password == 'secret'


class User(LoggingService, UserService):
    """
    A User model class for demonstration purposes. In production, this code authenticates user credentials by calling
    super().validate_credentials and having the MRO resolve which class should handle this call.
    """
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def authenticate(self):
        if super().validate_credentials(self.username, self.password):
            return True
        super().log_error('Incorrect username/password combination')
        return False

class MockUserService(UserService):
    """
    Provide an implementation for validate_credentials() method. Now, calls from super() stop here when part of MRO.
    """
    def validate_credentials(self, username, password):
        print('> MockUserService::validate_credentials')
        return True


class MockUser(User, MockUserService):
    """
    A wrapper class around User to change it's MRO so that MockUserService is injected before UserService.
    """
    pass

if __name__ == '__main__':
    # Normal useage of the User class which uses UserService to resolve super().validate_credentials() calls.
    user = User('iainjames88', 'secret')
    print(user.authenticate())

    # Use the wrapper class MockUser which positions the MockUserService before UserService in the MRO. Since the class
    # MockUserService provides an implementation for validate_credentials() calls to super().validate_credentials() from
    # MockUser class will be resolved by MockUserService and not passed to the next in line.
    mock_user = MockUser('iainjames88', 'secret')
    print(mock_user.authenticate())

이것은 매우 영리한 느낌이지만 파이썬의 다중 상속 및 메소드 해결 순서를 적절하고 올바르게 사용합니까? Java로 OOP를 배운 방식으로 상속에 대해 생각할 때 이것은 Usera UserService또는 Useris 라고 말할 수 없기 때문에 완전히 잘못 느낍니다 LoggingService. 그런 식으로 생각하고 상속을 사용하여 위의 코드에서 사용하는 방식은별로 의미가 없습니다. 아니면? 상속을 사용하여 코드 재사용을 제공하고 부모-> 자녀 관계라는 관점에서 생각하지 않는다면 그렇게 나쁘지 않습니다.

내가 잘못하고 있습니까?


여기에는 두 가지 다른 질문이있는 것 같습니다. "이런 종류의 MRO 조작은 안전합니까?" "파이썬 상속 모델이"is-a "관계라고 말하는 것이 부정확합니까?" 둘 다 물어 보거나 둘 중 하나만 물어 보려고합니까? (모두 좋은 질문입니다. 올바른 질문에 답하거나 두 가지를 원하지 않으면 두 질문으로
나눕니다

나는 그것을 읽을 때 질문을 해결했습니다.
Aaron Hall

@lxrec 당신이 옳다고 생각합니다. 두 가지 다른 질문을하려고합니다. 이것이 "올바른"느낌이 들지 않는 이유는이 유형 대신에 "is-a"상속 스타일 (GoldenRetriever "is-a"Dog and Dog "is-a"Animal)을 생각하고 있기 때문입니다. 구성 접근. 나는 이것이 내가 또 다른 질문을 열 수 있다고 생각한다 :)
Iain

이것은 또한 나를 크게 혼란스럽게합니다. 상속보다 합성이 선호되는 경우 LoggingService 및 UserService 인스턴스를 User 생성자에 전달하고 멤버로 설정하지 않는 이유는 무엇입니까? 그런 다음 의존성 주입에 duck 타이핑을 사용하고 대신 MockUserService 인스턴스를 User 생성자에 전달할 수 있습니다. DI에 super를 사용하는 것이 왜 바람직한가요?
Jake Spracher

답변:


7

의존성 주입을 위해 Python의 Method Resolution Order를 사용하는 것은 좋지 않습니까?

아닙니다. 이것은 이론적으로 C3 선형화 알고리즘을 사용하는 것입니다. 이것은 친숙한 is-a 관계에 위배되지만 일부는 구성이 상속보다 선호되는 것으로 간주합니다. 이 경우 몇 가지 관계가 있습니다. 파이썬이 로깅 모듈을 가지고 있지만 의미는 약간 의문의 여지가 있지만 학업 연습으로는 완벽하게 좋습니다.

조롱이나 원숭이 패치가 나쁜 것은 아니라고 생각하지만,이 방법으로 피할 수 있다면 더 좋을 것입니다. 더 많은 복잡성으로 인해 프로덕션 클래스 정의를 수정하지 않았습니다.

내가 잘못하고 있습니까?

좋아 보인다. 원숭이 패치 또는 모의 패치를 사용하지 않고 잠재적으로 비싼 방법을 재정의했습니다. 다시 말해 프로덕션 클래스 정의를 직접 수정하지 않은 것입니다.

실제로 테스트에서 자격 증명을 갖지 않고 기능을 연습하려는 경우 다음과 같은 작업을 수행해야합니다.

>>> print(MockUser('foo', 'bar').authenticate())
> MockUserService::validate_credentials
True

실제 자격 증명을 사용하는 대신 어설 션을 사용하여 매개 변수가 올바르게 수신되었는지 확인하십시오 (결국 테스트 코드이므로).

def validate_credentials(self, username, password):
    print('> MockUserService::validate_credentials')
    assert username_ok(username), 'username expected to be ok'
    assert password_ok(password), 'password expected to be ok'
    return True

그렇지 않으면, 당신이 알아 낸 것 같습니다. 다음과 같이 MRO를 확인할 수 있습니다.

>>> MockUser.mro()
[<class '__main__.MockUser'>, 
 <class '__main__.User'>, 
 <class '__main__.LoggingService'>, 
 <class '__main__.MockUserService'>, 
 <class '__main__.UserService'>, 
 <class 'object'>]

그리고 MockUserService가.보다 우선 함을 확인할 수 있습니다 UserService.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.