原文:『実用的なPythonプログラミング』04_02_Inheritance
54309 ワード
目次|前節(4.1類)|次節(4.3特殊方法)
4.2継承
継承(inheritance)は拡張可能なプログラムを記述する一般的な手段である.本節では継承の思想(idea)について検討する.
概要
継承:既存のオブジェクトを特殊化します.
新クラス
拡張
継承を使用すると、既存のクラスを取得できます.新規メソッド追加 既存メソッドの再定義 インスタンスに新しい属性を追加 最後に、既存のコードを拡張しました.
例
これが最初のクラスだとします.
Stockクラスの任意の部分を継承することで変更できます.
新しいメソッドの追加
(注:「panic」はここで「panic selling」を表し、パニック投げ売り)
使用例:
既存のメソッドの再定義
使用例:
新しいcost()メソッドは古いcost()メソッドに代わった.他の方法は影響を受けません.
メソッドオーバーライド
クラスは、既存のメソッドを拡張したいと同時に、新しい定義で既存の実装を使用したい場合があります.このため、
内蔵関数を使用する
注意:Python 2では、文法がより冗長になります.以下のようにします.
継承の使用
組織に関連するオブジェクトを継承する場合があります.
関連するオブジェクトを整理するには、論理階層を使用するか、分類することを考慮します.しかしながら、より一般的な(より実用的な)方法は、再利用可能で拡張可能なコードを作成することである.たとえば、フレームワークでベースクラスを定義し、カスタマイズを指導することができます.
ベースクラスには汎用コードが含まれています.あなたのクラスはベースクラスを継承し、特殊な部分をカスタマイズします.
「is a」関係
継承はタイプ関係を確立します.
オブジェクトのインスタンスを確認します.
重要なヒント:親インスタンスを使用して正常に動作するコードでも、子クラスのインスタンスを使用して正常に動作するのが理想的です.
クラスに親がいない場合は、
Pythonでは、
注意:技術的には必要ではありませんが、通常は
多重継承
クラス定義で複数のベースクラスを指定することで、多重継承を実現できます.
練習する
継承の主な目的は、特にライブラリまたはフレームワークで、拡張性とカスタマイズ性のあるコードをさまざまな方法で作成することです.この点を説明するには、
練習4.5:拡張性の問題
純テキスト、HTML、CSV、XMLなど、さまざまな出力フォーマットをサポートするために、
まず、テーブルの作成に関する手順に注目してください.表の上部にタイトルがあります.タイトルの後ろにデータ行があります.これらの手順を使用して、それぞれのクラスに配置しましょう.
後で他のクラスを定義する設計仕様として使用される以外は、このクラスは何もしません.このようなクラスを「抽象ベースクラス」と呼ぶことがある.
新しいコードを実行します.
プログラムはすぐにクラッシュするはずで、1つ
練習4.6:継承を使用して異なる出力を生成
a部で定義されている
下記のように修正してください
これにより、以前と同じ出力が生成されるはずです.
ただし、出力を他の内容に変更しましょう.CSV形式で出力を生成する
以下のようにメインプログラムを変更してください.
次のようなCSV出力が表示されます.
同様の考え方を用いて、
メインプログラムを変更してコードをテストしてください.メインプログラムは、
練習4.7:多態
オブジェクト向けプログラミング(oop)の主な特性は、オブジェクトをプログラムに挿入し、既存のコードを変更することなく実行できることです.たとえば、
1つの潜在的な問題は、ユーザーが望むフォーマットを選択する方法を明らかにすることです.
このコードでは、ユーザは簡略化された名前(例えば
異なるフォーマットで関数を呼び出して、正常に動作していることを確認します.
練習4.8:まとめ
コマンドラインで出力フォーマットを指定できるように、メインプログラムを変更してください.
ディスカッション
ライブラリとフレームワークでは、拡張可能なプログラムを作成することが継承の最も一般的な用途の1つです.たとえば、フレームワークでは、指定したベースクラスを継承する独自のオブジェクトを定義します.そして、さまざまな機能を実現する関数を追加することができます.
もう一つのより深い概念は「抽象的な思想を持つ」ことだ.練習では、テーブルをフォーマットするための独自のクラスを定義します.自分のコードを見て、「フォーマットライブラリや他の人が書いたものだけを使うべきだ!」と自分に伝えるかもしれません.いいえ、自分のクラスとライブラリを同時に使用する必要があります.独自のクラスを使用すると、プログラムの結合性を低下させ、プログラムの柔軟性を高めることができます.プログラムが使用するアプリケーションインタフェースが自分で定義したクラスから来ている限り、プログラムの内部実装を変更して、あなたが考えているように動作させることができます.フルカスタマイズ(all-custom)コードを作成したり、サードパーティ製パッケージを使用したりすることができます.より良いパッケージを見つけたら、サードパーティ製のパッケージを別のパッケージに置き換えることができます.これは重要ではありません.このインタフェースを保持すれば、アプリケーションコードは中断されません.これは強力な思想であり、継承を使用すべき理由の一つでもある.
すなわち,オブジェクト向けのプログラムを設計することは非常に困難である可能性がある.詳細については、デザインモデルのテーマに関する本を探して読むべきかもしれません(この練習の内容を理解することは、実用的な方法で使用対象から遠く離れていますが).
目次|前節(4.1類)|次節(4.3特殊方法)
注:完全な翻訳を参照https://github.com/codists/practical-python-zh
4.2継承
継承(inheritance)は拡張可能なプログラムを記述する一般的な手段である.本節では継承の思想(idea)について検討する.
概要
継承:既存のオブジェクトを特殊化します.
class Parent:
...
class Child(Parent):
...
新クラス
Child
派生クラス(derived class)またはサブクラス(subclass)と呼ぶ.クラスParent
ベースクラスまたはスーパークラスと呼ぶ.サブクラス名の後の括弧()
でベースクラス(Parent
)、class Child(Parent):
を指定します.拡張
継承を使用すると、既存のクラスを取得できます.
例
これが最初のクラスだとします.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
def sell(self, nshares):
self.shares -= nshares
Stockクラスの任意の部分を継承することで変更できます.
新しいメソッドの追加
class MyStock(Stock):
def panic(self):
self.sell(self.shares)
(注:「panic」はここで「panic selling」を表し、パニック投げ売り)
使用例:
>>> s = MyStock('GOOG', 100, 490.1)
>>> s.sell(25)
>>> s.shares
75
>>> s.panic()
>>> s.shares
0
>>>
既存のメソッドの再定義
class MyStock(Stock):
def cost(self):
return 1.25 * self.shares * self.price
使用例:
>>> s = MyStock('GOOG', 100, 490.1)
>>> s.cost()
61262.5
>>>
新しいcost()メソッドは古いcost()メソッドに代わった.他の方法は影響を受けません.
メソッドオーバーライド
クラスは、既存のメソッドを拡張したいと同時に、新しい定義で既存の実装を使用したい場合があります.このため、
super()
関数を用いて実現することができる(注:
時には
):class Stock:
...
def cost(self):
return self.shares * self.price
...
class MyStock(Stock):
def cost(self):
# Check the call to `super`
actual_cost = super().cost()
return 1.25 * actual_cost
内蔵関数を使用する
super()
以前のバージョンを呼び出す.注意:Python 2では、文法がより冗長になります.以下のようにします.
actual_cost = super(MyStock, self).cost()
__init__
相続__init__
メソッドがサブクラスで再定義されている場合は、親クラスを初期化する必要があります.class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
class MyStock(Stock):
def __init__(self, name, shares, price, factor):
# Check the call to `super` and `__init__`
super().__init__(name, shares, price)
self.factor = factor
def cost(self):
return self.factor * super().cost()
super
親を呼び出す__init__()
メソッドを使用する必要があります.前に示したように、以前のバージョンを呼び出すメソッドです.継承の使用
組織に関連するオブジェクトを継承する場合があります.
class Shape:
...
class Circle(Shape):
...
class Rectangle(Shape):
...
関連するオブジェクトを整理するには、論理階層を使用するか、分類することを考慮します.しかしながら、より一般的な(より実用的な)方法は、再利用可能で拡張可能なコードを作成することである.たとえば、フレームワークでベースクラスを定義し、カスタマイズを指導することができます.
class CustomHandler(TCPHandler):
def handle_request(self):
...
# Custom processing
ベースクラスには汎用コードが含まれています.あなたのクラスはベースクラスを継承し、特殊な部分をカスタマイズします.
「is a」関係
継承はタイプ関係を確立します.
class Shape:
...
class Circle(Shape):
...
オブジェクトのインスタンスを確認します.
>>> c = Circle(4.0)
>>> isinstance(c, Shape)
True
>>>
重要なヒント:親インスタンスを使用して正常に動作するコードでも、子クラスのインスタンスを使用して正常に動作するのが理想的です.
object
ベースクラスクラスに親がいない場合は、
object
ベースクラスとして使用されている場合があります.class Shape(object):
...
Pythonでは、
object
すべてのオブジェクトのベースクラスです.注意:技術的には必要ではありませんが、通常は
object
Python 2に保存されています.省略すると、クラスは暗黙的にobject
から継承されます.多重継承
クラス定義で複数のベースクラスを指定することで、多重継承を実現できます.
class Mother:
...
class Father:
...
class Child(Mother, Father):
...
Child
クラスは2つの親(Mother,Father)の特性を継承している.ここにはかなり厄介な細部があります.あなたが何をしているか知っていない限り、そうしないでください.詳細は次のセクションで説明しますが、このコースでは多重継承はさらに使用されません.練習する
継承の主な目的は、特にライブラリまたはフレームワークで、拡張性とカスタマイズ性のあるコードをさまざまな方法で作成することです.この点を説明するには、
report.py
プログラム中のprint_report()
関数を考慮してください.次のように見えます.def print_report(reportdata):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
headers = ('Name','Shares','Price','Change')
print('%10s %10s %10s %10s' % headers)
print(('-'*10 + ' ')*len(headers))
for row in reportdata:
print('%10s %10d %10.2f %10.2f' % row)
report.py
プログラムを実行すると、次のような出力が得られるはずです.>>> import report
>>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv')
Name Shares Price Change
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
練習4.5:拡張性の問題
純テキスト、HTML、CSV、XMLなど、さまざまな出力フォーマットをサポートするために、
print_report()
関数を変更したいとします.そのため、各機能を実現するために膨大な関数を作成してみることができます.しかし、これはコードが非常に混乱し、メンテナンスができない可能性があります.継承を使用するには絶好の機会です.まず、テーブルの作成に関する手順に注目してください.表の上部にタイトルがあります.タイトルの後ろにデータ行があります.これらの手順を使用して、それぞれのクラスに配置しましょう.
tableformat.py
という名前のファイルを作成し、次のクラスを定義します.# tableformat.py
class TableFormatter:
def headings(self, headers):
'''
Emit the table headings.
'''
raise NotImplementedError()
def row(self, rowdata):
'''
Emit a single row of table data.
'''
raise NotImplementedError()
後で他のクラスを定義する設計仕様として使用される以外は、このクラスは何もしません.このようなクラスを「抽象ベースクラス」と呼ぶことがある.
print_report()
関数を1つTableFormatter
オブジェクトを入力として受け入れるように修正し、TableFormatter
のメソッドを実行して出力を生成してください.例:# report.py
...
def print_report(reportdata, formatter):
'''
Print a nicely formated table from a list of (name, shares, price, change) tuples.
'''
formatter.headings(['Name','Shares','Price','Change'])
for name, shares, price, change in reportdata:
rowdata = [ name, str(shares), f'{price:0.2f}', f'{change:0.2f}' ]
formatter.row(rowdata)
portfolio_report()
関数にパラメータを追加したので、portfolio_report()
関数も修正する必要があります.portfolio_report()
関数を修正して、以下のように作成してくださいTableFormatter
:# report.py
import tableformat
...
def portfolio_report(portfoliofile, pricefile):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
formatter = tableformat.TableFormatter()
print_report(report, formatter)
新しいコードを実行します.
>>> ================================ RESTART ================================
>>> import report
>>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv')
... crashes ...
プログラムはすぐにクラッシュするはずで、1つ
NotImplementedError
異常が付いています.これはそれほど興奮していませんが、結果は確かに私たちが期待しています.次のセクションに進みます.練習4.6:継承を使用して異なる出力を生成
a部で定義されている
TableFormatter
クラスは継承によって拡張されることを目的としている.実際、これが思想全体です.この点を説明するには、以下のように定義してくださいTextTableFormatter
クラス:# tableformat.py
...
class TextTableFormatter(TableFormatter):
'''
Emit a table in plain-text format
'''
def headings(self, headers):
for h in headers:
print(f'{h:>10s}', end=' ')
print()
print(('-'*10 + ' ')*len(headers))
def row(self, rowdata):
for d in rowdata:
print(f'{d:>10s}', end=' ')
print()
下記のように修正してください
portfolio_report()
関数:# report.py
...
def portfolio_report(portfoliofile, pricefile):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
formatter = tableformat.TextTableFormatter()
print_report(report, formatter)
これにより、以前と同じ出力が生成されるはずです.
>>> ================================ RESTART ================================
>>> import report
>>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv')
Name Shares Price Change
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
>>>
ただし、出力を他の内容に変更しましょう.CSV形式で出力を生成する
CSVTableFormatter
を定義します.# tableformat.py
...
class CSVTableFormatter(TableFormatter):
'''
Output portfolio data in CSV format.
'''
def headings(self, headers):
print(','.join(headers))
def row(self, rowdata):
print(','.join(rowdata))
以下のようにメインプログラムを変更してください.
def portfolio_report(portfoliofile, pricefile):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
formatter = tableformat.CSVTableFormatter()
print_report(report, formatter)
次のようなCSV出力が表示されます.
>>> ================================ RESTART ================================
>>> import report
>>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv')
Name,Shares,Price,Change
AA,100,9.22,-22.98
IBM,50,106.28,15.18
CAT,150,35.46,-47.98
MSFT,200,20.89,-30.34
GE,95,13.48,-26.89
MSFT,50,20.89,-44.21
IBM,100,106.28,35.84
同様の考え方を用いて、
HTMLTableFormatter
クラスを定義し、以下の出力を持つテーブルを生成する.NameSharesPriceChange
AA1009.22-22.98
IBM50106.2815.18
CAT15035.46-47.98
MSFT20020.89-30.34
GE9513.48-26.89
MSFT5020.89-44.21
IBM100106.2835.84
メインプログラムを変更してコードをテストしてください.メインプログラムは、
HTMLTableFormatter
オブジェクトではなく、CSVTableFormatter
オブジェクトを作成します.練習4.7:多態
オブジェクト向けプログラミング(oop)の主な特性は、オブジェクトをプログラムに挿入し、既存のコードを変更することなく実行できることです.たとえば、
TableFormatter
オブジェクトを使用する予定のプログラムを作成すると、どんなタイプのTableFormatter
を与えても正常に動作します.このような行為を「多態」と呼ぶことがある.1つの潜在的な問題は、ユーザーが望むフォーマットを選択する方法を明らかにすることです.
TextTableFormatter
のように直接類名を使うのは、通常ちょっと煩わしいです.そのため、簡略化された方法を考えるべきです.コードに埋め込むことができますif
文:def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
if fmt == 'txt':
formatter = tableformat.TextTableFormatter()
elif fmt == 'csv':
formatter = tableformat.CSVTableFormatter()
elif fmt == 'html':
formatter = tableformat.HTMLTableFormatter()
else:
raise RuntimeError(f'Unknown format {fmt}')
print_report(report, formatter)
このコードでは、ユーザは簡略化された名前(例えば
'txt'
または'csv'
)を指定してフォーマットを選択することができるが、このようにportfolio_report()
関数に大量のif
文を使用することは本当に望ましいのだろうか.これらのコードを他の汎用関数に移動したほうがいいかもしれません.tableformat.py
ファイルには、create_formatter(name)
という名前の関数を追加してください.この関数により、ユーザが所定の出力名(例えば'txt'
、'csv'
、または'html'
)のフォーマット(formatter)を作成できるようにします.下記のように修正してくださいportfolio_report()
関数:def portfolio_report(portfoliofile, pricefile, fmt='txt'):
'''
Make a stock report given portfolio and price data files.
'''
# Read data files
portfolio = read_portfolio(portfoliofile)
prices = read_prices(pricefile)
# Create the report data
report = make_report_data(portfolio, prices)
# Print it out
formatter = tableformat.create_formatter(fmt)
print_report(report, formatter)
異なるフォーマットで関数を呼び出して、正常に動作していることを確認します.
練習4.8:まとめ
report.py
プログラムを修正して、portfolio_report()
関数がオプションパラメータで出力フォーマットを指定できるようにしてください.例:>>> report.portfolio_report('Data/portfolio.csv', 'Data/prices.csv', 'txt')
Name Shares Price Change
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
>>>
コマンドラインで出力フォーマットを指定できるように、メインプログラムを変更してください.
bash $ python3 report.py Data/portfolio.csv Data/prices.csv csv
Name,Shares,Price,Change
AA,100,9.22,-22.98
IBM,50,106.28,15.18
CAT,150,35.46,-47.98
MSFT,200,20.89,-30.34
GE,95,13.48,-26.89
MSFT,50,20.89,-44.21
IBM,100,106.28,35.84
bash $
ディスカッション
ライブラリとフレームワークでは、拡張可能なプログラムを作成することが継承の最も一般的な用途の1つです.たとえば、フレームワークでは、指定したベースクラスを継承する独自のオブジェクトを定義します.そして、さまざまな機能を実現する関数を追加することができます.
もう一つのより深い概念は「抽象的な思想を持つ」ことだ.練習では、テーブルをフォーマットするための独自のクラスを定義します.自分のコードを見て、「フォーマットライブラリや他の人が書いたものだけを使うべきだ!」と自分に伝えるかもしれません.いいえ、自分のクラスとライブラリを同時に使用する必要があります.独自のクラスを使用すると、プログラムの結合性を低下させ、プログラムの柔軟性を高めることができます.プログラムが使用するアプリケーションインタフェースが自分で定義したクラスから来ている限り、プログラムの内部実装を変更して、あなたが考えているように動作させることができます.フルカスタマイズ(all-custom)コードを作成したり、サードパーティ製パッケージを使用したりすることができます.より良いパッケージを見つけたら、サードパーティ製のパッケージを別のパッケージに置き換えることができます.これは重要ではありません.このインタフェースを保持すれば、アプリケーションコードは中断されません.これは強力な思想であり、継承を使用すべき理由の一つでもある.
すなわち,オブジェクト向けのプログラムを設計することは非常に困難である可能性がある.詳細については、デザインモデルのテーマに関する本を探して読むべきかもしれません(この練習の内容を理解することは、実用的な方法で使用対象から遠く離れていますが).
目次|前節(4.1類)|次節(4.3特殊方法)
注:完全な翻訳を参照https://github.com/codists/practical-python-zh