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

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

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

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

  • Pythonの継承の概念

  • Pythonの多重継承

  • `+ super()+`関数の仕組み

  • 単一継承での `+ super()+`関数の仕組み

  • 多重継承での `+ super()+`関数の仕組み

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

Pythonの `+ super()+`関数の概要

オブジェクト指向言語の経験がある場合は、すでに `+ super()+`の機能に精通しているかもしれません。

そうでない場合でも、恐れないでください! https://docs.python.org/3/library/functions.html#super [公式ドキュメント]はかなり技術的ですが、高レベルでは、 `+ super()+`により、スーパークラスのメソッドにアクセスできます。それを継承するサブクラス。

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

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

以前にビルドしたメソッドを `+ super()+`で呼び出すと、サブクラスでこれらのメソッドを書き換える必要がなくなり、最小限のコード変更でスーパークラスを交換できます。

単一継承の + super()+

オブジェクト指向プログラミングの概念に慣れていない場合、「継承」はなじみのない用語かもしれません。 継承はオブジェクト指向プログラミングの概念であり、クラスは属性と動作を別のクラスから派生(または*継承*)し、それらを再度実装する必要はありません。

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

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

ここには、2つの類似したクラスがあります: + Rectangle +`と `+ Square +

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

>>>

>>> 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 ()+`メソッドは非常に似ているため、スーパークラスの `+ . init ()+`メソッド( `+ Rectangle . init ()+)を呼び出すだけです。 `+ super()`を使用して、 ` Square `のものから。 これにより、単一の ` length `パラメータを ` Square `コンストラクタに指定するだけで、 ` .length `および ` .width +`属性が設定されます。

これを実行すると、 `+ Square `クラスは明示的に実装しませんが、 ` .area()`の呼び出しはスーパークラスで ` .area()`メソッドを使用し、 ` 16 + `。 `+ Square `クラス *inherited* ` .area()`の ` Rectangle +`クラス。

*注意:*継承とPythonのオブジェクト指向の概念の詳細については、https://realpython.com/python3-object-oriented-programming/[Python 3のオブジェクト指向プログラミング(OOP)]を確認してください。 。

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

それで、単一継承で `+ super()+`は何をすることができますか?

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

