はじめに
この実験では、継承を使って拡張可能なコードを書き、複数の形式でデータを出力する実用的なアプリケーションを作成する方法を学びます。また、抽象基底クラスとその具体的な実装の使い方を理解します。
さらに、クラスの選択を簡素化するためのファクトリパターンを実装します。変更するファイルは tableformat.py
です。
This tutorial is from open-source community. Access the source code
💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください
この実験では、継承を使って拡張可能なコードを書き、複数の形式でデータを出力する実用的なアプリケーションを作成する方法を学びます。また、抽象基底クラスとその具体的な実装の使い方を理解します。
さらに、クラスの選択を簡素化するためのファクトリパターンを実装します。変更するファイルは tableformat.py
です。
この実験では、Python の継承について学び、継承が拡張可能で適応性の高いコードを作成するのにどのように役立つかを見ていきます。継承は、オブジェクト指向プログラミングにおける強力な概念で、あるクラスが別のクラスから属性とメソッドを継承することができます。これにより、コードを再利用し、既存のコードの上により複雑な機能を構築することができます。
まずは、既存の print_table()
関数を見てみましょう。この関数を改良して、出力形式に関してより柔軟にする予定です。
まず、WebIDE エディタで tableformat.py
ファイルを開く必要があります。このファイルのパスは次の通りです。
/home/labex/project/tableformat.py
ファイルを開くと、print_table()
関数の現在の実装が表示されます。この関数は、表形式のデータを整形して出力するように設計されています。主に 2 つの入力を受け取ります。レコード(オブジェクト)のリストとフィールド名のリストです。これらの入力に基づいて、きれいに整形された表を出力します。
では、この関数がどのように動作するかをテストしてみましょう。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
出力は良好に見えますが、この関数には制限があります。現在、この関数はプレーンテキストという 1 つの出力形式のみをサポートしています。実際のシナリオでは、CSV、HTML などの異なる形式でデータを出力したい場合があります。
新しい出力形式をサポートするたびに print_table()
関数を変更する代わりに、継承を使用してより柔軟な解決策を作成することができます。以下にその方法を示します。
TableFormatter
クラスを定義します。このクラスには、データを整形するためのメソッドが含まれます。基本クラスは、すべてのサブクラスが基にする共通の構造と機能を提供します。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
クラスは抽象基底クラスです。抽象基底クラスはメソッドを定義するが、それらの実装を提供しないクラスです。代わりに、サブクラスがこれらの実装を提供することを期待しています。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
クラスのすべてのプロパティとメソッドを取得すると同時に、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)用の 2 つの新しいフォーマッタを作成します。これらのフォーマッタは基底クラスを継承するため、共通の振る舞いを共有しながら、独自のデータ整形方法を持つことになります。
では、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
メソッドは 1 行分のデータのリストを受け取り、同様にカンマで区切って出力します。
一方、HTMLTableFormatter
クラスは HTML の表コードを生成するために使用されます。headings
メソッドは HTML の <th>
タグを使って表のヘッダーを作成し、row
メソッドは HTML の <td>
タグを使って表の行を作成します。
これらの新しいフォーマッタがどのように動作するかをテストしてみましょう。
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
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
基底クラスで定義された同じインターフェースを共有しています。これは、継承と多態性の力を示す素晴らしい例です。基底クラスを使ってコードを書くことができ、それは自動的に任意のサブクラスで動作します。
print_table()
関数は、使用される具体的なフォーマッタについて何も知る必要はありません。基底クラスで定義されたメソッドを呼び出すだけで、提供されたフォーマッタのタイプに基づいて適切な実装が選択されます。これにより、コードがより柔軟になり、保守が容易になります。
継承を使用する際の一般的なチャレンジの 1 つは、ユーザーが具体的なフォーマッタクラスの名前を覚えておく必要があることです。特にクラスの数が増えると、これはかなり面倒なことになります。このプロセスを簡素化するために、ファクトリ関数を作成することができます。ファクトリ関数は、オブジェクトを作成して返す特殊なタイプの関数です。今回の場合、単純な形式名に基づいて適切なフォーマッタを返します。
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
データを指定された形式で出力します。
このコードを実行すると、形式名で区切られた 3 つの形式の出力が表示されるはずです。
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>
ファクトリ関数は、クラスのインスタンス化の詳細を隠すため、コードをより使いやすくします。ユーザーはフォーマッタオブジェクトを作成する方法を知る必要はなく、欲しい形式を指定するだけで済みます。
オブジェクトを作成するためにファクトリ関数を使用するこのパターンは、オブジェクト指向プログラミングにおける一般的なデザインパターンで、ファクトリパターンとして知られています。これは、クライアントコード(フォーマッタを使用するコード)と実際の実装クラス(フォーマッタクラス)の間に抽象化のレイヤーを提供します。これにより、コードがよりモジュール化され、使いやすくなります。
重要概念の復習:
抽象基底クラス: TableFormatter
クラスはインターフェースとして機能します。インターフェースは、それを実装するすべてのクラスが持たなければならないメソッドのセットを定義します。今回の場合、すべてのフォーマッタクラスは TableFormatter
クラスで定義されたメソッドを実装しなければなりません。
継承: TextTableFormatter
、CSVTableFormatter
、HTMLTableFormatter
のような具体的なフォーマッタクラスは、基底の TableFormatter
クラスを継承しています。これは、基底クラスから基本的な構造とメソッドを取得し、独自の具体的な実装を提供できることを意味します。
多態性: print_table()
関数は、必要なインターフェースを実装する任意のフォーマッタで動作することができます。これは、異なるフォーマッタオブジェクトを print_table()
関数に渡すことができ、それぞれで正しく動作することを意味します。
ファクトリパターン: create_formatter()
関数は、フォーマッタオブジェクトの作成を簡素化します。形式名に基づいて適切なオブジェクトを作成する詳細を処理するため、ユーザーはそれについて心配する必要がありません。
これらのオブジェクト指向の原則を使用することで、様々な出力形式で表形式のデータを整形するための柔軟で拡張可能なシステムを作成しました。
この実験では、オブジェクト指向プログラミングにおけるいくつかの重要な概念を学びました。拡張可能なコードを作成するための継承の使い方、インターフェースとしての抽象基底クラスの定義、および基底クラスを継承する具体的なサブクラスの実装方法を習得しました。さらに、異なる型で動作するコードを書くための多態性の使い方と、オブジェクトの作成を簡素化するためのファクトリパターンも学びました。
これらの概念は、保守可能で拡張可能なコードを作成するための強力なツールです。あなたが構築した表形式の整形システムは、継承が柔軟なシステムを作り出す方法を示しています。これらの原則を他のプログラミングタスクに適用することで、コードをモジュール化し、再利用可能にし、変化する要件に適応させることができます。