継承の実践的な使い方

PythonPythonBeginner
今すぐ練習

This tutorial is from open-source community. Access the source code

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

この実験では、継承を使って拡張可能なコードを書き、複数の形式でデータを出力する実用的なアプリケーションを作成する方法を学びます。また、抽象基底クラスとその具体的な実装の使い方を理解します。

さらに、クラスの選択を簡素化するためのファクトリパターンを実装します。変更するファイルは tableformat.py です。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python/FunctionsGroup -.-> python/function_definition("Function Definition") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/inheritance("Inheritance") python/ObjectOrientedProgrammingGroup -.-> python/polymorphism("Polymorphism") subgraph Lab Skills python/function_definition -.-> lab-132495{{"継承の実践的な使い方"}} python/classes_objects -.-> lab-132495{{"継承の実践的な使い方"}} python/inheritance -.-> lab-132495{{"継承の実践的な使い方"}} python/polymorphism -.-> lab-132495{{"継承の実践的な使い方"}} end

問題の理解

この実験では、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() 関数を変更する代わりに、継承を使用してより柔軟な解決策を作成することができます。以下にその方法を示します。

  1. 基本の TableFormatter クラスを定義します。このクラスには、データを整形するためのメソッドが含まれます。基本クラスは、すべてのサブクラスが基にする共通の構造と機能を提供します。
  2. 様々なサブクラスを作成します。各サブクラスは、異なる出力形式用に設計されます。たとえば、あるサブクラスは CSV 出力用、別のサブクラスは HTML 出力用などです。これらのサブクラスは、基本クラスのメソッドを継承し、独自の特定の機能を追加することもできます。
  3. print_table() 関数を修正して、任意のフォーマッタで動作するようにします。これは、TableFormatter クラスの異なるサブクラスを print_table() 関数に渡すことができ、適切な整形メソッドを使用できることを意味します。

このアプローチには大きな利点があります。print_table() 関数の核心機能を変更することなく、新しい出力形式を追加することができます。したがって、要件が変化し、より多くの出力形式をサポートする必要がある場合、新しいサブクラスを作成することで簡単に対応できます。

基底クラスの作成と print 関数の修正

プログラミングにおいて、継承はクラスの階層を作成することができる強力な概念です。異なる形式でデータを出力するために継承を利用するには、まず基底クラスを作成する必要があります。基底クラスは他のクラスの青写真となり、サブクラスが継承したりオーバーライドしたりできる共通のメソッドセットを定義します。

では、すべての表フォーマッタのインターフェースを定義する基底クラスを作成しましょう。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() メソッドは、各データ行を同様の方法で整形します。

では、新しいフォーマッタをテストしてみましょう。stockreadertableformat モジュールを使用して、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> タグを使って表の行を作成します。

これらの新しいフォーマッタがどのように動作するかをテストしてみましょう。

  1. まず、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
  1. 次に、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 基底クラスで定義された同じインターフェースを共有しています。これは、継承と多態性の力を示す素晴らしい例です。基底クラスを使ってコードを書くことができ、それは自動的に任意のサブクラスで動作します。

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>

ファクトリ関数は、クラスのインスタンス化の詳細を隠すため、コードをより使いやすくします。ユーザーはフォーマッタオブジェクトを作成する方法を知る必要はなく、欲しい形式を指定するだけで済みます。

オブジェクトを作成するためにファクトリ関数を使用するこのパターンは、オブジェクト指向プログラミングにおける一般的なデザインパターンで、ファクトリパターンとして知られています。これは、クライアントコード(フォーマッタを使用するコード)と実際の実装クラス(フォーマッタクラス)の間に抽象化のレイヤーを提供します。これにより、コードがよりモジュール化され、使いやすくなります。

重要概念の復習:

  1. 抽象基底クラス: TableFormatter クラスはインターフェースとして機能します。インターフェースは、それを実装するすべてのクラスが持たなければならないメソッドのセットを定義します。今回の場合、すべてのフォーマッタクラスは TableFormatter クラスで定義されたメソッドを実装しなければなりません。

  2. 継承: TextTableFormatterCSVTableFormatterHTMLTableFormatter のような具体的なフォーマッタクラスは、基底の TableFormatter クラスを継承しています。これは、基底クラスから基本的な構造とメソッドを取得し、独自の具体的な実装を提供できることを意味します。

  3. 多態性: print_table() 関数は、必要なインターフェースを実装する任意のフォーマッタで動作することができます。これは、異なるフォーマッタオブジェクトを print_table() 関数に渡すことができ、それぞれで正しく動作することを意味します。

  4. ファクトリパターン: create_formatter() 関数は、フォーマッタオブジェクトの作成を簡素化します。形式名に基づいて適切なオブジェクトを作成する詳細を処理するため、ユーザーはそれについて心配する必要がありません。

これらのオブジェクト指向の原則を使用することで、様々な出力形式で表形式のデータを整形するための柔軟で拡張可能なシステムを作成しました。

✨ 解答を確認して練習

まとめ

この実験では、オブジェクト指向プログラミングにおけるいくつかの重要な概念を学びました。拡張可能なコードを作成するための継承の使い方、インターフェースとしての抽象基底クラスの定義、および基底クラスを継承する具体的なサブクラスの実装方法を習得しました。さらに、異なる型で動作するコードを書くための多態性の使い方と、オブジェクトの作成を簡素化するためのファクトリパターンも学びました。

これらの概念は、保守可能で拡張可能なコードを作成するための強力なツールです。あなたが構築した表形式の整形システムは、継承が柔軟なシステムを作り出す方法を示しています。これらの原則を他のプログラミングタスクに適用することで、コードをモジュール化し、再利用可能にし、変化する要件に適応させることができます。