[关闭]
@runzhliu 2018-02-09T08:59:15.000000Z 字数 3269 阅读 1087

classmethod 构建多态通用的类

Python classmethod 多态 继承


参考资料
《Effective Python》


多态是指继承体系中的多个类都可以以各自独有的方式来实现某个方法,这些类都满足相同的接口或者继承自相同的抽象类,但却各有功能。下面是一个类继承的实例。

  1. class InputData(object):
  2. def read(self):
  3. raise NotImplementedError
  4. class PathInputData(InputData):
  5. def __init__(self, path):
  6. super(PathInputData, self).__init__()
  7. self.path = path
  8. def read(self):
  9. return open(self.path).read()
  10. class Worker(object):
  11. def __init__(self, input_data):
  12. self.input_data = input_data
  13. self.result = None
  14. def map(self):
  15. raise NotImplementedError
  16. def reduce(self, other):
  17. raise NotImplementedError
  18. class LineCountWorker(Worker):
  19. def map(self):
  20. data = self.input_data.read()
  21. self.result = data.count('\n')
  22. def reduce(self, other):
  23. self.result += other.result

InputData 为抽象类,其 read 方法需要由继承他的 PathInputData 子类来实现。然后是一个关于工作线程的一套抽象接口,Worker 类作为抽象类,需要子类 LineCountWorker 来实现 mapreduce 方法。为了串联这些类,可以通过一些辅助类、辅助方法来实现。

  1. def generate_inputs(data_dir):
  2. for name in os.listdir(data_dir):
  3. yield PathInputData(os.path.join(data_dir, name))
  4. def create_workers(input_list):
  5. workers = []
  6. for input_data in input_list:
  7. workers.append(LineCountWorker(input_data))
  8. return workers
  9. def execute(workers):
  10. threads = [Thread(target=w.map) for w in workers]
  11. for thread in threads:
  12. thread.start()
  13. for thread in threads:
  14. thread.join()
  15. first, rest = workers[0], workers[1:]
  16. for worker in rest:
  17. first.reduce(worker)
  18. return first.result
  19. def mapreduce(data_dir):
  20. inputs = generate_inputs(data_dir)
  21. workers = create_workers(inputs)
  22. return execute(workers)
  23. if __name__ == '__main__':
  24. print mapreduce("/Users/runzhliu/workspace/python-utils/data")

可以看到 mapreduce 方法实际集成了多个辅助方法,首先是创建一系列 input 然后通过创建 worker 来调度线程,最后是 excute 方法。实现起来很美,只是 mapreduce 不够通用啊!那具体是哪儿不通用呢,当需要编写其他的 InputDataWorker 子类,那就得重写那几个辅助方法了。而为了解决这个问题,需要一种更通用的方式来创建对象。在 Python 中,只允许 __init__ 这个构造器方法,而不像在 Java 或者 Scala 中可以通过构造器多态来实现。

尽管如此,Python 中 @classmethod 形式的多态可以解决这个问题。可以看一下实例。

  1. class GenericInputData(object):
  2. def read(self):
  3. raise NotImplementedError
  4. @classmethod
  5. def generate_inputs(cls, config):
  6. raise NotImplementedError
  7. class PathInputData(GenericInputData):
  8. def read(self):
  9. return open(self.path).read()
  10. @classmethod
  11. def generate_inputs(cls, config):
  12. data_dir = config['data_dir']
  13. for name in os.listdir(data_dir):
  14. yield cls(os.path.join(data_dir, name))
  15. class GenericWorker(object):
  16. def map(self):
  17. raise NotImplementedError
  18. def reduce(self, other):
  19. raise NotImplementedError
  20. @classmethod
  21. def create_workers(cls, input_class, config):
  22. workers = []
  23. for input_data in input_class.generate_inputs(config):
  24. workers.append(cls(input_data))
  25. return workers
  26. class LineCountWorker(GenericWorker):
  27. def reduce(self, other):
  28. self.result += other.result
  29. def map(self):
  30. data = self.input_data.read()
  31. self.result = data.count('\n')

对于像 InputData.read 那样的实例方法多态非常相似,只不过它会针对整个类,而不是从类中构建出来的对象。给抽象类 GenericInputData 提供一个 generate_inputs,可以接受一个含有配置参数的字典,而具体的子类则可以解读这些参数。同时给 GenericWorker 定义创建工作线程的辅助方法 create_workers,该方法中,input_class.generate_inputs 是个类级别的多态方法。在此处通过 cls 形式构造 GenericWorker 对象。

  1. def execute(workers):
  2. threads = [Thread(target=w.map) for w in workers]
  3. for thread in threads:
  4. thread.start()
  5. for thread in threads:
  6. thread.join()
  7. first, rest = workers[0], workers[1:]
  8. for worker in rest:
  9. first.reduce(worker)
  10. return first.result
  11. def mapreduce(work_class, input_class, config):
  12. workers = work_class.create_workers(input_class, config)
  13. return execute(workers)
  14. if __name__ == '__main__':
  15. print mapreduce(LineCountWorker, PathInputData, config={'data_dir': "/Users/runzhliu/workspace/python-utils/data"})

最后是重写一些辅助方法。显然此时的 mapreduce 方法可以通过传入不同的子类,而不用重写其他辅助方法来达到通用的目的。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注