Python super()でクラスを強化する

Python super()でクラスを強化する

Pythonは純粋にオブジェクト指向の言語ではありませんが、オブジェクト指向のパラダイムを使用してアプリケーションを構築できるほど十分に柔軟で強力です。 Pythonがこれを実現する方法の1つは、super()で行うinheritanceをサポートすることです。

このチュートリアルでは、次のことについて学習します。

  • Pythonの継承の概念

  • Pythonの多重継承

  • super()関数のしくみ

  • 単一継承でのsuper()関数のしくみ

  • 多重継承でのsuper()の機能

Free Bonus:5 Thoughts On Python Masteryは、Python開発者向けの無料コースで、Pythonスキルを次のレベルに引き上げるために必要なロードマップと考え方を示しています。

Pythonのsuper()関数の概要

オブジェクト指向言語の経験がある場合は、super()の機能に既に精通している可能性があります。

そうでない場合でも、恐れないでください! official documentationはかなり技術的ですが、高レベルでは、super()を使用すると、スーパークラスから継承するサブクラスからスーパークラスのメソッドにアクセスできます。

super()だけで、スーパークラスの一時オブジェクトが返されます。これにより、そのスーパークラスのメソッドを呼び出すことができます。

なぜあなたはこれをしたいのですか? 可能性は想像力によって制限されますが、一般的な使用例は、以前に構築されたクラスの機能を拡張するクラスを構築することです。

以前に構築されたメソッドをsuper()で呼び出すと、サブクラスでそれらのメソッドを書き直す必要がなくなり、最小限のコード変更でスーパークラスを交換できます。

単一継承のsuper()

オブジェクト指向プログラミングの概念に慣れていない場合は、inheritanceはなじみのない用語である可能性があります。 継承は、オブジェクト指向プログラミングの概念であり、クラスは、属性と動作を再度実装することなく、別のクラスから派生(またはinherits)します。

少なくとも私にとっては、コードを見たときにこれらの概念を理解する方が簡単なので、いくつかの形状を説明するクラスを書きましょう。

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * self.length + 2 * self.width

class Square:
    def __init__(self, length):
        self.length = length

    def area(self):
        return self.length * self.length

    def perimeter(self):
        return 4 * self.length

ここでは、RectangleSquareの2つの類似したクラスがあります。

以下のように使用できます。

>>>

>>> square = Square(4)
>>> square.area()
16
>>> rectangle = Rectangle(2,4)
>>> rectangle.area()
8

この例では、相互に関連する2つの形状があります。正方形は特別な種類の長方形です。 ただし、コードはその関係を反映していないため、本質的に繰り返されるコードが含まれています。

継承を使用することで、作成するコードの量を減らすと同時に、長方形と正方形の実際の関係を反映させることができます。

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * self.length + 2 * self.width

# Here we declare that the Square class inherits from the Rectangle class
class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)

ここでは、super()を使用してRectangleクラスの__init__()を呼び出し、コードを繰り返さずにSquareクラスで使用できるようにしています。 以下では、変更後もコア機能が残ります。

>>>

>>> square = Square(4)
>>> square.area()
16

この例では、Rectangleがスーパークラスであり、Squareがサブクラスです。

SquareメソッドとRectangle.__init__()メソッドは非常に似ているため、Squareのメソッドからスーパークラスの.__init__()メソッド(Rectangle.__init__())を呼び出すだけで済みます。 super()を使用します。 これにより、Squareコンストラクターに単一のlengthパラメーターを指定する必要がある場合でも、.length属性と.width属性が設定されます。

これを実行すると、Squareクラスが明示的に実装していなくても、.area()の呼び出しはスーパークラスの.area()メソッドを使用し、16を出力します。 RectangleクラスのSquareクラスinherited.area()

Note: Pythonの継承とオブジェクト指向の概念の詳細については、必ずObject-Oriented Programming (OOP) in Python 3を確認してください。

