Python Advanced Guide (Easy Advanced Programming): 16 のオブジェクト指向プログラミングと継承

オリジナル: http://inventwithpython.com/beyond/chapter16.html

関数を定義し、複数の場所から呼び出すことで、ソース コードをコピーして貼り付ける手間を省くことができます。コードを複製しないことは良い習慣です。コードを変更する必要がある場合 (バグの修正や新機能の追加など)、1 か所だけ変更すればよいからです。コードの繰り返しがなく、プログラムが短くなり、読みやすくなります。

関数と同様に、継承はクラスに適用できるコードの再利用手法です。これは、子クラスが親のメソッドのコピーを継承する親子関係にクラスを配置する行為であり、複数のクラスでメソッドを複製する必要がなくなります。

多くのプログラマーは、継承は過大評価され、危険でさえあると考えています。これは、継承されたクラスが多数あるとプログラムが複雑になるためです。「継承は悪」というタイトルのブログ投稿は、完全にとりとめのないものではありません; 継承は確かに乱用される傾向があります。ただし、この手法を限定的に使用すると、コードを整理するときに多くの時間を節約できます。

継承のしくみ

新しいサブクラスを作成するには、既存の親クラスの名前をclassステートメントの括弧内に入れます。サブクラス化を練習するには、新しいファイル エディター ウィンドウを開き、次のコードを入力します。名前を付けて保存しますinheritanceExample.py

class ParentClass: # 1
    def printHello(self): # 2
        print('Hello, world!')

