- 클래스 다형성의 필요성을 알아볼 것임
- 아래와 같이 추상 인터페이스와, 그를 활용해 구현한 PathInputData, LineConterWorker 두 클래스가 있음
이해를 위해 필요한 개념
- 다형성(Polymophism)
- 하나의 객체(Object)가 여러가지 타입을 가질 수 있는 것
- 구현 방법
- 오버로딩
- 메소드 이름을 같게
- 매개변수의 개수 또는 타입을 다르게
- 오버라이딩 (상속이 선행되야함)
- 메소드 이름 같게
- 매개변수 완전 같게
- 처리를 다르게
- 함수형 인터페이스
- 오버로딩
- 이해를 위한 전체 구조 그림
- 지금부터 설명할 코드들은 상속 이후의 다형성을 설명하는 것이므로 오버라이딩임
import os
import random
import threading
class InputData:
def read(self):
raise NotImplementedError
class PathInputData(InputData):
def __init__(self, path):
super().__init__()
self.path = path
def read(self):
with open(self.path) as f:
return f.read()
class Worker:
def __init__(self, input_data):
self.input_data = input_data
self.result = None
def map(self):
raise NotImplementedError
def reduce(self, other):
raise NotImplementedError
class LineCountWorker(Worker):
def map(self):
data = self.input_data.read()
self.result = data.count('\n')
def reduce(self, other):
self.result += other.result
def write_test_files(tmpdir):
os.makedirs(tmpdir)
for i in range(100):
with open(os.path.join(tmpdir, str(i)), 'w') as f:
f.write('\n' * random.randint(0, 100))
def generate_inputs(data_dir):
for name in os.listdir(data_dir):
yield PathInputData(os.path.join(data_dir, name))
def execute(workers):
threads = [Thread(target=w.map) for w in workers]
for thread in threads: thread.start()
for thread in threads: thread.join()
first, *rest = workers
for worker in rest:
first.reduce(worker)
return first.result
def create_workers(input_list):
workers = []
for input_data in input_list:
workers.append(LineCountWorker(input_data))
return workers
from threading import Thread
def mapreduce(data_dir):
inputs = generate_inputs(data_dir)
workers = create_workers(inputs)
return execute(workers)
tmpdir = 'test_inputs'
# write_test_files(tmpdir)
result = mapreduce(tmpdir)
print(f'총 {result} 줄이 있습니다.')
총 4709 줄이 있습니다.
- generate_inputs
- 지정한 경로에 존재하는 모든 dir에 대해 PathInputData 이터레이터를 발생시키는 제너레이터 함수
- create_workers
- 전체 입력 경로 리스트에 대응되게끔 하나씩 LineCounterWorker 인스턴스를 생성
답안 코드
### GenericInputData와 PathInputData를 사용한 방법
class GenericInputData:
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):
with open(self.path) as f:
return f.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))
class GenericWorker:
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
def mapreduce(worker_class, input_class, config):
workers = worker_class.create_workers(input_class, config)
return execute(workers)
config = {'data_dir': tmpdir}
result = mapreduce(LineCountWorker, PathInputData, config)
print(f'총 {result} 줄이 있습니다.')
내 코드
### GenericInputData와 PathInputData를 사용한 방법
class GenericInputData:
def read(self):
raise NotImplementedError
@classmethod
def generate_inputs(cls, config):
raise NotImplementedError
class PathInputData(GenericInputData):
def __init__(self, path, config):
super().__init__()
self.path = path
self.data_dir = config
# self.data_dir = config['data_dir']
def read(self):
with open(self.path) as f:
return f.read()
# @classmethod
# def generate_inputs(cls, config):
def generate_inputs(self, config):
# data_dir = config['data_dir']
for name in os.listdir(self.data_dir['data_dir']):
yield PathInputData(os.path.join(self.data_dir['data_dir'], name), config)
class GenericWorker:
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
def mapreduce(worker_class, input_class):
workers = worker_class.create_workers(input_class, input_class.data_dir)
return execute(workers)
config = {'data_dir': tmpdir}
result = mapreduce(LineCountWorker, PathInputData(tmpdir, config))
print(f'총 {result} 줄이 있습니다.')
총 4709 줄이 있습니다.
아직 해결 안된 의문
- 내가 작성한 코드처럼 @classmethod가 아니라 __init__에서 config에 대한 데이터 처리를 진행하는식으로 해도 mapreduce 함수는 제너릭함.
- 답안 코드와 내 코드의 차이로 인해 발생되는 안좋은 점이 있는가..? 아직은 내가 내공이 부족해서 그런지 보이지 않음
PREVIOUS경제