ミックスインクラスと協調的継承

PythonPythonBeginner
今すぐ練習

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

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

はじめに

この実験では、ミックスインクラス(mixin classes)について学び、コードの再利用性を高める際におけるその役割を理解します。既存のコードを変更することなく、ミックスインを実装してクラスの機能を拡張する方法を学びます。

また、Python の協調的継承(cooperative inheritance)技術を習得します。実験中に 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/FunctionsGroup -.-> python/default_arguments("Default Arguments") python/FunctionsGroup -.-> python/keyword_arguments("Keyword Arguments") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/inheritance("Inheritance") subgraph Lab Skills python/function_definition -.-> lab-132498{{"ミックスインクラスと協調的継承"}} python/default_arguments -.-> lab-132498{{"ミックスインクラスと協調的継承"}} python/keyword_arguments -.-> lab-132498{{"ミックスインクラスと協調的継承"}} python/classes_objects -.-> lab-132498{{"ミックスインクラスと協調的継承"}} python/inheritance -.-> lab-132498{{"ミックスインクラスと協調的継承"}} end

列フォーマットの問題の理解

このステップでは、現在の表フォーマット実装における制限について調べます。また、この問題のいくつかの解決策を検討します。

まず、やることを理解しましょう。VSCode エディタを開き、プロジェクトディレクトリ内の tableformat.py ファイルを見ます。このファイルは、表形式のデータをテキスト、CSV、HTML などのさまざまな形式でフォーマットするコードが含まれているため重要です。

ファイルを開くには、ターミナルで以下のコマンドを使用します。cd コマンドはディレクトリをプロジェクトディレクトリに変更し、code コマンドは VSCode で tableformat.py ファイルを開きます。

cd ~/project
code tableformat.py

ファイルを開くと、いくつかのクラスが定義されていることに気づくでしょう。これらのクラスは、表データのフォーマットにおいて異なる役割を果たします。

  • TableFormatter:これは抽象基底クラス(abstract base class)です。表の見出しと行をフォーマットするためのメソッドがあります。他のフォーマッタクラスの青写真と考えてください。
  • TextTableFormatter:このクラスは、表を平文形式で出力するために使用されます。
  • CSVTableFormatter:このクラスは、表データを CSV(Comma-Separated Values)形式でフォーマットする責任があります。
  • HTMLTableFormatter:このクラスは、表データを HTML 形式でフォーマットします。

また、ファイルには print_table() 関数もあります。この関数は、先ほど述べたフォーマッタクラスを使用して表形式のデータを表示します。

では、いくつかの Python コードを実行して、これらのクラスがどのように動作するかを見てみましょう。ターミナルを開き、Python セッションを開始します。以下のコードは、tableformat.py ファイルから必要な関数とクラスをインポートし、TextTableFormatter オブジェクトを作成し、print_table() 関数を使用してポートフォリオデータを表示します。

python3 -c "
from tableformat import print_table, TextTableFormatter, portfolio
formatter = TextTableFormatter()
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"

コードを実行すると、以下のような出力が表示されるはずです。

      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

では、問題を見つけましょう。price 列の値が一貫してフォーマットされていないことに注意してください。一部の値は 32.2 のように小数点以下 1 桁で、他の値は 51.23 のように小数点以下 2 桁です。金融データでは、通常、フォーマットを一貫させたいと思います。

以下は、出力を望む形式です。

      name     shares      price
---------- ---------- ----------
        AA        100      32.20
       IBM         50      91.10
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50      65.10
       IBM        100      70.44

これを修正する 1 つの方法は、print_table() 関数を変更してフォーマット指定を受け取るようにすることです。以下のコードは、これを行う方法を示しています。追加の formats パラメータを受け取る新しい print_table() 関数を定義します。関数内で、これらのフォーマット指定を使用して行内の各値をフォーマットします。

python3 -c "
from tableformat import TextTableFormatter, portfolio

def print_table(records, fields, formats, formatter):
    formatter.headings(fields)
    for r in records:
        rowdata = [(fmt % getattr(r, fieldname))
             for fieldname, fmt in zip(fields, formats)]
        formatter.row(rowdata)