class ChildClass(ParentClass): # 3
    def someNewMethod(self):
        print('ParentClass objects don't have this method.')

class GrandchildClass(ChildClass): # 4
    def anotherNewMethod(self):
        print('Only GrandchildClass objects have this method.')

print('Create a ParentClass object and call its methods:')
parent = ParentClass()
parent.printHello()

print('Create a ChildClass object and call its methods:')
child = ChildClass()
child.printHello()
child.someNewMethod()

print('Create a GrandchildClass object and call its methods:')
grandchild = GrandchildClass()
grandchild.printHello()
grandchild.someNewMethod()
grandchild.anotherNewMethod()

print('An error:')
parent.someNewMethod()

プログラムを実行すると、出力は次のようになります。

Create a ParentClass object and call its methods:
Hello, world!
Create a ChildClass object and call its methods:
Hello, world!
ParentClass objects don't have this method.
Create a GrandchildClass object and call its methods:
Hello, world!
ParentClass objects don't have this method.
Only GrandchildClass objects have this method.
An error:
Traceback (most recent call last):
  File "inheritanceExample.py", line 35, in <module>
    parent.someNewMethod() # ParentClass objects don't have this method.
AttributeError: 'ParentClass' object has no attribute 'someNewMethod'

ParentClass1、3、4という名前の 3 つのクラスChildClass作成しました。サブクラスとは、とすべて同じメソッドを持つことを意味します。継承されたメソッドと言いますさらに、サブクラスはを継承しているため、親クラスと同じメソッドをすべて持っています。GrandchildClassChildClass ParentClassChildClassParentClassChildClass ParentClassGrandchildClassChildClassChildClassParentClass

この手法を使用して、printHello()方法 2 のコードをChildClassandGrandchildClassクラスに効果的にコピー アンド ペーストしました。printHello()コードを変更すると、 だけでなくParentClassと も更新されChildClassますGrandchildClassこれは、関数内のコードを変更すると、そのすべての関数呼び出しが更新されるのと同じです。この関係を図 16-1 に示します。クラス ダイアグラムでは、矢印がサブクラスから基本クラスを指していることに注意してください。これは、クラスがその基本クラスを常に認識しているが、そのサブクラスを認識していないという事実を反映しています。

f16001

図 16-1: 3 つのクラスとそれらが持つメソッドの関係を示す階層図 (左) とベン図 (右)

親子クラスは「is-a」関係を表しているとよく言われます。オブジェクトChildClassは、オブジェクトが定義するいくつかの追加メソッドを含め、オブジェクトと同じメソッドをすべてParentClass持っているため、オブジェクトです。ParentClassこの関係は一方向です。ParentClassオブジェクトはオブジェクトではありませんChildClassオブジェクト (およびのサブクラス)にのみ存在する をParentClassオブジェクトが呼び出そうとすると、Python は をスローしますsomeNewMethod()ChildClassChildClassAttributeError

プログラマーは、関連するクラスが実際の "is" 階層に収まらなければならないと考えることがよくあります。通常、OOP チュートリアルにはVehicle``FourWheelVehicle▶``Car``Animal``Bird``Sparrow、またはShape``Rectangle``Square親クラス、子クラス、孫クラスがあります。ただし、継承の主な目的はコードの再利用であることを忘れないでください。プログラムで、他のクラスのメソッドの完全なスーパーセットである一連のメソッドを持つクラスが必要な場合、継承により、コードのコピーと貼り付けを回避できます。

また、サブクラスをサブクラスまたは派生クラス、親クラスをスーパークラスまたは基底クラスと呼ぶこともあります。

オーバーライド方法

サブクラスは、その親クラスのすべてのメソッドを継承します。ただし、サブクラスは、独自のメソッドに独自のコードを提供することで、継承されたメソッドをオーバーライドできます。サブクラスのオーバーライドされたメソッドは、スーパークラスのメソッドと同じ名前になります。

この概念を説明するために、前の章で作成した三目並べゲームに戻りましょう。今回は、より小さな三目並べボードを提供するために、MiniBoard継承TTTBoardとオーバーライドを行う新しいクラスを作成します。getBoardStr()プログラムは、プレーヤーにどのスタイルのボードを使用するかを尋ねます。TTTBoard残りのメソッドはMiniBoard継承されるため、コピーして貼り付ける必要はありません。

tictactoe_oop.pyファイルの末尾に次を追加して、元のTTTBoardクラスをサブクラス化し、getBoardStr()メソッドをオーバーライドします。

class MiniBoard(TTTBoard):
    def getBoardStr(self):
        """Return a tiny text-representation of the board."""
        # Change blank spaces to a '.'
        for space in ALL_SPACES:
            if self._spaces[space] == BLANK:
                self._spaces[space] = '.'

        boardStr = f'''
          {
      
      self._spaces['1']}{
      
      self._spaces['2']}{
      
      self._spaces['3']} 123
          {
      
      self._spaces['4']}{
      
      self._spaces['5']}{
      
      self._spaces['6']} 456
          {
      
      self._spaces['7']}{
      
      self._spaces['8']}{
      
      self._spaces['9']} 789'''

        # Change '.' back to blank spaces.
        for space in ALL_SPACES:
            if self._spaces[space] == '.':
                self._spaces[space] = BLANK
        return boardStr

TTTBoardクラスのgetBoardStr()メソッドと同様に、MiniBoardこのメソッドは、関数に渡されたときに表示されるgetBoardStr()三目並べボードの複数行の文字列を作成します。print()しかし、この文字列ははるかに小さく、X トークンと O トークンの間の線を削除し、ドットを使用してスペースを表しています。

オブジェクトではなくオブジェクトmain()をインスタンス化するように行を変更しますMiniBoardTTTBoard

 if input('Use mini board? Y/N: ').lower().startswith('y'):
        gameBoard = MiniBoard() # Create a MiniBoard object.
    else:
        gameBoard = TTTBoard() # Create a TTTBoard object.

この 1 行の変更を除いてmain()、プログラムの残りの部分は以前と同じです。ここでプログラムを実行すると、出力は次のようになります。

Welcome to Tic-Tac-Toe!
Use mini board? Y/N: y

          ... 123
          ... 456
          ... 789
What is X's move? (1-9)
1

          X.. 123
          ... 456
          ... 789
What is O's move? (1-9)
`--snip--`
          XXX 123
          .OO 456
          O.X 789
X has won the game!
Thanks for playing!

あなたのプログラムは、これら 2 つの三目並べボード クラスを簡単に実装できるようになりました。もちろん、ボードのミニ バージョンのみが必要な場合は、メソッドTTTBoard内のコードを簡単に置き換えることができます。しかし、 と がgetBoardStr()必要な場合は、継承により、共通のコードを再利用して 2 つのクラスを簡単に作成できます。

継承を使用していない場合は、という新しいプロパティをTTTBoard追加しuseMiniBoardその中に通常のパネルまたはミニパネルをいつ表示するかを決定するステートメントをgetBoardStr()入れることができます。if-elseこのような単純な変更の場合、これはうまく機能します。しかし、MiniBoardサブクラスが 2 つ、3 つ、または 100 個のメソッドをオーバーライドする必要がある場合はどうでしょうか? いくつかの異なるサブクラスを作成したい場合はどうすればよいでしょうかTTTBoard? 継承を使用しないと、メソッドでステートメントが急増しif-else、コードが大幅に複雑になります。サブクラスとオーバーライド メソッドを使用することで、コードを個別のクラスに整理して、これらのさまざまなユース ケースを処理できます。

super()関数

サブクラスのオーバーライドされたメソッドは通常、スーパークラスのメソッドに似ています。継承はコードを再利用する手法ですが、メソッドをオーバーライドすると、親クラス メソッドの同じコードがサブクラス メソッドの一部としてオーバーライドされる可能性があります。このコードの重複を防ぐために、組み込みsuper()関数を使用すると、オーバーライド メソッドが親クラスの元のメソッドを呼び出すことができます。

たとえば、のサブクラスでHintBoardある という新しいクラスを作成してみましょう。TTTBoard新しいクラスは をオーバーライドするgetBoardStr()ため、三目並べボードを描いた後、X または O が次の動きで勝った場合のヒントも追加されます。つまり、HintBoardクラスのメソッドは、三目並べを描くクラスメソッドと同じタスクをgetBoardStr()すべて実行する必要があります。コードを複製する代わりに、クラスメソッドからクラスのメソッドを呼び出すことでこれを行うことができます。ファイルの末尾に次を追加します。TTTBoardgetBoardStr()super()HintBoardgetBoardStr()TTTBoardgetBoardStr()tictactoe_oop.py

class HintBoard(TTTBoard):
    def getBoardStr(self):
        """Return a text-representation of the board with hints."""
        boardStr = super().getBoardStr() # Call getBoardStr() in TTTBoard. # 1

        xCanWin = False
        oCanWin = False
        originalSpaces = self._spaces # Backup _spaces. # 2
        for space in ALL_SPACES: # Check each space:
            # Simulate X moving on this space:
            self._spaces = copy.copy(originalSpaces)
            if self._spaces[space] == BLANK:
                self._spaces[space] = X
            if self.isWinner(X):
                xCanWin = True
            # Simulate O moving on this space:
            self._spaces = copy.copy(originalSpaces) # 3
            if self._spaces[space] == BLANK:
                self._spaces[space] = O
            if self.isWinner(O):
                oCanWin = True
        if xCanWin:
            boardStr += '\nX can win in one more move.'
        if oCanWin:
            boardStr += '\nO can win in one more move.'
        self._spaces = originalSpaces
        return boardStr

まず、 1 は​​親クラスsuper().getBoardStr()内でコードを実行し、三目並べボードの文字列を返します。この文字列をという変数に一時的に保存します。このメソッドの残りのコードは、クラスを再利用して作成されたチェッカーボード文字列を使用して、ヒントの生成を処理します。次に、このメソッドはand変数を に設定しディクショナリを変数 2 としてバックアップします。次に、ループが からまでのすべてのボード スペースをループしますループ内では、属性がディクショナリのコピーに設定され、ループされている現在のスペースが空の場合は、そこに X が配置されます。これは、この空きスペースでの X の次の移動をシミュレートします。への呼び出しは、これが勝ちの手であるかどうかを判断し、そうであれば、それを に設定します次に、O についてこれらの手順を繰り返して、O がこのスペースで 3 を移動して勝つことができるかどうかを確認します。このメソッドはモジュールを使用して辞書をコピーするため、次の行を の先頭に追加します。TTTBoardgetBoardStr()boardStrTTTBoardgetBoardStr()getBoardStr()xCanWinoCanWinFalseself._spacesoriginalSpacesfor'1''9'self._spacesoriginalSpacesself.isWinner()xCanWinTruecopyself._spacestictactoe.py

import copy

次に、オブジェクトではなくオブジェクトをmain()インスタンス化するように行を変更しますHintBoardTTTBoard

 gameBoard = HintBoard() # Create a TTT board object.

この 1 行の変更を除いてmain()、プログラムの残りの部分は以前とまったく同じです。ここでプログラムを実行すると、出力は次のようになります。

Welcome to Tic-Tac-Toe!
`--snip--`
      X| |   1 2 3
      -+-+-
       | |O  4 5 6
      -+-+-
       | |X  7 8 9
X can win in one more move.
What is O's move? (1-9)
5

      X| |   1 2 3
      -+-+-
       |O|O  4 5 6
      -+-+-
       | |X  7 8 9
O can win in one more move.
`--snip--`
The game is a tie!
Thanks for playing!

メソッドの最後にtruexCanWinの場合、追加のステートメント メッセージが文字列に追加されます。最後に、もう一度戻ってください。oCanWinTrueboardStrboardStr

オーバーライドされたすべてのメソッドを使用する必要があるわけではありませんsuper()super()クラスのオーバーライド メソッドが、親クラスのオーバーライドされたメソッドとはまったく異なることを行う場合、オーバーライドされたメソッドを呼び出す必要はありません。super()関数は、この章の「多重継承」で説明するように、クラスに複数の親メソッドがある場合に特に役立ちます。

継承より合成を好む

継承は、コードを再利用するための優れた手法であり、おそらくすべてのクラスですぐに使い始めたいと思うでしょう。ただし、基本クラスとサブクラスを常に緊密に結合する必要があるとは限りません。複数レベルの継承を作成しても、コードに組織が追加されるのではなく、官僚主義が追加されます。

"is" 関係を持つクラス (つまり、サブクラスが一種の親クラスである場合) で継承を使用できますが、"is" 関係を持つクラスで構成と呼ばれる手法を使用すると有利な場合がよくあります コンポジションは、オブジェクトを継承するクラスではなく、クラス内にオブジェクトを含めるクラス設計手法です。これは、クラスにプロパティを追加するときに行うことです。継承を使用してクラスを設計する場合は、継承よりも構成を優先してください。以下の表で説明するように、これは、この章と前の章のすべての例で行ったことです。

  • オブジェクトは、一定数のガレオン、シックル、およびクヌートのコインを「持っています」。
  • オブジェクトは 9 個のスペースのセットを「持っています」。
  • オブジェクトMiniBoardはオブジェクトなTTTBoardので、9 つのスペースのセットも「持っています」。
  • オブジェクトHintBoardはオブジェクトなTTTBoardので、9 つのスペースのセットも「持っています」。

WizCoin前の章のクラスに戻りましょう。WizardCustomer魔法界の顧客を表す新しいクラスを作成すると、これらの顧客は一定の金額を持ち、WizCoinクラスを通じてこのお金を表すことができます。しかし、2 つのクラスの間には「1 つである」という関係はなく、WizardCustomerオブジェクトはオブジェクトではありませんWizCoin継承を使用すると、ぎこちないコードになる可能性があります。

import wizcoin

class WizardCustomer(wizcoin.WizCoin): # 1
    def __init__(self, name):
        self.name = name
        super().__init__(0, 0, 0)

wizard = WizardCustomer('Alice')
print(f'{
      
      wizard.name} has {
      
      wizard.value()} knuts worth of money.')
print(f'{
      
      wizard.name}\'s coins weigh {
      
      wizard.weightInGrams()} grams.')

