私はレイモンドヘッティンガーのPyconの講演「超熟考スーパー」を見て、決定論的な方法でクラス「親」クラスを線形化するPythonのMRO(メソッド解決順序)について少し学びました。これを使用して、以下のコードのように、依存性注入を行うことができます。だから今、当然、すべてにsuper
を使用したいと思います!
以下の例では、User
クラスは、LoggingService
とUserService
の両方から継承することで、依存関係を宣言しています。これは特に特別なことではありません。興味深いのは、メソッド解決順序を使用して、単体テスト中に依存関係を模擬できることです。以下のコードは、MockUserService
を継承するUserService
を作成し、モックしたいメソッドの実装を提供します。以下の例では、_validate_credentials
_の実装を提供しています。 MockUserService
に_validate_credentials
_への呼び出しを処理させるには、MROでUserService
の前に配置する必要があります。これは、User
と呼ばれるMockUser
の周りにラッパークラスを作成し、User
およびMockUserService
から継承させることで行われます。
ここで、_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())
_
これはかなり賢く感じられますが、これはPythonの多重継承とメソッド解決順序の有効かつ有効な使用ですか私が学んだ方法で継承について考えるときOOP with Javaこれは、User
がUserService
またはUser
はLoggingService
です。そのように考えると、上記のコードのように継承を使用しても意味がありません。あるいは、継承を使用する場合純粋にコードの再利用を提供するためだけで、親と子の関係を考慮せずに、これはそれほど悪くないように見えます。
私はそれを間違っていますか?
依存関係注入のためのPythonのメソッド解決順序の使用-これは悪いですか?
いいえ。これは、C3線形化アルゴリズムの理論的な使用目的です。これはおなじみのis-a関係に反しますが、継承よりも構成を優先すると考える人もいます。この場合は、いくつかのa-a関係を構成しました。あなたは正しい方向に進んでいるようです(Pythonにはロギングモジュールがあるため、セマンティクスは少し疑問ですが、学術的な演習としては完全に問題ありません)。
モックやモンキーパッチは悪いことではないと私は思いますが、この方法で回避できる場合は、良いことです。複雑さが増しているため、本番クラスの定義の変更を避けています。
私はそれを間違っていますか?
よさそうです。モンキーパッチやモックパッチを使用せずに、コストがかかる可能性のあるメソッドをオーバーライドしました。これも、プロダクションクラスの定義を直接変更していないことを意味します。
テストで実際に資格情報を持たずに機能を実行することが目的である場合は、おそらく次のようにする必要があります。
>>> 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
よりも優先されていることを確認できます。