formatter = TextTableFormatter()
print_table(portfolio,
            ['name','shares','price'],
            ['%s','%d','%0.2f'],
            formatter)
"

この解決策は機能しますが、欠点があります。関数のインターフェースを変更すると、古いバージョンの print_table() 関数を使用している既存のコードが動作しなくなる可能性があります。

別のアプローチは、サブクラス化によってカスタムフォーマッタを作成することです。TextTableFormatter を継承する新しいクラスを作成し、row() メソッドをオーバーライドして、望むフォーマットを適用することができます。

python3 -c "
from tableformat import TextTableFormatter, print_table, portfolio

class PortfolioFormatter(TextTableFormatter):
    def row(self, rowdata):
        formats = ['%s','%d','%0.2f']
        rowdata = [(fmt % d) for fmt, d in zip(formats, rowdata)]
        super().row(rowdata)

formatter = PortfolioFormatter()
print_table(portfolio, ['name','shares','price'], formatter)
"

この解決策も機能しますが、あまり便利ではありません。異なるフォーマットが必要になるたびに、新しいクラスを作成する必要があります。また、今回の場合 TextTableFormatter からサブクラス化しているため、特定のフォーマッタタイプに制限されます。

次のステップでは、ミックスインクラス(mixin classes)を使用したよりエレガントな解決策を探ります。

フォーマット用のミックスインクラスの実装

このステップでは、ミックスインクラス(mixin classes)について学びます。ミックスインクラスは Python で非常に便利なテクニックです。これを使うと、クラスの元のコードを変更することなく、追加の機能をクラスに追加できます。これは、コードをモジュール化して管理しやすくするのに役立つため、非常に優れています。

ミックスインクラスとは何か?

ミックスインは特殊なタイプのクラスです。その主な目的は、他のクラスが継承できる機能を提供することです。ただし、ミックスインは単独で使用することを想定していません。直接ミックスインクラスのインスタンスを作成することはありません。代わりに、特定の機能を他のクラスに制御可能かつ予測可能な方法で追加する手段として使用します。これは多重継承(multiple inheritance)の一種で、クラスが複数の親クラスから継承することができます。

では、tableformat.py ファイルに 2 つのミックスインクラスを実装しましょう。まず、エディタでファイルを開きます。ターミナルで以下のコマンドを実行することで、これを行うことができます。

cd ~/project
code tableformat.py

ファイルが開いたら、既存の関数の前で、ファイルの末尾に以下のクラス定義を追加します。

class ColumnFormatMixin:
    formats = []
    def row(self, rowdata):
        rowdata = [(fmt % d) for fmt, d in zip(self.formats, rowdata)]
        super().row(rowdata)

この ColumnFormatMixin クラスは、列フォーマット機能を提供します。formats クラス変数は、フォーマットコードを保持するリストです。これらのコードは、各列のデータをフォーマットするために使用されます。row() メソッドは行データを受け取り、行内の各要素にフォーマットコードを適用し、その後 super().row(rowdata) を使用してフォーマットされた行データを親クラスに渡します。

次に、表のヘッダーを大文字で表示する別のミックスインクラスを追加します。

class UpperHeadersMixin:
    def headings(self, headers):
        super().headings([h.upper() for h in headers])

この UpperHeadersMixin クラスは、ヘッダーテキストを大文字に変換します。ヘッダーのリストを受け取り、各ヘッダーを大文字に変換し、その後 super().headings() を使用して変更されたヘッダーを親クラスの headings() メソッドに渡します。

ミックスインクラスの使用

新しいミックスインクラスをテストしましょう。いくつかの Python コードを実行して、それらがどのように動作するかを確認します。

python3 -c "
from tableformat import TextTableFormatter, ColumnFormatMixin, portfolio, print_table

class PortfolioFormatter(ColumnFormatMixin, TextTableFormatter):
    formats = ['%s', '%d', '%0.2f']

formatter = PortfolioFormatter()
print_table(portfolio, ['name','shares','price'], formatter)
"

このコードを実行すると、きれいにフォーマットされた出力が表示されるはずです。ColumnFormatMixin が提供するフォーマットにより、価格列は小数点以下の桁数が一致します。

      name     shares      price
---------- ---------- ----------
        AA        100      32.20
       IBM         50      91.10
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50      65.10
       IBM        100      70.44