この例では、や などの 1 オブジェクトのメソッドWizardCustomerを継承しています技術的には、から継承すると、オブジェクトをプロパティとして含めるのと同じタスクを実行できます。しかし、メソッドメソッドの名前は誤解を招きます。それらは、魔法使いのコインの値と重さではなく、魔法使いの値と重さを返すようです。また、後でウィザードの weight にメソッドを追加したい場合、そのメソッド名は既に取得されています。WizCoinvalue()weightInGrams()WizCoinWizardCustomerWizCoinWizardCustomerwizard.value()wizard.weightInGrams()weightInGrams()

WizCoinウィザードの顧客は一定量のウィザード コインを「持っている」ため、オブジェクトをプロパティとして持つ方が適切です。

import wizcoin

class WizardCustomer:
    def __init__(self, name):
        self.name = name
        self.purse = wizcoin.WizCoin(0, 0, 0) # 1

wizard = WizardCustomer('Alice')
print(f'{
      
      wizard.name} has {
      
      wizard.purse.value()} knuts worth of money.')
print(f'{
      
      wizard.name}\'s coins weigh {
      
      wizard.purse.weightInGrams()} grams.')

WizardCustomerクラスに からメソッドを継承させる代わりに、オブジェクトを含む属性 1 をクラスにWizCoin与えましコンポジションを使用する場合、クラスのメソッドを変更しても、クラスのメソッドは変更されません。この手法により、2 つのクラスの将来の設計変更に対する柔軟性が向上し、コードがより保守しやすくなります。WizardCustomerpurseWizCoinWizCoinWizardCustomer

