Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Python で Dependency Injection(DI) をやるには?

shimakaze-git
February 23, 2019

Python で Dependency Injection(DI) をやるには?

shimakaze-git

February 23, 2019
Tweet

More Decks by shimakaze-git

Other Decks in Programming

Transcript

  1. Who are you? おまえだれよ? from dataclasses import dataclass @dataclass class

    User: """""" name: str = "shimakaze_soft" shimakaze-soft 2
  2. 実際に作成してみる - 1 import json # Jsonファイルを生成するクラス class JsonOutputFile: title:

    str description: str def output(self, title: str, description: str) -> None: self.title = title self.description = description output: dict[str, str] = { "title": self.title, "description": self.description } with open("output.json", "w") as f: json.dump(output, f, indent=4) shimakaze-soft 14
  3. 実際に作成してみる - 2 class Report: title: str description: str output_obj:

    JsonOutputFile def __init__(self, title: str, description: str) -> None: self.title = title self.description = description self.output_obj = JsonOutputFile() def output_file(self) -> None: self.output_obj.output(self.title, self.description) title: str = "title" description: str = "description" report: Report = Report(title, description) report.output_file() shimakaze-soft 15
  4. 直接インスタンスを生成しているので、変更に少し弱 い実装 class Report: title: str description: str output_obj: JsonOutputFile

    def __init__(self, title: str, description: str) -> None: self.title = title self.description = description # この行でファイルを書き込むクラスのJsonOutputFileのインスタンスを直接生成している self.output_obj = JsonOutputFile() def output_file(self) -> None: self.output_obj.output(self.title, self.description) shimakaze-soft 17
  5. 実際に作成してみる - 1 class HtmlOutputFile: def output(self, title: str, description:

    str) -> None: text: str = "<html>\n" text += "<head>\n" text += "<title>" + title + "</title>\n" text += "</head>\n" text += "<head>\n" text += "<body><p>" + description + "</p></body>\n" text += "</html>\n" with open("output.html", mode="w") as f: f.write(text) shimakaze-soft 19
  6. 実際に作成してみる - 2 インスタンス変数である output_obj に対して、 JsonOutputFile から HtmlOutputFile に直接書き換えなければいけない。

    class Report: title: str description: str output_obj: HtmlOutputFile def __init__(self, title: str, description: str) -> None: self.title = title self.description = description # この行で直接、HtmlOutputFileに書き換えている self.output_obj = HtmlOutputFile() def output_file(self) -> None: self.output_obj.output(self.title, self.description) shimakaze-soft 20
  7. 先程のコードの問題点 クラスのインスタンスを直接生成してしまっている 「JSONファイルの生成からHTMLファイルの生成に変更」 などの場合のように、変更に弱くな る実装 Reportクラス が JsonOutputFileクラス または HtmlOutputFileクラス

    に依存してい る状態 class Report: .... def __init__(self, title: str, description: str) -> None: .... # この行で直接、JsonOutputFileからHtmlOutputFileに書き換えなければいけない # self.output_obj = JsonOutputFile() self.output_obj = HtmlOutputFile() shimakaze-soft 21
  8. DIを適用する例 - 2 class Report: title: str description: str def

    __init__(self, title: str, description: str, output_obj) -> None: self.title = title self.description = description # インスタンスをコンストラクタで受け取る self.output_obj = output_obj def output_file(self) -> None: self.output_obj.output(self.title, self.description) title: str = "title" description: str = "description" # インスタンスの生成は外側で行う output_obj: HtmlOutputFile = HtmlOutputFile() # output_obj: JsonOutputFile = JsonOutputFile() report: Report = Report(title, description, output_obj) report.output_file() shimakaze-soft 25
  9. 先程の実装の問題点 class Report: title: str description: str # コンストラクタの引数、output_objの型が不明 def

    __init__(self, title: str, description: str, output_obj) -> None: self.title = title self.description = description # インスタンス変数であるoutput_objの型が不明 self.output_obj = output_obj def output_file(self) -> None: self.output_obj.output(self.title, self.description) title: str = "title" description: str = "description" output_obj: HtmlOutputFile = HtmlOutputFile() # output_obj: JsonOutputFile = JsonOutputFile() report: Report = Report(title, description, output_obj) report.output_file() shimakaze-soft 28
  10. ある程度の制約は設ける コンストラクタの引数である output_obj が JsonOutputFile であることがわかる class Report: title: str

    description: str output_obj: JsonOutputFile # output_objがJsonOutputFileのインスタンス def __init__(self, title: str, description: str, output_obj: JsonOutputFile) -> None: self.title = title self.description = description self.output_obj = output_obj def output_file(self) -> None: self.output_obj.output(self.title, self.description) shimakaze-soft 29
  11. 特定のクラスの型しか受け取れない この例では JsonOutputFile しか受け取れない class Report: title: str description: str

    output_obj: JsonOutputFile def __init__(self, title: str, description: str, output_obj: JsonOutputFile) -> None: self.title = title self.description = description self.output_obj = output_obj def output_file(self) -> None: self.output_obj.output(self.title, self.description) shimakaze-soft 31
  12. JsonOutputFileとHtmlOutputFileのどちらもoutputというメソッ ドを実装していた class JsonOutputFile: title: str description: str def output(self,

    title: str, description: str) -> None: self.title = title self.description = description output: dict[str, str] = { "title": self.title, "description": self.description } with open("output.json", "w") as f: json.dump(output, f, indent=4) shimakaze-soft 36
  13. JsonOutputFileとHtmlOutputFileのどちらもoutputというメソッ ドを実装していた class HtmlOutputFile: def output(self, title: str, description: str)

    -> None: text: str = "<html>\n" text += "<head>\n" text += "<title>" + title + "</title>\n" text += "</head>\n" text += "<head>\n" text += "<body><p>" + description + "</p></body>\n" text += "</html>\n" with open("output.html", mode="w") as f: f.write(text) shimakaze-soft 37
  14. PythonのAbstract Base Class(abc) Pythonには abc という抽象クラスを実現するモジュールを標準で備えている from abc import ABC,

    abstractmethod class AbstractOutputFile(ABC): @abstractmethod def output(self, title: str, description: str) -> None: raise NotImplementedError() shimakaze-soft 47
  15. 抽象に依存させたコード 抽象クラスである AbstractOutputFile from abc import ABC, abstractmethod class AbstractOutputFile(ABC):

    @abstractmethod def output(self, title: str, description: str) -> None: raise NotImplementedError() shimakaze-soft 48
  16. 抽象に依存させたコード - 3 `AbstractOutputFile`を継承 class JsonOutputFile(AbstractOutputFile): title: str description: str

    def output(self, title: str, description: str) -> None: self.title = title self.description = description output: dict[str, str] = { "title": self.title, "description": self.description } with open("output.json", "w") as f: json.dump(output, f, indent=4) # `AbstractOutputFile`を継承 class HtmlOutputFile(AbstractOutputFile): def output(self, title: str, description: str) -> None: text: str = "<html>\n" text += "<head>\n" text += "<title>" + title + "</title>\n" text += "</head>\n" text += "<head>\n" text += "<body><p>" + description + "</p></body>\n" text += "</html>\n" with open("output.html", mode="w") as f: f.write(text) shimakaze-soft 50
  17. 抽象に依存させたコード - 3 AbstractOutputFile型 を引数で受け取れるようにする class Report: title: str description:

    str # インスタンス変数であるoubput_objはAbstractOutputFile型 output_obj: AbstractOutputFile # コンストラクタの引数であるoubput_objはAbstractOutputFile型 def __init__(self, title: str, description: str, output_obj: AbstractOutputFile) -> None: self.title = title self.description = description self.output_obj = output_obj def output_file(self) -> None: self.output_obj.output(self.title, self.description) shimakaze-soft 51
  18. 実は継承しなくても動きます # AbstractOutputFileを継承していない class OtherOutputFile: def output(self, title: str, description:

    str) -> None: """""" .... class Report: title: str description: str output_obj: AbstractOutputFile def __init__(self, title: str, description: str, output_obj: AbstractOutputFile) -> None: self.title = title self.description = description self.output_obj = output_obj def output_file(self) -> None: self.output_obj.output(self.title, self.description) output_obj: OtherOutputFile = OtherOutputFile() report: Report = Report("title", "description", output_obj) report.output_file() shimakaze-soft 57
  19. mypyでチェックする 正しい型の値 が入っているかをチェックできる mypy というサードパーティのツールが あるため、mypyでチェックを行う # インストール方法 $ pip

    install mypy $ mypy sample.py Success: no issues found in 1 source file Success: no issues found in 1 source file と出れば型定義に問題なく成功 shimakaze-soft 59
  20. mypyでチェックする HtmlOutputFile から AbstractOutputFile の継承を外してみる class HtmlOutputFile(): def output(self, title:

    str, description: str) -> None: """""" .... 以下のようなエラーが出るはずです。 $ mypy sample.py sample.py:63: error: Argument 3 to "Report" has incompatible type "HtmlOutputFile"; expected "AbstractOutputFile" [arg-type] Found 1 error in 1 file (checked 1 source file) shimakaze-soft 60