『Effective Python』の続き。__init__
のポリモーフィズム?
www.effectivepython.com
Googleに勤務する筆者らしく、MapReduceの構築を例にジェネリックなオブジェクト構築を説明している。 ハードディスク上の巨大なファイルの行数を数え上げる機能を実装しているが、拡張しやすいようにジェネリックに書きたいよね、という流れ。
Pythonのクラスのコンストラクタは__init__
しかないので、@classmethod
デコレータでジェネリックなオブジェクト構築を実現しようという流れ。
class GenericInputData(object): def read(self): raise NotImplementedError @classmethod def generate_inputs(cls, config): raise NotImplementedError class PathInputData(GenericInputData): def __init__(self, path): super().__init__() self.path = path def read(self): return open(self.path).read() @classmethod def generate_inputs(cls, config): data_dir = config['data_dir'] for name in os.listdir(data_dir): yield cls(os.path.join(data_dir, name))
@classmethod
でデコレートされたgenerate_inputs
内で__init__
を実行している。
ちょっとわかりずらいが、generate_inputs
はクラスメソッドであるので第一引数にクラスそのものcls
が渡され、cls()
として__init__
を実行している。
こちらはMapReduceにおけるWorker
の実装。
class GenericWorker(object): def __init__(self, input_data): self.input_data = input_data self.result = None def map(self): raise NotImplementedError def reduce(self, other): raise NotImplementedError @classmethod def create_workers(cls, input_class, config): workers = [] for input_data in input_class.generate_inputs(config): workers.append(cls(input_data)) return workers class LineCountWorker(GenericWorker): def map(self): data = self.input_data.read() self.result = data.count('\n') def reduce(self, other): self.result += other.result
create_workers
内でinput_class.generate_inputs
を利用している。また、workers.append(cls(input_data))
として内部で__init__
を実行している。
恥ずかしながら、Pythonで大規模なプログラムを構築したことが無く、このありがたみがよくわかっていないが、サブクラスがどんな形をしていても同じように実行できるという点はよいことである。 また、この項目を読んだ際にTypoレベルの誤りを発見し、筆者に報告したところ取り上げてくれた。
Item 24, p. 69, L13: classes vs subclasses · Issue #25 · bslatkin/effectivepython · GitHub
それにしても、もう少しちゃんとした英文が書けるようになりたいものである。