継承された短所

継承の主な欠点は、親クラスに加えた将来の変更が、そのすべてのサブクラスに継承されることです。ほとんどの場合、この密結合はまさにあなたが望むものです。ただし、場合によっては、コードのニーズが継承モデルに完全に適合しないことがあります。

たとえば、車両シミュレーション プログラムにCarとクラスMotorcycleがあるとしますLunarRoverstartIgnition()それらはすべて、や などの同様のメソッドを必要としますchangeTire()このコードをコピーして各クラスに貼り付ける代わりに、親クラスを作成してVehicleCar、 、Motorcycleおよびそれを継承することができます。LunarRoverこれで、たとえばメソッドのバグを修正する必要がある場合changeTire()、1 か所を変更するだけで済みます。Vehicleこれは、継承元のさまざまな車両関連のクラスが多数ある場合に特に便利です。これらのクラスのコードは次のようになります。

class Vehicle:
    def __init__(self):
        print('Vehicle created.')
    def startIgnition(self):
        pass  # Ignition starting code goes here.
    def changeTire(self):
        pass  # Tire changing code goes here.

class Car(Vehicle):
    def __init__(self):
        print('Car created.')

class Motorcycle(Vehicle):
    def __init__(self):
        print('Motorcycle created.')

class LunarRover(Vehicle):
    def __init__(self):
        print('LunarRover created.')

ただし、今後のすべてのVehicle変更は、これらのサブクラスにも影響します。メソッドが必要な場合はどうしますかchangeSparkPlug()車やオートバイにはスパークプラグ付きの内燃エンジンがありますが、月面車にはありません。CombustionEngine継承よりも構成を優先することで、個人とElectricEngineクラスを作成できます。次に、CombustionEngine ElectricEngineVehicleを「持つ」ようにクラスを設計しました。engine attribute, either a 对象,使用适当的方法:

class CombustionEngine:
   def __init__(self):
       print('Combustion engine created.')
   def changeSparkPlug(self):
       pass  # Spark plug changing code goes here.

class ElectricEngine:
   def __init__(self):
       print('Electric engine created.')

class Vehicle:
   def __init__(self):
       print('Vehicle created.')
       self.engine = CombustionEngine()  # Use this engine by default.
`--snip--`

class LunarRover(Vehicle):
   def __init__(self):
       print('LunarRover created.')
       self.engine = ElectricEngine()

Vehicleこれには、特に既存のクラスから継承する複数のクラスがある場合、多くのコードの書き直しが必要になる可能性があります。Vehicleクラスまたはそのサブクラスの各オブジェクトに対して、すべてのvehicleObj.changeSparkPlug()呼び出しを に変換する必要がありますvehicleObj.engine.changeSparkPlug()LunarVehicleこのような大きな変更によりエラーが発生する可能性があるため、単純にメソッドに何も行わせないことをお勧めしますchangeSparkPlug()この場合、Python スタイルのアプローチは、LunarVehicleクラス内で次のようにchangeSparkPlug設定することですNone

class LunarRover(Vehicle):
    changeSparkPlug = None
    def __init__(self):
        print('LunarRover created.')

changeSparkPlug = None行は、この章で後述する「クラス属性」で説明する構文に従います。これはからVehicle継承されたメソッドをオーバーライドするchangeSparkPlug()ため、LunarRoverオブジェクトで呼び出すとエラーが発生します。

>>> myVehicle = LunarRover()
LunarRover created.
>>> myVehicle.changeSparkPlug()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable

LunarRoverこのエラーにより、オブジェクトでこの不適切なメソッドを呼び出そうとした場合に、すぐに失敗して問題をすぐに見つけることができます。サブクラスLunarRoverchangeSparkPlug()このNone値を継承します。TypeError: 'NoneType' object is not callableエラー メッセージは、LunarRoverクラスのプログラマーが意図的にchangeSparkPlug()メソッドを に設定したことを示していますNoneそもそもそのようなメソッドがなかった場合、NameError: name 'changeSparkPlug' is not definedエラー メッセージが表示されます。

継承によって、複雑で矛盾するクラスが作成される可能性があります。多くの場合、代わりにテキストを使用する方が有利です。

isinstance()issubclass()関数

type()オブジェクトの型を知る必要がある場合、前の章で説明したように、オブジェクトを組み込み関数に渡すことができます。ただし、オブジェクトの型チェックを行う場合は、isinstance()より柔軟な組み込み関数を使用することをお勧めします。オブジェクトが指定されたクラスまたは指定されたクラスのサブクラスに属している場合、関数は戻りますインタラクティブ シェルに次のように入力します。isinstance()True

>>> class ParentClass:
...    pass
...
>>> class ChildClass(ParentClass):
...    pass
...
>>> parent = ParentClass() # Create a ParentClass object.
>>> child = ChildClass() # Create a ChildClass object.
>>> isinstance(parent, ParentClass)
True
>>> isinstance(parent, ChildClass)
False
>>> isinstance(child, ChildClass) # 1
True
>>> isinstance(child, ParentClass) # 2
True