super()はあなたのために何ができますか?

では、super()は、単一継承で何ができるのでしょうか。

他のオブジェクト指向言語と同様に、サブクラスでスーパークラスのメソッドを呼び出すことができます。 これの主な使用例は、継承されたメソッドの機能を拡張することです。

以下の例では、Squareから継承し、.area()の機能を拡張するクラスCubeを作成します(RectangleクラスからSquareまで継承) Cubeインスタンスの表面積と体積を計算するには:

class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)

class Cube(Square):
    def surface_area(self):
        face_area = super().area()
        return face_area * 6

    def volume(self):
        face_area = super().area()
        return face_area * self.length

クラスを作成したので、辺の長さが3の立方体の表面積と体積を見てみましょう。

>>>

>>> cube = Cube(3)
>>> cube.surface_area()
54
>>> cube.volume()
27

Caution:上記の例では、super()だけではメソッド呼び出しを行わないことに注意してください。プロキシオブジェクト自体でメソッドを呼び出す必要があります。

ここでは、Cubeクラスに.surface_area().volume()の2つのメソッドを実装しました。 これらの計算はどちらも単一の面の面積の計算に依存しているため、面積の計算を再実装するのではなく、super()を使用して面積の計算を拡張します。

また、Cubeクラス定義には.__init__()がないことに注意してください。 CubeSquareから継承し、.__init__()Squareに対して既に行っているのと実際には何も変わらないため、定義をスキップできます。スーパークラス(Square)の.__init__()が自動的に呼び出されます。

super()はデリゲートオブジェクトを親クラスに返すため、必要なメソッドsuper().area()を直接呼び出します。

これにより、面積計算を書き直す必要がなくなるだけでなく、内部の.area()ロジックを単一の場所で変更することもできます。 これは、1つのスーパークラスから継承するサブクラスが多数ある場合に特に便利です。

super()の詳細

多重継承に進む前に、super()の仕組みを簡単に迂回しましょう。

上記(および下記)の例ではパラメーターなしでsuper()を呼び出していますが、super()は2つのパラメーターを取ることもできます。1つ目はサブクラスで、2つ目はそのサブクラスのインスタンスであるオブジェクトです。

最初に、すでに示したクラスを使用して、最初の変数を操作することで何ができるかを示す2つの例を見てみましょう。

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * self.length + 2 * self.width

class Square(Rectangle):
    def __init__(self, length):
        super(Square, self).__init__(length, length)

Python 3では、super(Square, self)呼び出しは、パラメーターなしのsuper()呼び出しと同等です。 最初のパラメーターはサブクラスSquareを参照し、2番目のパラメーターはSquareオブジェクト(この場合はself)を参照します。 他のクラスでもsuper()を呼び出すことができます。

class Cube(Square):
    def surface_area(self):
        face_area = super(Square, self).area()
        return face_area * 6

    def volume(self):
        face_area = super(Square, self).area()
        return face_area * self.length

この例では、Cubeではなくsuper()のサブクラス引数としてSquareを設定しています。 これにより、super()は、インスタンス階層のSquareの1つ上のレベル(この場合はRectangle)で一致するメソッド(この場合は.area())の検索を開始します。

この特定の例では、動作は変わりません。 しかし、SquareCubeが使用しないようにしたい.area()関数も実装したと想像してください。 この方法でsuper()を呼び出すと、それが可能になります。

Caution:内部でどのように機能するかを調べるために、super()のパラメータをいじくり回している間、これを定期的に行うことには注意が必要です。

ほとんどのユースケースでは、パラメーターなしのsuper()の呼び出しが推奨され、十分であり、検索階層を定期的に変更する必要がある場合は、設計上の問題が大きいことを示している可能性があります。

2番目のパラメーターはどうですか? これは、最初のパラメーターとして使用されるクラスのインスタンスであるオブジェクトであることを忘れないでください。 たとえば、isinstance(Cube, Square)Trueを返す必要があります。