では、UpperHeadersMixin を試してみましょう。以下のコードを実行します。

python3 -c "
from tableformat import TextTableFormatter, UpperHeadersMixin, portfolio, print_table

class PortfolioFormatter(UpperHeadersMixin, TextTableFormatter):
    pass

formatter = PortfolioFormatter()
print_table(portfolio, ['name','shares','price'], formatter)
"

このコードは、ヘッダーを大文字で表示するはずです。

      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

協調的継承の理解

ミックスインクラスでは、super().method() を使用していることに注意してください。これは「協調的継承(cooperative inheritance)」と呼ばれます。協調的継承では、継承チェーン内の各クラスが協力して動作します。クラスが super().method() を呼び出すとき、それはチェーン内の次のクラスにタスクの一部を実行するように依頼しています。このように、クラスのチェーンはそれぞれ独自の動作を全体のプロセスに追加することができます。

継承の順序は非常に重要です。class PortfolioFormatter(ColumnFormatMixin, TextTableFormatter) を定義するとき、Python はまず ColumnFormatMixin でメソッドを探し、次に TextTableFormatter で探します。したがって、ColumnFormatMixinsuper().row() が呼び出されるとき、それは TextTableFormatter.row() を指します。

両方のミックスインを組み合わせることもできます。以下のコードを実行します。

python3 -c "
from tableformat import TextTableFormatter, ColumnFormatMixin, UpperHeadersMixin, portfolio, print_table

class PortfolioFormatter(ColumnFormatMixin, UpperHeadersMixin, TextTableFormatter):
    formats = ['%s', '%d', '%0.2f']

formatter = PortfolioFormatter()
print_table(portfolio, ['name','shares','price'], formatter)
"

このコードは、大文字のヘッダーとフォーマットされた数値の両方を提供します。

      NAME     SHARES      PRICE
---------- ---------- ----------
        AA        100      32.20
       IBM         50      91.10
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50      65.10
       IBM        100      70.44

次のステップでは、create_formatter() 関数を拡張することで、これらのミックスインを使いやすくします。

✨ 解答を確認して練習

ミックスイン用の使いやすい API の作成

ミックスインは Python の強力な機能ですが、多重継承を伴うため、初心者には少し扱いにくい場合があります。多重継承はかなり複雑になることがあります。このステップでは、create_formatter() 関数を改良することで、ユーザーにとって使いやすいものにします。これにより、ユーザーは多重継承の詳細にあまり気を使う必要がなくなります。

まず、tableformat.py ファイルを開く必要があります。ターミナルで以下のコマンドを実行することで、これを行うことができます。cd コマンドはディレクトリをプロジェクトフォルダに変更し、code コマンドはコードエディタで tableformat.py ファイルを開きます。

cd ~/project
code tableformat.py

ファイルが開いたら、create_formatter() 関数を見つけます。現在、この関数は次のようになっています。

def create_formatter(name):
    """
    Create an appropriate formatter based on the name.
    """
    if name == 'text':
        return TextTableFormatter()
    elif name == 'csv':
        return CSVTableFormatter()
    elif name == 'html':
        return HTMLTableFormatter()
    else:
        raise RuntimeError(f'Unknown format {name}')

この関数は名前を引数として受け取り、対応するフォーマッタを返します。しかし、もっと柔軟にするために、ミックスイン用のオプション引数を受け取れるように変更します。

既存の create_formatter() 関数を以下の拡張版に置き換えます。この新しい関数では、列フォーマットとヘッダーを大文字に変換するかどうかを指定できます。

def create_formatter(name, column_formats=None, upper_headers=False):
    """
    Create a formatter with optional enhancements.

    Parameters:
    name : str
        Name of the formatter ('text', 'csv', 'html')
    column_formats : list, optional
        List of format strings for column formatting
    upper_headers : bool, optional
        Whether to convert headers to uppercase
    """
    if name == 'text':
        formatter_cls = TextTableFormatter
    elif name == 'csv':
        formatter_cls = CSVTableFormatter
    elif name == 'html':
        formatter_cls = HTMLTableFormatter
    else:
        raise RuntimeError(f'Unknown format {name}')

    ## Apply mixins if requested
    if column_formats and upper_headers:
        class CustomFormatter(ColumnFormatMixin, UpperHeadersMixin, formatter_cls):
            formats = column_formats
        return CustomFormatter()
    elif column_formats:
        class CustomFormatter(ColumnFormatMixin, formatter_cls):
            formats = column_formats
        return CustomFormatter()
    elif upper_headers:
        class CustomFormatter(UpperHeadersMixin, formatter_cls):
            pass
        return CustomFormatter()
    else:
        return formatter_cls()