isinstance()表現child内のオブジェクトは、 1 のインスタンスと2 のインスタンスChildClassであることに注意してください。オブジェクトは一種のオブジェクトであるため、これは理にかなっていますChildClassParentClassChildClassParentClass

クラス オブジェクトのタプルを 2 番目の引数として渡して、最初の引数がタプル内のいずれかのクラスであるかどうかを確認することもできます。

>>> isinstance(42, (int, str, bool)) # True if 42 is an int, str, or bool.
True

あまり一般的ではないissubclass()組み込み関数は、最初の引数として渡されたクラス オブジェクトが 2 番目の引数として渡されたクラス オブジェクトのサブクラス (または同じクラス) であるかどうかを認識します。

>>> issubclass(ChildClass, ParentClass) # ChildClass subclasses ParentClass.
True
>>> issubclass(ChildClass, str) # ChildClass doesn't subclass str.
False
>>> issubclass(ChildClass, ChildClass) # ChildClass is ChildClass.
True

use と同様にisinstance()、クラス オブジェクトのタプルを 2 番目の引数として渡してissubclass()、最初の引数がタプル内のいずれかのクラスのサブクラスであるかどうかを確認できます。isinstance()との主な違いは、2 つのクラス オブジェクトが渡されるのに対し、オブジェクトとクラス オブジェクトが渡されることissubclass()です。issubclass()isinstance()

クラスメソッド

クラス メソッドは、通常のメソッドのように単一のオブジェクトではなく、クラスに関連付けられます。次の例に示すようにdef、メソッドのステートメントの前の@classmethodデコレーターと最初のパラメーターとしての使用の 2 つのトークンが表示されると、コード内のクラス メソッドを認識できます。cls

class ExampleClass:
    def exampleRegularMethod(self):
        print('This is a regular method.')

    @classmethod
    def exampleClassMethod(cls):
        print('This is a class method.')

# Call the class method without instantiating an object:
ExampleClass.exampleClassMethod()

obj = ExampleClass()
# Given the above line, these two lines are equivalent:
obj.exampleClassMethod()
obj.__class__.exampleClassMethod()

パラメータは、オブジェクトのクラスではなくselfオブジェクトを参照することを除いて、同様に動作しますつまり、クラス メソッド内のコードは、個々のオブジェクト プロパティにアクセスしたり、オブジェクトの通常のメソッドを呼び出したりすることはできません。クラス メソッドは、他のクラス メソッドを呼び出すか、クラス属性にアクセスすることしかできません。名前は Python のキーワードであるため使用します。 、 、などの他のキーワードと同様に、パラメーター名には使用できません。のように、クラス オブジェクトを介してクラス属性を呼び出すことがよくありますしかし、 のように、クラスの任意のオブジェクトを介してそれらを呼び出すこともできますclsclsselfclsclassifwhileimportExampleClass.exampleClassMethod()obj.exampleClassMethod()

クラス メソッドは一般的に使用されません。最も一般的な使用例は、__init__()以外のオプションのコンストラクターを提供することです。たとえば、新しいオブジェクトが必要とするデータの文字列と、新しいオブジェクトが必要とするデータを含むファイル名の文字列の両方をコンストラクターが受け入れることができるとしたら? __init__()メソッドの長くて紛らわしいパラメーター リストは必要ありません。代わりに、新しいオブジェクトを返すクラス メソッドを使用しましょう。

たとえば、AsciiArtクラスを作成してみましょう。第 14 章で見たように、アスキー アートはテキスト文字を使用してイメージを形成します。

class AsciiArt:
   def __init__(self, characters):
       self._characters = characters

   @classmethod
   def fromFile(cls, filename):
       with open(filename) as fileObj:
           characters = fileObj.read()
           return cls(characters)

   def display(self):
       print(self._characters)

   # Other AsciiArt methods would go here...

face1 = AsciiArt(' _______\n' +
                '|  . .  |\n' +
                '| \\___/ |\n' +
                '|_______|')
face1.display()

face2 = AsciiArt.fromFile('face.txt')
face2.display()

AsciiArtクラスには、__init__()画像のテキスト文字を文字列として渡すことができるメソッドがあります。また、fromFile()ASCII アートを含むテキスト ファイルのファイル名文字列を渡すことができるクラス メソッドもあります。どちらのメソッドもAsciiArtオブジェクトを作成します。

このプログラムを実行して ASCII アート サーフェスを含むファイルを作成するとface.txt、出力は次のようになります。

 _______
|  . .  |
| \___/ |
|_______|
 _______
|  . .  |
| \___/ |
|_______|

__init__()クラス メソッドを使用すると、すべてを実行するよりもfromFile()コードが読みやすくなります。

AsciiArtクラス メソッドのもう 1 つの利点は、クラスのサブクラスがそのメソッドを継承できる(必要に応じてオーバーライドできる)ことですfromFile()そのため、の代わりにAsciiArtクラスのfromFile()メソッドでcls呼び出しますクラスはメソッドにハードコードされていないため、呼び出しは変更なしで のサブクラスでも機能します。ただし、呼び出しは常に、サブクラスの ではなく、クラスのを呼び出します「このクラスを表すオブジェクト」として理解できます(characters)AsciiArt(characters)cls()AsciiArtAsciiArtAsciiArt()AsciiArt__init__()__init__()cls

通常のメソッドが常にコード内のどこかでパラメーターを使用する必要があるのと同様にself、クラス メソッドは常にそのclsパラメーターを使用する必要があることに注意してください。クラス メソッドのコードがパラメーターをまったく使用しない場合cls、これはクラス メソッドがおそらく単なる関数であることを示しています。