インスタンス化されたオブジェクトを含めることにより、super()bound methodを返します。これはオブジェクトにバインドされたメソッドであり、インスタンス属性などのオブジェクトのコンテキストをメソッドに提供します。 このパラメーターが含まれていない場合、返されるメソッドは、オブジェクトのコンテキストに関連付けられていない単なる関数です。

バインドされたメソッド、バインドされていないメソッド、および関数の詳細については、Pythonのドキュメントon its descriptor systemを参照してください。

Note:技術的には、super()はメソッドを返しません。 proxy objectを返します。 これは、追加のオブジェクトを作成せずに適切なクラスメソッドへの呼び出しを委任するオブジェクトです。

多重継承のsuper()

概要とsuper()と単一継承のいくつかの例を実行したので、複数の継承がどのように機能し、super()がその機能をどのように有効にするかを示す概要といくつかの例を紹介します。

多重継承の概要

super()が実際に輝く別のユースケースがあり、これは単一継承シナリオほど一般的ではありません。 Pythonは、単一の継承に加えて、多重継承をサポートしています。多重継承では、サブクラスは、必ずしも相互に継承する必要のない複数のスーパークラスから継承できます(sibling classesとも呼ばれます)。

私は非常に視覚的な人物であり、図はこのような概念を理解するのに非常に役立ちます。 以下の画像は、1つのクラスが2つの無関係な(兄弟)スーパークラスから継承する非常に単純な多重継承シナリオを示しています。

動作中の多重継承をわかりやすく説明するために、TriangleSquareから正しいピラミッド(正方形の底面を持つピラミッド)を構築する方法を示す、試してみるコードを次に示します。

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return 0.5 * self.base * self.height

class RightPyramid(Triangle, Square):
    def __init__(self, base, slant_height):
        self.base = base
        self.slant_height = slant_height

    def area(self):
        base_area = super().area()
        perimeter = super().perimeter()
        return 0.5 * perimeter * self.slant_height + base_area

Note:slant heightという用語は、特にジオメトリクラスを受講したり、ピラミッドで作業したりしてからしばらく経っている場合は、なじみがない場合があります。

傾斜高さは、オブジェクトの底の中心(ピラミッドのような)からそのオブジェクトの頂点までの高さです。 傾斜高さの詳細については、WolframMathWorldを参照してください。

この例では、TriangleクラスとSquareTriangleの両方から継承するRightPyramidクラスを宣言します。

単一継承の場合と同じようにsuper()を使用する別の.area()メソッドが表示されます。これは、で定義されている.perimeter()メソッドと.area()メソッドに到達することを目的としています。 Rectangleクラス。

Note:上記のコードは、Triangleクラスから継承されたプロパティをまだ使用していないことに気付くかもしれません。 後の例では、TriangleSquareの両方からの継承を十分に活用します。

ただし、問題は、両方のスーパークラス(TriangleSquare)が.area()を定義することです。 少し時間を取って、RightPyramid.area()を呼び出すとどうなるかを考えてから、次のように呼び出してみてください。

>>>

>> pyramid = RightPyramid(2, 4)
>> pyramid.area()
Traceback (most recent call last):
  File "shapes.py", line 63, in 
    print(pyramid.area())
  File "shapes.py", line 47, in area
    base_area = super().area()
  File "shapes.py", line 38, in area
    return 0.5 * self.base * self.height
AttributeError: 'RightPyramid' object has no attribute 'height'

PythonがTriangle.area()を呼び出そうとすると思いましたか? これは、method resolution orderと呼ばれるものが原因です。

Note:Triangle.area()が呼び出され、期待どおりにSquare.area()が呼び出されなかったことにどのように気づきましたか? トレースバックの最後の行(AttributeErrorの前)を見ると、特定のコード行への参照が表示されます。

return 0.5 * self.base * self.height