以下の例では、 + Square +`を継承し、 `+ .area()+( `+ Rectangle `クラスから ` Square `を継承)の機能を拡張するクラス ` Cube `を作成します。 ` 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

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

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

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

+ super()+`は、デリゲートオブジェクトを親クラスに返すため、目的のメソッドを直接呼び出します: `+ super()。area()+

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

`+ super()+`ディープダイブ

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

上記の(および以下の)例ではパラメーターなしで `+ super()`を呼び出しますが、 ` super()+`は2つのパラメーターを取ることもできます。最初のパラメーターはサブクラスで、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

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

この特定の例では、動作は変わりません。 しかし、 `+ Square `が ` .area()`関数を実装し、 ` Cube `が使用しないことを確認したいと考えてください。 この方法で ` super()+`を呼び出すと、それが可能になります。

*注意:*ボンネットの下でどのように機能するかを調べるために、 `+ super()+`のパラメーターをいじくり回していますが、定期的にこれを行うことには注意してください。

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

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

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

バインドされたメソッド、バインドされていないメソッド、および関数の詳細については、Pythonドキュメントhttps://docs.python.org/3.7/howto/descriptor.html [その記述子システムについて]を参照してください。

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

多重継承の + super()+

`+ super()`と単一継承の概要といくつかの例について説明したので、次に、多重継承の仕組みと ` super()+`の仕組みを説明する概要といくつかの例を紹介します。その機能を有効にします。

多重継承の概要

`+ super()+`が本当に輝くもう1つのユースケースがありますが、これは単一継承シナリオほど一般的ではありません。 Pythonは、単一の継承に加えて、複数の継承をサポートしています。サブクラスは、必ずしも相互に継承する必要のない複数のスーパークラス(兄弟クラス)から継承できます。

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

実行中の多重継承をよりわかりやすく説明するために、試してみるコードをいくつか示します。これは、「+ Triangle 」と「 Square +」から正しいピラミッド(正方形の底面を持つピラミッド)を構築する方法を示しています。

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

*注:*ジオメトリクラスを受講したり、ピラミッドで作業してからしばらく経っている場合は特に、*スラントハイト*という用語はよく知らない場合があります。

傾斜高さは、オブジェクトの底の中心(ピラミッドのような)からそのオブジェクトの頂点までの高さです。 傾斜高さの詳細については、http://mathworld.wolfram.com/SlantHeight.html [WolframMathWorld]をご覧ください。

この例では、 `+ Triangle `クラスと、 ` Square `と ` Triangle `の両方を継承する ` RightPyramid +`クラスを宣言しています。

別の + .area()+`メソッドが表示されます。このメソッドは、単一の継承と同じように、 `+ .perimeter()+`と `+ .area( )+ `メソッドは、 + Rectangle + `クラスですべて定義されています。

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

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

>>>

>> pyramid = RightPyramid(2, 4)
>> pyramid.area()
Traceback (most recent call last):
  File "shapes.py", line 63, in <module>
    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 と呼ばれるものが原因です。

*注意:*希望どおりに `+ Square.area()`ではなく、 ` Triangle.area()`が呼び出されたことにどのように気付きましたか? トレースバックの最後の行( ` AttributeError +`の前)を見ると、特定のコード行への参照が表示されます。

return 0.5 *self.base* self.height

ジオメトリクラスからこれを三角形の面積の式として認識することができます。 そうでなければ、あなたが私のようであれば、 `+ Triangle `と ` Rectangle `クラス定義までスクロールして、 ` Triangle.area()+`で同じコードを見たかもしれません。

メソッド解決順序

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

すべてのクラスには、順序を検査できるようにする `+ . mro +`属性があります。

>>>

>>> RightPyramid.__mro__
(<class '__main__.RightPyramid'>, <class '__main__.Triangle'>,
 <class '__main__.Square'>, <class '__main__.Rectangle'>,
 <class 'object'>)

これにより、メソッドは最初に「+ Rightpyramid 」、次に「 Triangle 」、次に「 Square 」、次に「 Rectangle 」、次に「 object +」で検索されることがわかります。すべてのクラスの起源。

ここでの問題は、インタープリターが「+ Square 」と「 Rectangle 」の前に「 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__
(<class '__main__.RightPyramid'>, <class '__main__.Square'>,
<class '__main__.Rectangle'>, <class '__main__.Triangle'>,
<class 'object'>)
>>> pyramid.area()
20.0

MROが期待どおりのものであることがわかります。また、 `+ .area()`と ` .perimeter()+`のおかげで、ピラミッドのエリアも検査できます。

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

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

多重継承で `+ super()+`を使用している場合、*協力*するようにクラスを設計することが不可欠です。 これの一部は、メソッド名またはメソッドパラメーターを使用してメソッドシグネチャが一意であることを確認することにより、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 + ` + .base + および + .height + `には値がありません。

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

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

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

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

  • * `+ kwargs `は一部の場所で変更されます( ` RightPyramid . init ()+`など):*これにより、これらのオブジェクトのユーザーは、その特定のオブジェクトに意味のある引数でのみインスタンス化できます。

  • 名前付き引数を `+ * kwargs `の前に設定する:*これは ` RightPyramid . init ()`で見ることができます。 これには、そのキーを ` * kwargs `辞書から直接ポップするというきちんとした効果があるため、 ` object `クラスの ` * kwargs +`でMROの最後に達するまでに空です。

*注意:*ここでは `+ kwargs `の状態を追跡するのが難しい場合があるため、 ` . init ()`呼び出しのテーブルを順番に示し、その呼び出しを所有するクラスと ` kwargs +`の内容を示しますその呼び出し中:

Class Named Arguments kwargs

RightPyramid

base, slant_height

Square

length

base, height

Rectangle

length, width

base, height

Triangle

base, height

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

>>>

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

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

複数の継承の選択肢

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

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

コンポジションを使用すると、 mixin と呼ばれる特殊な単純なクラスから非常に具体的な機能をクラスに追加できます。

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

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()+`の使用の詳細については、次のリソースをご覧ください。