소개
이 랩에서는 상속을 사용하여 확장 가능한 코드를 작성하고 여러 형식으로 데이터를 출력하는 실용적인 애플리케이션을 만드는 방법을 배우게 됩니다. 또한 추상 기본 클래스 (abstract base class) 와 구체적인 구현 (concrete implementation) 을 사용하는 방법을 이해하게 됩니다.
더 나아가, 클래스 선택을 단순화하기 위해 팩토리 패턴 (factory pattern) 을 구현할 것입니다. 수정할 파일은 tableformat.py입니다.
문제 이해
이 랩에서는 Python 의 상속에 대해 배우고, 상속이 확장 가능하고 적응 가능한 코드를 만드는 데 어떻게 도움이 되는지 알아볼 것입니다. 상속은 객체 지향 프로그래밍 (object-oriented programming) 에서 클래스가 다른 클래스에서 속성 (attribute) 과 메서드 (method) 를 상속받을 수 있는 강력한 개념입니다. 이를 통해 코드를 재사용하고 기존 코드 위에 더 복잡한 기능을 구축할 수 있습니다.
먼저, 기존의 print_table() 함수를 살펴보겠습니다. 이 함수는 출력 형식 측면에서 더 유연하게 만들기 위해 개선할 것입니다.
먼저, WebIDE 편집기에서 tableformat.py 파일을 열어야 합니다. 이 파일의 경로는 다음과 같습니다.
/home/labex/project/tableformat.py
파일을 열면 print_table() 함수의 현재 구현을 볼 수 있습니다. 이 함수는 표 형식 데이터를 형식화하고 인쇄하도록 설계되었습니다. 두 가지 주요 입력을 받습니다. 레코드 목록 (객체) 과 필드 이름 목록입니다. 이러한 입력을 기반으로, 잘 형식화된 테이블을 인쇄합니다.
이제 이 함수가 어떻게 작동하는지 테스트해 보겠습니다. WebIDE 에서 터미널을 열고 다음 Python 명령을 실행합니다. 이 명령은 필요한 모듈을 가져오고, CSV 파일에서 데이터를 읽은 다음, print_table() 함수를 사용하여 데이터를 표시합니다.
import stock
import reader
import tableformat
portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
tableformat.print_table(portfolio, ['name', 'shares', 'price'])
이 명령을 실행하면 다음과 같은 출력이 표시됩니다.
name shares price
---------- ---------- ----------
AA 100 32.2
IBM 50 91.1
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.1
IBM 100 70.44
출력은 보기 좋지만, 이 함수에는 한계가 있습니다. 현재는 일반 텍스트라는 하나의 출력 형식만 지원합니다. 실제 시나리오에서는 CSV, HTML 등과 같은 다양한 형식으로 데이터를 출력하고 싶을 수 있습니다.
새로운 출력 형식을 지원할 때마다 print_table() 함수를 변경하는 대신, 상속을 사용하여 더 유연한 솔루션을 만들 수 있습니다. 방법은 다음과 같습니다.
- 기본
TableFormatter클래스를 정의합니다. 이 클래스에는 데이터를 형식화하는 데 사용되는 메서드가 있습니다. 기본 클래스는 모든 서브클래스가 구축할 수 있는 공통 구조와 기능을 제공합니다. - 다양한 서브클래스를 만듭니다. 각 서브클래스는 다른 출력 형식을 위해 설계됩니다. 예를 들어, 하나의 서브클래스는 CSV 출력을 위한 것이고, 다른 서브클래스는 HTML 출력을 위한 것입니다. 이러한 서브클래스는 기본 클래스에서 메서드를 상속받으며 자체적인 특정 기능을 추가할 수도 있습니다.
print_table()함수를 수정하여 모든 형식 지정자와 함께 작동하도록 합니다. 즉,TableFormatter클래스의 서로 다른 서브클래스를print_table()함수에 전달할 수 있으며, 적절한 형식 지정 메서드를 사용할 수 있습니다.
이 접근 방식에는 큰 장점이 있습니다. print_table() 함수의 핵심 기능을 변경하지 않고도 새로운 출력 형식을 추가할 수 있습니다. 따라서 요구 사항이 변경되어 더 많은 출력 형식을 지원해야 하는 경우, 새로운 서브클래스를 생성하여 쉽게 수행할 수 있습니다.
기본 클래스 생성 및 인쇄 함수 수정
프로그래밍에서 상속은 클래스 계층 구조를 만들 수 있게 해주는 강력한 개념입니다. 다양한 형식으로 데이터를 출력하기 위해 상속을 사용하려면 먼저 기본 클래스를 만들어야 합니다. 기본 클래스는 다른 클래스를 위한 청사진 역할을 하며, 서브클래스가 상속하고 재정의할 수 있는 공통 메서드 집합을 정의합니다.
이제 모든 테이블 형식 지정자에 대한 인터페이스를 정의할 기본 클래스를 만들어 보겠습니다. WebIDE 에서 tableformat.py 파일을 열고 파일 맨 위에 다음 코드를 추가합니다.
class TableFormatter:
"""
Base class for all table formatters.
This class defines the interface that all formatters must implement.
"""
def headings(self, headers):
"""
Generate the table headings.
"""
raise NotImplementedError()
def row(self, rowdata):
"""
Generate a single row of table data.
"""
raise NotImplementedError()
TableFormatter 클래스는 추상 기본 클래스 (abstract base class) 입니다. 추상 기본 클래스는 메서드를 정의하지만 해당 메서드에 대한 구현을 제공하지 않는 클래스입니다. 대신, 서브클래스가 이러한 구현을 제공할 것으로 예상합니다. NotImplementedError 예외는 이러한 메서드가 서브클래스에 의해 재정의되어야 함을 나타내는 데 사용됩니다. 서브클래스가 이러한 메서드를 재정의하지 않고 사용하려고 하면 오류가 발생합니다.
다음으로, print_table() 함수를 수정하여 TableFormatter 클래스를 사용해야 합니다. print_table() 함수는 객체 목록에서 테이블 데이터를 인쇄하는 데 사용됩니다. TableFormatter 클래스를 사용하도록 수정하면 함수를 더 유연하게 만들고 다양한 테이블 형식으로 작업할 수 있습니다.
기존의 print_table() 함수를 다음 코드로 바꿉니다.
def print_table(records, fields, formatter):
"""
Print a table of data from a list of objects using the specified formatter.
Args:
records: A list of objects
fields: A list of field names
formatter: A TableFormatter object
"""
formatter.headings(fields)
for r in records:
rowdata = [getattr(r, fieldname) for fieldname in fields]
formatter.row(rowdata)
여기서 핵심적인 변경 사항은 print_table()이 이제 formatter 매개변수를 받는다는 것입니다. 이 매개변수는 TableFormatter 또는 서브클래스의 인스턴스여야 합니다. 즉, 서로 다른 테이블 형식 지정자를 print_table() 함수에 전달할 수 있으며, 함수는 적절한 형식 지정자를 사용하여 테이블을 인쇄합니다. 이 함수는 headings() 및 row() 메서드를 호출하여 형식 지정자 객체에 형식 지정 책임을 위임합니다.
기본 TableFormatter 클래스를 사용해 보면서 변경 사항을 테스트해 보겠습니다.
import stock
import reader
import tableformat
portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
formatter = tableformat.TableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)
이 코드를 실행하면 오류가 표시됩니다.
Traceback (most recent call last):
...
NotImplementedError
이 오류는 추상 기본 클래스를 직접 사용하려고 하지만 메서드에 대한 구현을 제공하지 않기 때문에 발생합니다. TableFormatter 클래스의 headings() 및 row() 메서드는 NotImplementedError를 발생시키므로 Python 은 이러한 메서드가 호출될 때 무엇을 해야 할지 알 수 없습니다. 다음 단계에서는 이러한 구현을 제공하는 구체적인 서브클래스를 만들 것입니다.
구체적인 형식 지정자 구현
이제 추상 기본 클래스를 정의하고 print_table() 함수를 업데이트했으므로 구체적인 형식 지정자 클래스를 만들 차례입니다. 구체적인 형식 지정자 클래스는 추상 기본 클래스에 정의된 메서드에 대한 실제 구현을 제공하는 클래스입니다. 이 경우, 데이터를 일반 텍스트 테이블로 형식화할 수 있는 클래스를 만들 것입니다.
tableformat.py 파일에 다음 클래스를 추가해 보겠습니다. 이 클래스는 TableFormatter 추상 기본 클래스에서 상속받아 headings() 및 row() 메서드를 구현합니다.
class TextTableFormatter(TableFormatter):
"""
Formatter that generates a plain - text table.
"""
def headings(self, headers):
"""
Generate plain - text table headings.
"""
print(' '.join('%10s' % h for h in headers))
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
"""
Generate a plain - text table row.
"""
print(' '.join('%10s' % d for d in rowdata))
TextTableFormatter 클래스는 TableFormatter에서 상속받습니다. 즉, TableFormatter 클래스에서 모든 속성 (property) 과 메서드를 가져오지만, headings() 및 row() 메서드에 대한 자체 구현도 제공합니다. 이러한 메서드는 각각 테이블 헤더와 행을 형식화하는 역할을 합니다. headings() 메서드는 헤더를 보기 좋게 형식화하여 인쇄한 다음, 헤더와 데이터를 구분하기 위해 대시 줄을 인쇄합니다. row() 메서드는 각 데이터 행을 유사한 방식으로 형식화합니다.
이제 새로운 형식 지정자를 테스트해 보겠습니다. stock, reader, tableformat 모듈을 사용하여 CSV 파일에서 데이터를 읽고 새로운 형식 지정자를 사용하여 인쇄합니다.
import stock
import reader
import tableformat
portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
formatter = tableformat.TextTableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)
이 코드를 실행하면 이전과 동일한 출력이 표시됩니다. 이는 새로운 형식 지정자가 원래의 print_table() 함수와 동일한 일반 텍스트 테이블을 생성하도록 설계되었기 때문입니다.
name shares price
---------- ---------- ----------
AA 100 32.2
IBM 50 91.1
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.1
IBM 100 70.44
이 출력은 TextTableFormatter가 올바르게 작동하고 있음을 확인합니다. 이 접근 방식을 사용하는 장점은 코드를 더 모듈화하고 확장 가능하게 만들었다는 것입니다. 형식 지정 로직을 별도의 클래스 계층 구조로 분리함으로써 새로운 출력 형식을 쉽게 추가할 수 있습니다. print_table() 함수를 수정하지 않고 TableFormatter의 새로운 서브클래스를 만들기만 하면 됩니다. 이러한 방식으로, 향후 CSV 또는 HTML 과 같은 다양한 출력 형식을 지원할 수 있습니다.
추가 형식 지정자 생성
프로그래밍에서 상속은 기존 클래스를 기반으로 새로운 클래스를 만들 수 있게 해주는 강력한 개념입니다. 이는 코드 재사용을 돕고 프로그램을 더 확장 가능하게 만듭니다. 이 실험의 이 부분에서는 상속을 사용하여 CSV 및 HTML 과 같은 서로 다른 출력 형식에 대한 두 개의 새로운 형식 지정자를 만들 것입니다. 이러한 형식 지정자는 기본 클래스에서 상속받으므로 일부 공통 동작을 공유하면서 고유한 방식으로 데이터를 형식화합니다.
이제 tableformat.py 파일에 다음 클래스를 추가해 보겠습니다. 이러한 클래스는 각각 CSV 및 HTML 형식으로 데이터를 형식화하는 방법을 정의합니다.
class CSVTableFormatter(TableFormatter):
"""
Formatter that generates CSV formatted data.
"""
def headings(self, headers):
"""
Generate CSV headers.
"""
print(','.join(headers))
def row(self, rowdata):
"""
Generate a CSV data row.
"""
print(','.join(str(d) for d in rowdata))
class HTMLTableFormatter(TableFormatter):
"""
Formatter that generates HTML table code.
"""
def headings(self, headers):
"""
Generate HTML table headers.
"""
print('<tr>', end=' ')
for header in headers:
print(f'<th>{header}</th>', end=' ')
print('</tr>')
def row(self, rowdata):
"""
Generate an HTML table row.
"""
print('<tr>', end=' ')
for data in rowdata:
print(f'<td>{data}</td>', end=' ')
print('</tr>')
CSVTableFormatter 클래스는 CSV(Comma-Separated Values, 쉼표로 구분된 값) 형식으로 데이터를 형식화하도록 설계되었습니다. headings 메서드는 헤더 목록을 가져와 쉼표로 구분하여 인쇄합니다. row 메서드는 단일 행에 대한 데이터 목록을 가져와 쉼표로 구분하여 인쇄합니다.
반면에 HTMLTableFormatter 클래스는 HTML 테이블 코드를 생성하는 데 사용됩니다. headings 메서드는 HTML <th> 태그를 사용하여 테이블 헤더를 만들고, row 메서드는 HTML <td> 태그를 사용하여 테이블 행을 만듭니다.
이러한 새로운 형식 지정자가 어떻게 작동하는지 테스트해 보겠습니다.
- 먼저 CSV 형식 지정자를 테스트해 보겠습니다.
import stock
import reader
import tableformat
portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
formatter = tableformat.CSVTableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)
이 코드에서는 먼저 필요한 모듈을 가져옵니다. 그런 다음 portfolio.csv라는 CSV 파일에서 데이터를 읽고 Stock 클래스의 인스턴스를 만듭니다. 다음으로, CSVTableFormatter 클래스의 인스턴스를 만듭니다. 마지막으로, print_table 함수를 사용하여 포트폴리오 데이터를 CSV 형식으로 인쇄합니다.
다음과 같은 CSV 형식의 출력이 표시됩니다.
name,shares,price
AA,100,32.2
IBM,50,91.1
CAT,150,83.44
MSFT,200,51.23
GE,95,40.37
MSFT,50,65.1
IBM,100,70.44
- 이제 HTML 형식 지정자를 테스트해 보겠습니다.
formatter = tableformat.HTMLTableFormatter()
tableformat.print_table(portfolio, ['name', 'shares', 'price'], formatter)
여기서는 HTMLTableFormatter 클래스의 인스턴스를 만들고 print_table 함수를 다시 사용하여 포트폴리오 데이터를 HTML 형식으로 인쇄합니다.
다음과 같은 HTML 형식의 출력이 표시됩니다.
<tr> <th>name</th> <th>shares</th> <th>price</th> </tr>
<tr> <td>AA</td> <td>100</td> <td>32.2</td> </tr>
<tr> <td>IBM</td> <td>50</td> <td>91.1</td> </tr>
<tr> <td>CAT</td> <td>150</td> <td>83.44</td> </tr>
<tr> <td>MSFT</td> <td>200</td> <td>51.23</td> </tr>
<tr> <td>GE</td> <td>95</td> <td>40.37</td> </tr>
<tr> <td>MSFT</td> <td>50</td> <td>65.1</td> </tr>
<tr> <td>IBM</td> <td>100</td> <td>70.44</td> </tr>
보시다시피, 각 형식 지정자는 서로 다른 형식으로 출력을 생성하지만 모두 TableFormatter 기본 클래스에 의해 정의된 동일한 인터페이스를 공유합니다. 이는 상속과 다형성 (polymorphism) 의 강력함을 보여주는 훌륭한 예입니다. 기본 클래스로 작동하는 코드를 작성할 수 있으며, 이는 자동으로 모든 서브클래스에서도 작동합니다.
print_table() 함수는 사용 중인 특정 형식 지정자에 대해 아무것도 알 필요가 없습니다. 기본 클래스에 정의된 메서드를 호출하기만 하면 제공된 형식 지정자의 유형에 따라 적절한 구현이 선택됩니다. 이렇게 하면 코드가 더 유연해지고 유지 관리가 쉬워집니다.
팩토리 함수 생성
상속을 사용할 때 흔히 발생하는 문제 중 하나는 사용자가 특정 형식 지정자 클래스의 이름을 기억해야 한다는 것입니다. 특히 클래스 수가 증가함에 따라 이는 상당히 번거로울 수 있습니다. 이 프로세스를 단순화하기 위해 팩토리 함수를 만들 수 있습니다. 팩토리 함수는 객체를 생성하고 반환하는 특수한 유형의 함수입니다. 이 경우, 간단한 형식 이름을 기반으로 적절한 형식 지정자를 반환합니다.
tableformat.py 파일에 다음 함수를 추가해 보겠습니다. 이 함수는 형식 이름을 인수로 받아 해당 형식 지정자 객체를 반환합니다.
def create_formatter(format_name):
"""
Create a formatter of the specified type.
Args:
format_name: Name of the formatter ('text', 'csv', 'html')
Returns:
A TableFormatter object
Raises:
ValueError: If format_name is not recognized
"""
if format_name == 'text':
return TextTableFormatter()
elif format_name == 'csv':
return CSVTableFormatter()
elif format_name == 'html':
return HTMLTableFormatter()
else:
raise ValueError(f'Unknown format {format_name}')
create_formatter() 함수는 팩토리 함수입니다. 제공한 format_name 인수를 확인합니다. 'text'인 경우 TextTableFormatter 객체를 생성하여 반환합니다. 'csv'인 경우 CSVTableFormatter 객체를 반환하고, 'html'인 경우 HTMLTableFormatter 객체를 반환합니다. 형식 이름이 인식되지 않으면 ValueError를 발생시킵니다. 이러한 방식으로 사용자는 특정 클래스 이름을 알 필요 없이 간단한 이름만 제공하여 형식 지정자를 쉽게 선택할 수 있습니다.
이제 팩토리 함수를 테스트해 보겠습니다. CSV 파일에서 데이터를 읽고 다양한 형식으로 인쇄하기 위해 기존 함수와 클래스를 사용합니다.
import stock
import reader
from tableformat import create_formatter, print_table
portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
## Test with text formatter
formatter = create_formatter('text')
print("\nText Format:")
print_table(portfolio, ['name', 'shares', 'price'], formatter)
## Test with CSV formatter
formatter = create_formatter('csv')
print("\nCSV Format:")
print_table(portfolio, ['name', 'shares', 'price'], formatter)
## Test with HTML formatter
formatter = create_formatter('html')
print("\nHTML Format:")
print_table(portfolio, ['name', 'shares', 'price'], formatter)
이 코드에서는 먼저 필요한 모듈과 함수를 가져옵니다. 그런 다음 portfolio.csv 파일에서 데이터를 읽고 portfolio 객체를 만듭니다. 그 후, 'text', 'csv', 'html'과 같은 다양한 형식 이름으로 create_formatter() 함수를 테스트합니다. 각 형식에 대해 형식 지정자 객체를 만들고 형식 이름을 인쇄한 다음, print_table() 함수를 사용하여 지정된 형식으로 portfolio 데이터를 인쇄합니다.
이 코드를 실행하면 형식 이름으로 구분된 세 가지 형식의 출력이 표시됩니다.
Text Format:
name shares price
---------- ---------- ----------
AA 100 32.2
IBM 50 91.1
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.1
IBM 100 70.44
CSV Format:
name,shares,price
AA,100,32.2
IBM,50,91.1
CAT,150,83.44
MSFT,200,51.23
GE,95,40.37
MSFT,50,65.1
IBM,100,70.44
HTML Format:
<tr> <th>name</th> <th>shares</th> <th>price</th> </tr>
<tr> <td>AA</td> <td>100</td> <td>32.2</td> </tr>
<tr> <td>IBM</td> <td>50</td> <td>91.1</td> </tr>
<tr> <td>CAT</td> <td>150</td> <td>83.44</td> </tr>
<tr> <td>MSFT</td> <td>200</td> <td>51.23</td> </tr>
<tr> <td>GE</td> <td>95</td> <td>40.37</td> </tr>
<tr> <td>MSFT</td> <td>50</td> <td>65.1</td> </tr>
<tr> <td>IBM</td> <td>100</td> <td>70.44</td> </tr>
팩토리 함수는 클래스 인스턴스화의 세부 정보를 숨기기 때문에 코드를 더 사용자 친화적으로 만듭니다. 사용자는 형식 지정자 객체를 만드는 방법을 알 필요가 없으며 원하는 형식을 지정하기만 하면 됩니다.
객체를 생성하기 위해 팩토리 함수를 사용하는 이 패턴은 객체 지향 프로그래밍에서 흔히 사용되는 디자인 패턴으로, 팩토리 패턴 (Factory Pattern) 이라고 합니다. 이는 클라이언트 코드 (형식 지정자를 사용하는 코드) 와 실제 구현 클래스 (형식 지정자 클래스) 사이에 추상화 계층을 제공합니다. 이렇게 하면 코드가 더 모듈화되고 사용하기 쉬워집니다.
핵심 개념 검토:
추상 기본 클래스 (Abstract Base Class):
TableFormatter클래스는 인터페이스 역할을 합니다. 인터페이스는 이를 구현하는 모든 클래스가 가져야 하는 메서드 집합을 정의합니다. 이 경우, 모든 형식 지정자 클래스는TableFormatter클래스에 정의된 메서드를 구현해야 합니다.상속 (Inheritance):
TextTableFormatter,CSVTableFormatter,HTMLTableFormatter와 같은 구체적인 형식 지정자 클래스는 기본TableFormatter클래스에서 상속받습니다. 즉, 기본 클래스에서 기본 구조와 메서드를 가져오고 자체적인 특정 구현을 제공할 수 있습니다.다형성 (Polymorphism):
print_table()함수는 필요한 인터페이스를 구현하는 모든 형식 지정자와 함께 작동할 수 있습니다. 즉,print_table()함수에 서로 다른 형식 지정자 객체를 전달할 수 있으며 각 객체에 대해 올바르게 작동합니다.팩토리 패턴 (Factory Pattern):
create_formatter()함수는 형식 지정자 객체 생성을 단순화합니다. 형식 이름에 따라 올바른 객체를 생성하는 세부 사항을 처리하므로 사용자는 이에 대해 걱정할 필요가 없습니다.
이러한 객체 지향 원칙을 사용하여 다양한 출력 형식으로 표 형식 데이터를 형식화하기 위한 유연하고 확장 가능한 시스템을 만들었습니다.
요약
이 랩에서는 객체 지향 프로그래밍의 몇 가지 핵심 개념을 배웠습니다. 확장 가능한 코드를 생성하기 위해 상속을 사용하는 방법, 인터페이스로 추상 기본 클래스를 정의하는 방법, 기본 클래스에서 상속받는 구체적인 서브클래스를 구현하는 방법을 익혔습니다. 또한, 다양한 유형으로 작동하는 코드를 작성하기 위해 다형성을 사용하는 방법과 객체 생성을 단순화하기 위한 팩토리 패턴을 배우셨습니다.
이러한 개념은 유지 관리 가능하고 확장 가능한 코드를 생성하기 위한 강력한 도구입니다. 여러분이 구축한 테이블 형식 시스템은 상속이 어떻게 유연한 시스템을 만들 수 있는지 보여줍니다. 이러한 원칙을 다른 프로그래밍 작업에 적용하여 코드를 모듈화하고, 재사용 가능하며, 변화하는 요구 사항에 적응할 수 있습니다.