ジオメトリクラスからこれを三角形の面積の式として認識することができます。 そうでなければ、あなたが私のようであれば、TriangleRectangleのクラス定義までスクロールして、Triangle.area()に同じコードが表示されている可能性があります。

メソッド解決順序

メソッド解決順序(またはMRO)は、継承されたメソッドを検索する方法をPythonに指示します。 これは、super()を使用している場合に便利です。これは、MROが、super()で呼び出しているメソッドをPythonが検索する場所とその順序を正確に通知するためです。

すべてのクラスには、順序を検査できる.__mro__属性があるので、それを実行しましょう。

>>>

>>> RightPyramid.__mro__
(, ,
 , ,
 )

これは、メソッドが最初にRightpyramid、次にTriangle、次にSquare、次にRectangleで検索され、何も見つからない場合はobject、すべてのクラスが由来します。

ここでの問題は、インタプリタがSquareRectangleの前にTriangle.area()を検索し、Triangle.area()を見つけるとPythonであるということです。あなたが望むものの代わりにそれを呼びます。 Triangle.area().height属性と.base属性があることを想定しているため、PythonはAttributeErrorをスローします。

幸いなことに、MROの構築方法をある程度制御できます。 RightPyramidクラスのシグネチャを変更するだけで、必要な順序で検索でき、メソッドは正しく解決されます。

class RightPyramid(Square, Triangle):
    def __init__(self, base, slant_height):
        self.base = base
        self.slant_height = slant_height
        super().__init__(self.base)

    def area(self):
        base_area = super().area()
        perimeter = super().perimeter()
        return 0.5 * perimeter * self.slant_height + base_area

RightPyramidは、Squareクラスの.__init__()で部分的に初期化されることに注意してください。 これにより、.area()は、設計どおりにオブジェクトの.lengthを使用できます。

これで、ピラミッドを構築し、MROを検査し、表面積を計算できます。

>>>

>>> pyramid = RightPyramid(2, 4)
>>> RightPyramid.__mro__
(, ,
, ,
)
>>> pyramid.area()
20.0

MROが期待どおりになり、.area().perimeter()のおかげで、ピラミッドの領域も検査できることがわかります。

ただし、ここにはまだ問題があります。 簡単にするために、この例ではいくつか間違ったことをしました。最初の、そしておそらく最も重要なことは、同じメソッド名とシグネチャを持つ2つの別個のクラスがあったことです。

これにより、MROリストで検出された.area()の最初のインスタンスが呼び出されるため、メソッドの解決に問題が発生します。

多重継承でsuper()を使用している場合は、クラスをcooperateに設計することが不可欠です。 これの一部は、メソッド名またはメソッドパラメーターを使用してメソッドシグネチャが一意であることを確認することにより、MROで解決されるようにメソッドが一意であることを保証することです。

この場合、コードの完全なオーバーホールを回避するために、Triangleクラスの.area()メソッドの名前を.tri_area()に変更できます。 このように、エリアメソッドは外部パラメーターを取得するのではなく、クラスプロパティを引き続き使用できます。

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height
        super().__init__()

    def tri_area(self):
        return 0.5 * self.base * self.height

また、これをRightPyramidクラスで使用してみましょう。

class RightPyramid(Square, Triangle):
    def __init__(self, base, slant_height):
        self.base = base
        self.slant_height = slant_height
        super().__init__(self.base)

    def area(self):
        base_area = super().area()
        perimeter = super().perimeter()
        return 0.5 * perimeter * self.slant_height + base_area

    def area_2(self):
        base_area = super().area()
        triangle_area = super().tri_area()
        return triangle_area * 4 + base_area

ここでの次の問題は、コードにSquareオブジェクトの場合のように委任されたTriangleオブジェクトがないため、.area_2()を呼び出すとAttributeErrorが得られることです。 (t4)sと.heightには値がありません。