クラス属性

クラス属性は、オブジェクトではなくクラスに属する変数です。ファイル内ですべての関数の外にグローバル変数を作成するのと同じように、.pyクラス内ですべてのメソッドの外でクラス属性を作成します。作成されたオブジェクトのcount数を追跡する という名前のクラス属性の例を次に示します。CreateCounter

class CreateCounter:
    count = 0 # This is a class attribute.

    def __init__(self):
        CreateCounter.count += 1

print('Objects created:', CreateCounter.count)  # Prints 0.
a = CreateCounter()
b = CreateCounter()
c = CreateCounter()
print('Objects created:', CreateCounter.count)  # Prints 3.

CreateCounterクラスには、countという名前のクラス属性があります。すべてのCreateCounterオブジェクトは、独自の個別のcountプロパティを持つ代わりに、このプロパティを共有します。そのため、コンストラクターの行は、CreateCounter.count += 1作成される各オブジェクトを記録できますCreateCounterプログラムを実行すると、出力は次のようになります。

Objects created: 0
Objects created: 3

クラス属性はめったに使用しません。この「作成されたオブジェクトの数をカウントするCreateCounter」例でさえ、クラス プロパティの代わりにグローバル変数を使用することで、より簡単に実行できます。

静的メソッド

静的メソッドにselforパラメーターがありませんcls静的メソッドは、クラスまたはそのオブジェクトのプロパティまたはメソッドにアクセスできないという点で、実際には単なる関数です。Python では、静的メソッドが必要になることはめったにありません。関数を使用する場合は、通常の関数の作成を検討する必要があります。

静的メソッドのステートメントの前にデコレータをdef配置して、静的メソッドを定義します。@staticmethod以下は静的メソッドの例です。

class ExampleClassWithStaticMethod:
   @staticmethod
   def sayHello():
       print('Hello!')

# Note that no object is created, the class name precedes sayHello():
ExampleClassWithStaticMethod.sayHello()

ExampleClassWithStaticMethodクラス内のsayHello()静的メソッドと関数sayHello()の間にはほとんど違いはありません。実際、事前にクラス名を入力しなくても関数を呼び出すことができるため、関数を使用することをお勧めします。

静的メソッドは、Python の柔軟な言語機能を持たない他の言語でより一般的です。Python には、他の言語の機能を模倣する静的メソッドが含まれていますが、実用的な価値はほとんどありません。

オブジェクト指向クラスと静的属性を使用する場合

クラス メソッド、クラス プロパティ、および静的メソッドが必要になることはほとんどありません。また、過度に使用する傾向があります。「代わりに関数やグローバル変数を使用できないのはなぜですか?」と考えている場合は、おそらくクラス メソッド、クラス プロパティ、または静的メソッドを使用する必要がないというヒントです。この中級レベルの本でそれらを紹介する唯一の理由は、コードでそれらに遭遇したときにそれらを認識できるようにするためですが、それらを使用しないことをお勧めします. これらは、フレームワークを使用するプログラマーによってサブクラス化される一連の慎重に作成されたクラスで独自のフレームワークを作成する場合に役立ちます。しかし、単純な Python アプリケーションを作成している場合は、おそらくそれらは必要ありません。

これらの機能の詳細と、それらが必要な理由と必要でない理由については、Phillip J. Eby のdirtsimple.org/2004/12/python-is-not-java.html記事「Python は Java ではない」と Ryan Tomaykotomayko.com/blog/2004/the-static-method-thingの記事「静的メソッド」を参照してください。

オブジェクト指向のバズワード

通常、OOP の説明は、継承、カプセル化、ポリモーフィズムなど、いくつかの用語から始まります。これらの用語を知ることの重要性は過大評価されていますが、少なくとも基本的な理解が必要です。継承については既に説明したので、ここではその他の概念について説明します。

カプセル化

カプセル化という言葉には、一般的ではあるが関連する定義が 2 つあります。最初の定義は、カプセル化とは、関連するデータとコードを 1 つのユニットにまとめることです。カプセル化するとは、ボックス化することを意味します。クラスは、関連するプロパティとメソッドをグループ化します。たとえば、このWizCoinクラスは 3 つの整数knutssicklesgalleons1 つのオブジェクトにカプセル化しますWizCoin

2 番目の定義は、カプセル化は、オブジェクトがどのように機能するかに関する複雑な実装の詳細をオブジェクトが隠蔽できるようにする情報隠蔽技術であるということです。これについては、282 ページの「プライベート プロパティとメソッド」で確認できます。ここでは、BankAccountオブジェクトは、プロパティの処理方法の詳細を非表示にするdeposit()とメソッドを提供します。関数は同様のブラック ボックスの目的を果たします。関数が数値の平方根を計算する方法は隠されています。関数は、渡した数値の平方根を返すことを知っておく必要があります。withdraw()_balancemath.sqrt()

ポリモーフィズム

ポリモーフィズムにより、あるタイプのオブジェクトを別のタイプのオブジェクトとして扱うことができます。たとえば、len()関数は渡された引数の長さを返します。文字列を渡してlen()文字数を確認できますが、リストまたは辞書を渡して、len()エントリまたはキーと値のペアの数をそれぞれ確認することもできます。この形式のポリモーフィズムは、さまざまな種類のオブジェクトを処理できるため、ジェネリック関数またはパラメトリック ポリモーフィズムとして知られています。

ポリモーフィズムは、アドホック ポリモーフィズムまたは演算子のオーバーロードも指します。演算子 ( または+など*) は、操作対象のオブジェクトのタイプに応じて異なる動作をする可能性があります。たとえば、+演算子は、2 つの整数値または浮動小数点値を処理する場合は数学的加算を行いますが、2 つの文字列を処理する場合は文字列連結を行います。演算子のオーバーロードについては、第 17 章で説明します。