この拡張された関数は、まず name 引数に基づいて基本のフォーマッタクラスを決定します。次に、column_formatsupper_headers が指定されているかどうかに応じて、適切なミックスインを含むカスタムフォーマッタクラスを作成します。最後に、カスタムフォーマッタクラスのインスタンスを返します。

では、さまざまなオプションの組み合わせで拡張された関数をテストしましょう。

まず、列フォーマットを使用してみましょう。ターミナルで以下のコマンドを実行します。このコマンドは、tableformat.py ファイルから必要な関数とデータをインポートし、列フォーマットを持つフォーマッタを作成し、そのフォーマッタを使用して表を印刷します。

python3 -c "
from tableformat import create_formatter, portfolio, print_table

formatter = create_formatter('text', column_formats=['%s', '%d', '%0.2f'])
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"

列がフォーマットされた表が表示されるはずです。出力は次のようになります。

      name     shares      price
---------- ---------- ----------
        AA        100      32.20
       IBM         50      91.10
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50      65.10
       IBM        100      70.44

次に、大文字のヘッダーを使用してみましょう。以下のコマンドを実行します。

python3 -c "
from tableformat import create_formatter, portfolio, print_table

formatter = create_formatter('text', upper_headers=True)
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"

大文字のヘッダーを持つ表が表示されるはずです。出力は次のようになります。

      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

最後に、両方のオプションを組み合わせてみましょう。このコマンドを実行します。

python3 -c "
from tableformat import create_formatter, portfolio, print_table

formatter = create_formatter('text', column_formats=['%s', '%d', '%0.2f'], upper_headers=True)
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"

これにより、列がフォーマットされ、ヘッダーが大文字の表が表示されるはずです。出力は次のようになります。

      NAME     SHARES      PRICE
---------- ---------- ----------
        AA        100      32.20
       IBM         50      91.10
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50      65.10
       IBM        100      70.44

拡張された関数は、他のフォーマッタタイプでも機能します。たとえば、CSV フォーマッタで試してみましょう。以下のコマンドを実行します。

python3 -c "
from tableformat import create_formatter, portfolio, print_table

formatter = create_formatter('csv', column_formats=['\\"%s\\"', '%d', '%0.2f'])
print_table(portfolio, ['name', 'shares', 'price'], formatter)
"

これにより、列がフォーマットされた CSV 出力が生成されるはずです。出力は次のようになります。

name,shares,price
"AA",100,32.20
"IBM",50,91.10
"CAT",150,83.44
"MSFT",200,51.23
"GE",95,40.37
"MSFT",50,65.10
"IBM",100,70.44

create_formatter() 関数を拡張することで、使いやすい API を作成しました。ユーザーは今では、多重継承の複雑な詳細を理解することなく、簡単にミックスインを使用できます。これにより、ユーザーは自分のニーズに合わせてフォーマッタをカスタマイズする柔軟性が得られます。

✨ 解答を確認して練習

まとめ

この実験では、Python のミックスインクラスと協調的継承について学びました。これらは、既存のコードを変更することなくクラスの機能を拡張するための強力なテクニックです。単一継承の制限を理解すること、特定の機能のためのミックスインクラスを作成すること、協調的継承のために super() を使用してメソッドチェーンを構築することなどの重要な概念を探りました。

これらのテクニックは、特にフレームワークやライブラリにおいて、保守可能で拡張可能な Python コードを書くために非常に価値があります。これらにより、ユーザーが既存のコードを書き換える必要なくカスタマイズポイントを提供でき、複数のミックスインを組み合わせて複雑な動作を構成することができます。また、ユーザーに優しい API で継承の複雑さを隠すことも可能です。