これを修正するには、2つのことを行う必要があります。

  1. super()で呼び出されるすべてのメソッドは、そのメソッドのスーパークラスのバージョンを呼び出す必要があります。 これは、TriangleおよびRectangle.__init__()メソッドにsuper().__init__()を追加する必要があることを意味します。

  2. すべての.__init__()呼び出しを再設計して、キーワード辞書を取得します。 以下の完全なコードを参照してください。

このコードには多くの重要な違いがあります。

  • kwargs is modified in some places (such as RightPyramid.__init__()):これにより、これらのオブジェクトのユーザーは、その特定のオブジェクトに意味のある引数を使用してのみそれらをインスタンス化できます。

  • Setting up named arguments before **kwargs:これはRightPyramid.__init__()で確認できます。 これには、そのキーを**kwargsディクショナリからすぐにポップするという優れた効果があるため、objectクラスのMROの最後に到達するまでに、**kwargsは次のようになります。空の。

Note:ここでは、kwargsの状態を追跡するのが難しい場合があるため、.__init__()呼び出しの表を順番に示し、その呼び出しを所有するクラスとkwargsの内容を示します。その通話中:

クラス 名前付き引数 kwargs

RightPyramid

baseslant_height

Square

length

baseheight

Rectangle

lengthwidth

baseheight

Triangle

baseheight

これらの更新されたクラスを使用すると、次のようになります。

>>>

>>> pyramid = RightPyramid(base=2, slant_height=4)
>>> pyramid.area()
20.0
>>> pyramid.area_2()
20.0

できます! super()を使用して複雑なクラス階層を正常にナビゲートし、継承と構成の両方を使用して、最小限の再実装で新しいクラスを作成しました。

複数の継承の選択肢

ご覧のとおり、多重継承は有用ですが、非常に複雑な状況や読みにくいコードにもつながります。 また、他の複数のオブジェクトからすべてをきちんと継承するオブジェクトを持つことはまれです。

多重継承と複雑なクラス階層を使い始めていることに気付いた場合は、継承の代わりにcompositionを使用することで、よりクリーンで理解しやすいコードを実現できるかどうかを自問する価値があります。

構成を使用すると、mixinと呼ばれる特殊で単純なクラスから、非常に特殊な機能をクラスに追加できます。

この記事は継承に焦点を当てているので、構成とPythonでの使用方法についてはあまり詳しく説明しませんが、VolumeMixinを使用して3Dオブジェクトに特定の機能を与える短い例を次に示します。この場合は、体積計算:

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    def area(self):
        return self.length * self.width

class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)

class VolumeMixin:
    def volume(self):
        return self.area() * self.height

class Cube(VolumeMixin, Square):
    def __init__(self, length):
        super().__init__(length)
        self.height = length

    def face_area(self):
        return super().area()

    def surface_area(self):
        return super().area() * 6

この例では、VolumeMixinと呼ばれるミックスインを含むようにコードが作り直されました。 次に、ミックスインはCubeによって使用され、Cubeにそのボリュームを計算する機能を提供します。これを以下に示します。

>>>

>>> cube = Cube(2)
>>> cube.surface_area()
24
>>> cube.volume()
8

このミックスインは、領域が定義されていて、式area * heightが正しいボリュームを返すクラスで同じように使用できます。

super()の要約

このチュートリアルでは、super()でクラスを過給する方法を学びました。 あなたの旅は、単一継承のレビューから始まり、super()を使用してスーパークラスメソッドを簡単に呼び出す方法を示しました。

次に、Pythonで多重継承がどのように機能するか、およびsuper()を多重継承と組み合わせる手法を学びました。 また、Pythonがメソッド解決順序(MRO)を使用してメソッド呼び出しを解決する方法、およびMROを検査および変更して適切なメソッドが適切なタイミングで呼び出されるようにする方法についても学びました。

Pythonでのオブジェクト指向プログラミングとsuper()の使用の詳細については、次のリソースを確認してください。