継承を使用しない場合

継承を使用してクラスをオーバーエンジニアリングするのは簡単です。Luciano Ramaljo が言ったように、「物事を整理することは、私たちの秩序感覚を刺激します。プログラマーは楽しみのためにそれを行います。」クラスまたはモジュール内の複数の関数が同じことを達成できる場合、クラス、サブクラス、およびサブクラスを作成します。しかし、第 6 章の Python マントラを思い出してください: シンプルは複雑よりも優れています。

pyOOP を使用すると、特定の順序で定義されていない数百の関数を含む 1 つの大きなファイルよりも簡単に推論できる小さな単位 (この場合はクラス) にコードを編成できます。継承は、すべてが同じディクショナリまたはリスト データ構造で動作する複数の関数がある場合に便利です。この場合、それらをクラスに編成すると便利です。

ただし、クラスの作成や継承の使用を必要としない例をいくつか示します。

  • selfまたはパラメータを使用しないメソッドでクラスが構成されている場合はcls、そのクラスを削除し、メソッドの代わりに関数を使用します。
  • サブクラスが 1 つしかない親クラスを作成したが、親クラスのオブジェクトを作成したことがない場合は、それらを 1 つのクラスに結合できます。
  • 3 つまたは 4 つを超えるレベルのサブクラスを作成すると、不必要に継承を使用している可能性があります。これらのサブクラスをより少ないクラスにマージします。

前の章で示した三目並べプログラムの非オブジェクト指向バージョンとオブジェクト指向バージョンのように、クラスを使用しなくても、動作するエラーのないプログラムを作成することはもちろん可能です。プログラムをクラスの複雑なネットワークに設計する必要があるとは思わないでください。機能する単純なソリューションは、機能しない複雑なソリューションよりも優れています。Joel Spolsky はブログ記事「Don't Let Let Astronaut Architects Scare You」に書いています。

継承などのオブジェクト指向の概念がどのように機能するかを知っておく必要があります。これは、コードを整理し、開発とデバッグを容易にするのに役立ちます。Python の柔軟性により、この言語は OOP 機能を提供するだけでなく、プログラムのニーズに合わない場合でもそれらを使用する必要がありません。

多重継承

多くのプログラミング言語では、クラスを最大 1 つの親クラスに制限しています。Python は、多重継承と呼ばれる機能を提供することで、複数の親クラスをサポートします。たとえば、flyInTheAir()メソッドを持つクラスとメソッドをAirplane持つクラスを持つことができます次に、ステートメントに 2 つをコンマで区切ってリストすることにより、 andを継承するクラスを作成できます。新しいファイル エディター ウィンドウを開き、次のファイルを名前を付けて保存しますfloatOnWater()ShipAirplaneShipFlyingBoatclassflyingboat.py

class Airplane:
    def flyInTheAir(self):
        print('Flying...')

class Ship:
    def floatOnWater(self):
        print('Floating...')

class FlyingBoat(Airplane, Ship):
    pass

インタラクティブ シェルで確認できるように、作成するオブジェクトはおよびメソッドFlyingBoatを継承します。flyInTheAir()floatOnWater()

>>> from flyingboat import *
>>> seaDuck = FlyingBoat()
>>> seaDuck.flyInTheAir()
Flying...
>>> seaDuck.floatOnWater()
Floating...

親クラスのメソッド名が異なり、重複しない限り、多重継承は単純です。これらの種類のクラスはmixinと呼ばれます。(これは単なるクラスの一般的な用語です。Python にはキーワードがありませんmixin。) しかし、メソッド名を共有する複数の複雑なクラスから継承するとどうなるでしょうか?

たとえば、この章の前半のMiniBoardTic Tac Toe ボード クラスについて考えてみましょうHintTTTBoardミニチュアの三目並べボードを表示してヒントを与えるクラスが必要だとしたら? 多重継承により、これらの既存のクラスを再利用できます。次をtictactoe_oop.pyファイルの最後に追加しますが、main()関数を呼び出すステートメントの前に追加しますif

class HybridBoard(HintBoard, MiniBoard):
    pass

このクラスには何もありません。継承HintBoardMiniBoard. 次に、main()オブジェクトを作成するように関数のコードを変更しますHybridBoard

gameBoard = HybridBoard() # Create a TTT board object.

両方の親クラスMiniBoardHintBoard両方にgetBoardStr()というメソッドがあるため、HybridBoardどちらから継承するか? このプログラムを実行すると、出力にはミニチュアの三目並べが表示されますが、ヒントも提供されます。

`--snip--`
          X.. 123
          .O. 456
          X.. 789
X can win in one more move.

Python は魔法のようにMiniBoardクラスgetBoardStr()メソッドとHintBoardクラスgetBoardStr()メソッドをマージして両方を実現しているようです! しかし、それは私がそれらを相互に作用するように書いたからです。実際、HybridBoardclassclassステートメントでクラスの順序を変更すると、次のようになります。

`class HybridBoard(MiniBoard, HintBoard):` 

ヒントを完全に失います:

`--snip--`
          X.. 123
          .O. 456
          X.. 789

この理由を理解するには、Python のメソッド解決順序( MRO ) と、super()関数が実際にどのように機能するかを理解する必要があります。

メソッド解決順序

図 16-2 に示すように、tic-tac-toe プログラムにはボードを表す 4 つのクラスがあり、そのうちの 3 つはgetBoardStr()メソッドが定義されており、1 つはメソッドが継承されています。getBoardStr()

f16002

図 16-2: 三目並べプログラムの 4 つのクラス

HybridBoardobjectで呼び出すとgetBoardStr()、Python はHybridBoardクラスにその名前のメソッドがないことを認識しているため、その親クラスをチェックします。しかし、クラスには 2 つの親があり、両方ともgetBoardStr()メソッドを持っています。どれが呼ばれますか?

HybridBoardクラスの MRO を調べることで見つけることができます。これはsuper()、Python がメソッドを継承するとき、またはメソッドが関数を呼び出すときにチェックするクラスの順序付きリストです。HybridBoardクラスmro()の MRO は、インタラクティブ シェルでメソッドを呼び出すことで確認できます。

>>> from tictactoe_oop import *
>>> HybridBoard.mro()
[<class 'tictactoe_oop.HybridBoard'>, <class 'tictactoe_oop.HintBoard'>, <class 'tictactoe_oop.MiniBoard'>, <class 'tictactoe_oop.TTTBoard'>, <class 'object'>]

この戻り値からわかるように、 でHybridBoardメソッドを呼び出すと、Python は最初にHybridBoardクラス内でそれをチェックします。存在しない場合、Python はHintBoardクラス、次にMiniBoardクラス、最後にTTTBoardクラスをチェックします。各 MRO リストの最後には組み込みobjectクラスがあり、これは Python のすべてのクラスの親クラスです。

単一継承では、MRO の決定は簡単です。親クラスのチェーンを作成するだけです。多重継承では、よりトリッキーになります。Python の MRO は C3 アルゴリズムに従いますが、その詳細はこの本の範囲を超えています。ただし、次の 2 つのルールを覚えておくことで、MRO を決定できます。

  • Python は、スーパークラスをチェックする前にサブクラスをチェックします。
  • Python は、classステートメントの左から右にリストされている継承されたクラスを調べます。

HybridBoardobject を呼び出すgetBoardStr()、Python は最初にHybridBoardクラスをチェックします。次に、クラスの親は sum from left to HintBoardrightであるためMiniBoard、Python は をチェックしますHintBoardこの親クラスにはgetBoardStr()メソッドがあるので、HybridBoard継承して呼び出します。

しかし、それだけではありません。次に、メソッド call ですsuper().getBoardStr()superは、親クラスを返す代わりに MRO の次のクラスを返すため、 Pythonsuper()の関数のやや誤解を招く名前です。これは、HybridBoardobject を呼び出すとgetBoardStr()、その MRO 内の次のクラスであるHintBoardafter はMiniBoard親クラスではなく、 であることを意味しますTTTBoardつまり、ミニチュアの三目並べ文字列を返すクラスメソッドsuper().getBoardStr()の呼び出しです。この呼び出しの後のクラスの残りのコードは、プロンプト テキストをこの文字列に追加します。MiniBoardgetBoardStr()super()HintBoardgetBoardStr()

HybridBoardclassclassステートメントを最初にリストされるように変更するとMiniBoardHintBoardその MRO が最初にMiniBoardリストされますHintBoardこれは、を呼び出さずにHybridBoardからMiniBoard継承することを意味しますこの順序付けにより、ミニチュアの三目並べボードにサイレント エラーが表示さますgetBoardStr()MiniBoardsuper()super()MiniBoardgetBoardStr()HintBoardgetBoardStr()

多重継承を使用すると、少量のコードで多くの機能を作成できますが、過度に設計された、理解しにくいコードに簡単につながる可能性があります。単一継承、ミックスイン クラス、または継承なしをサポートします。これらの手法は、多くの場合、プログラムのタスクをより適切に達成できます。

要約する

継承は、コードの再利用手法です。親クラスのメソッドを継承するサブクラスを作成できます。super()これらのメソッドをオーバーライドして新しいコードを与えることができますが、関数を使用して親クラスの元のメソッドを呼び出すこともできます。サブクラスのオブジェクトは親クラスのオブジェクトの型であるため、サブクラスには親クラスとの「is」関係があります。

Python では、クラスと継承の使用はオプションです。一部のプログラマーは、継承を多用することの複雑さに見合うだけの価値があるとは考えていません。継承ではなく構成を使用すると、他のクラスからメソッドを直接継承するのではなく、あるクラスのオブジェクトと他のクラスのオブジェクトとの「has」関係を実装するため、多くの場合より柔軟になります。これは、あるクラスのオブジェクトが別のクラスのオブジェクトを所有できることを意味します。たとえば、Customerオブジェクトには、クラスのサブクラスではなくDateオブジェクトに割り当てられたプロパティがある場合がありますbirthdateCustomerDate

type()渡されたオブジェクトの型を返すことができるように、isinstance()関数issubclass()は渡されたオブジェクトの型と継承情報を返します。

クラスはオブジェクト メソッドとプロパティを持つことができますが、クラス メソッド、クラス プロパティ、および静的メソッドを持つこともできます。これらはめったに使用されませんが、グローバル変数や関数ではサポートできない他のオブジェクト指向の手法をサポートできます。

Python では、クラスが複数の親クラスから継承できるようになっていますが、これによりコードがわかりにくくなる可能性があります。super()クラスの関数とメソッドは、MRO に基づいてメソッドがどのように継承されるかを決定します。mro()クラスのメソッドを呼び出すことにより、対話型シェルでクラスの MRO を表示できます。

この章と前の章では、一般的な OOP の概念について説明しました。次の章では、Python 固有の OOP 手法について説明します。`

おすすめ

転載: blog.csdn.net/wizardforcel/article/details/130030640
おすすめ