Don't be scared! Moreover, unless you make strong use of multiple inheritance and you have non-trivial hierarchies, you don't need to understand the C3 algorithm, and you can easily skip this paper. On the other hand, if you really want to know how multiple inheritance works, then this paper is for you. The good news is that things are not as complicated as you might expect.
藤本 博子さんの投稿
(投稿ID: 5422)
重ねての質問となり恐縮でございますが、どうぞよろしくお願いいたします。
■多重継承するクラス両方に、__init__メソッドがある場合
・動画sample44.py class Stock(CSVMixin, BaseAsset):の場合は、BaseAssetのみ、__init__メソッドがあります。super()でBaseAssetのコンストラクタを呼び出しました。
・part09_52_multiple_inheitance.pyのclass WrappedStock(CSVDealsMixin, Asset):の場合は、両方に__init__メソッドがあります。
super()でCSVDealsMixinのクラスを明示しなくても、CSVMixinの__init__メソッドでコンストラクタを呼び出されました。
多重継承のクラスで、super()を呼び出すときは、左側の継承先のクラスのインスタンスメソッドが呼び出されるという理解で問題ございませんでしょうか。
継承の順番を注意する必要があるのだと思いました。
■コードがエラーになる箇所
以下2点気になりました。
[1]part09_52_multiple_inheritance.py 10行目
クラスとインスタンス read.md P13にも同じサンプルコードがございます。
class WrappedStockで、super()で継承先のクラスのコンストラクタを呼び出すコードがございます。
引数(name, base_price * total_weight)を渡すコードのため、
TypeError: CSVDealsMixin.__init__() missing 1 required positional argument: 'amount'というエラーが表示されました。
super()でmixins.pyのclass CSVDealsMixin:のコンストラクタへ変数を渡しますが、
受取る引数は(self, name, base_price, amount)なので、以下のコードになるかと思いました。
元のコード
エラーのメッセージ
以下の引数で渡すと実行できました。
[2]mixins.py 45行目 def sell(self, units):でget_current_price_per_unitというインスタンスメソッドを指定しますが、get_current_price_per_unit_from_csv()だと思いました。
エラーのメッセージ
以下のインスタンスメソッドを指定すると、part09_52_multiple_inheritance.py 26行目
print(orange.sell(3000))の戻り値が売値150*3000=450,000となります。
■動画の講座でCSVファイルを解析するmixinクラスをDictreaderで読み込むと、
引数の内容が分かりやすくなっていいと思いました。
藤本 博子さんのコメント
(コメントID: 7976)
print(orange.sell(3000))の戻り値が売値150*3000=450,000となります。
のコードの貼り付けを間違えました。
だと、戻り値を取得することができました。
藤本 博子さんのコメント
(コメントID: 7996)
・メソッド検索の優先順位を確認する mro() クラスメソッドがある
・pythonにおいては、多重継承の順位をMRO(Method Resolutin Order)という仕組みでC3アルゴリズムを用いて探索する
という情報をみつけました。
part9_52_multiple_inheritance.pyのWrappedStockクラスで試してみました。
WrappedStockクラス多重継承のクラス定義をしています。
カッコ内にCSVDealsMixin, Assetと2つの親クラスを示しています。
結果は、左側のDealsMixin,次に Assetの順番でした。
part9_52_multiple_inheritance.py
「MROは「深さ優先、左から右、の順番で検索をするが、検索ルートの中で特定のクラスが複数回出てきた場合には後回しとなるよう」に基本的には決定される。
「後回し」とは複数ある直接基底クラスが共通する基底クラスを持っていた場合、その基底クラスは直接基底クラスよりも検索順が後になるということだ」
とのことでした。
以下リンクの説明を参考しました。
https://atmarkit.itmedia.co.jp/ait/articles/1611/11/news030_4.html
前提として、多重継承するのは大規模な開発で使用されるケース。
使用することにより、継承関係把握が難しくなり混乱を招く可能性があり、ならべく使わない仕組みを考えることが必要だと思います。
とはいえ、なんとなく気になったので、調べてみてよかったと思いました。
・pythonの多重継承 C3線形化のアルゴリズムの公式ドキュメントを読みましたが、難しすぎて分かりませんでした。
https://www.python.org/download/releases/2.3/mro/
以下の説明を読んで、読み飛ばそうと思いました…
Don't be scared!
Moreover, unless you make strong use of multiple inheritance and you have non-trivial hierarchies, you don't need to understand the C3 algorithm, and you can easily skip this paper. On the other hand, if you really want to know how multiple inheritance works, then this paper is for you. The good news is that things are not as complicated as you might expect.
また、多重継承を強く利用し、自明でない階層を持つのでなければ、C3アルゴリズムを理解する必要はなく、本論文は簡単に読み飛ばすことができます。一方、多重継承がどのように機能するかを本当に知りたいのであれば、この論文はあなたのためのものです。良い知らせは、物事はあなたが期待するほど複雑ではないということです。
・スーパークラスを直接指定する
super()を使わずに、スーパークラス名を直接指定することができるようですね。
とはいえ、まずはシンプルで多重継承しなくてよい構造化を目指します。
小川 慶一さんのコメント
(コメントID: 7999)
同名のメソッドが複数継承元にある場合、どのメソッドとどう関わるのかということを決めるのが、MRO(Metho Resolution Order = メソッド解決順序)です。
> 「後回し」とは複数ある直接基底クラスが共通する基底クラスを持っていた場合、その基底クラスは直接基底クラスよりも検索順が後になるということだ
「複数の継承を辿っていったら、どちらの Mixin も同じ親を継承元としていた」というような場合がややこしいです。
ご理解されていることと思いますが、上記は、そういう場合について述べています。
ですが、かなりの edge case ですので、その場合のアルゴリズムまで理解できている必要はないです。
藤本さん自身が言われているとおり、もしも自分で作ったプログラム内だけでそこまで考えなくてはならないような多重継承になっていたとしたら、そもそも設計がおかしいです。
人が作った巨大ライブラリでも、果たして、この件で悩むことがどれだけあるか...というところ。
悩むとしても、前段の前半部、「深さ優先、左から右、の順番で検索をする」だけ理解できていればまず解決します。
僕自身も、「上記『後回し』にでくわして挙動で困った」という経験は一度もありません。
> super()を使わずに、スーパークラス名を直接指定することができるようですね。
以下の [1] の行は WrappedStock インスタンスのメソッドではなく、純粋に、CSVDealsMixin クラスの __init__ メソッドを呼び出しているだけです。
「super()を使わずに、スーパークラス名を直接指定している」わけでないです。
(この呼び出し方の場合、呼び出し先が自身のスーパークラスである必要がない)
クラス内で定義されたメソッド内で呼び出されているので、上記のような感想を持たれたのかもしれません。
以下のようなサンプルを書いてみました。
これだとどうでしょう。 [a] はともかく、 [b] は、 NetInfoMixin クラスで定義されたメソッドに、第一引数として john インスタンスを放り込んでいます。
以下は、もっと直接的。
藤本 博子さんのコメント
(コメントID: 8000)
MROの件についてご回答くださいまして、ありがとうございます。
どのあたりまで自分が理解しておけばいいのか確認することができました。
※クラスの呼び出しについて
今回解説いただいたことで、自分の理解が足りてないことに気づきました。
・クラスは、クラス属性にアクセスできるが、インスタンス属性にはアクセスできません。
(クラス.インスタンスメソッド()という書き方)
・しかし、ご提示いただいたサンプルコードは、クラス.インスタンスメソッド(引数 インスタンス変数)とインスタンスを引数で渡すことにより、インスタンスメソッドにアクセスできるし、呼び出し先のクラスのインスタンスメソッド内でインスタンスのプロパティにアクセスできると分かりました。
CSVDealsMixin.__init__(self,name, base_price ,total_weight)も同じことなのに、ご提示いただいたサンプルコードのような考えに、私は到達出来ておりませんでした。
サンプルコード2番目は、コンストラクタで初期化するクラス(Person)は、Mixinクラス(NexInfoMixin)を継承しません。
しかし、クラス.インスタンスメソッド(引数 インスタンス)とインスタンスを渡すカラブルで、インスタンスのプロパティにアクセスできるのですね。自分の理解では考えが及びませんでした。
・インスタンスメソッドを呼び出すケースについて、以下2つは自分も想定内でした。
インスタンスは、インスタンス属性にアクセスできる。
(1)小川先生のサンプルコード1番目の[a] インスタンス変数.インスタンスメソッド()
(2)継承元のクラスで継承先のmixinクラスのインスタンスメソッドを呼び出す
なお、動画9:00 多重継承したクラスの中で、自由にクラス変数にアクセスできる。という解説があり、多重継承するクラス(CSVMixinクラス)の中で、self.type_nameと書いても動く。
Sample44.py
呼び出し元のクラス(Stock)のインスタンスメソッドで、mixinクラス(CSVMixin)のインスタンスメソッドを呼び出す。
mixinクラスのインスタンスメソッドは、引数でresponseオブジェクトと列数の番号を受取り、responseオブジェクトを改行文字毎に分割・リスト化したインデックス番号1の値とself.type_nameが等しかったら…という条件分岐の処理をして戻り値を返す。
まだまだ理解や思考が及びませんが、少しづつ勉強していきたいです。
ありがとうございました。
Sample44.py
小川 慶一さんのコメント
(コメントID: 8002)
(クラスを元にして生成されたインスタンスもオブジェクトですが、クラス自体もオブジェクト。)
[参考] 3. データモデル > 3.1. オブジェクト、値、および型
https://docs.python.org/ja/3/reference/datamodel.html#objects-values-and-types
以下、復習をかねて。
Pythonでは、あらゆるものがオブジェクトです。クラスインスタンスだけでなく、クラスも、関数も、オブジェクト。id, 型, 値を持ちます。
以下、その前提を踏まえて。
上のサンプルでは、「CSVDealsMixin というオブジェクトが __init__ という名前のメソッドを持っていたので、それを使ってみた」というだけのことです。
そして、このメソッドは、self, name, base_price, total_weight という4つの引数を受け取るそうなので、 self にはクラスインスタンス、 name には、 base_price には、 ... と、引数を渡していっただけ。
インスタンスが自身のメソッドを呼び出すときは、 self には自動的に自分自身が割り当てられるので、 name, base_price, total_weight という3つの引数だけ渡せば良いです。
抽象度がさらに高い再説明ですが...。
この説明、どうでしょうか。
もっとも、クラスの本来の使い方ではないので、こういうやり方は避けたほうが良いです。
毎度の話になりますが、こういう迂回策を取らなくてはならないとしたら、そもそもの設計のところを見直したほうがよいです。
藤本 博子さんのコメント
(コメントID: 8004)
ご回答ありがとうございます。
Pythonでは、あらゆるものがオブジェクト。
文字列、リストや辞書などのデータ型ごとに、必要なメソッドを定義できる。
自ら型を定義し作ったclass(CSVDealsMixin)は、
・「CSVDealsMixin というオブジェクトが __init__ という名前のメソッドを持っていたので、それを使ってみた」
・そして、このメソッドは、self, name, base_price ,total_weight という4つの引数を受け取るそうなので、 self にはクラスインスタンス、 name には、 base_price には、 ... と、引数を渡していっただけ。
と混乱なく理解できるようになりました。
>インスタンスが自身のメソッドを呼び出すときは、 self には自動的に自分自身が割り当てられるので、 name, base_price ,total_weight という3つの引数だけ渡せば良いです。
動画「インスタンスを作ってみる」09:30で、インスタンスが自身のメソッドを呼び出すときの説明をいただきました。
>もっとも、クラスの本来の使い方ではないので、こういうやり方は避けたほうが良いです。
毎度の話になりますが、こういう迂回策を取らなくてはならないとしたら、そもそもの設計のところを見直したほうがよいです。
はい。このような迂回策をとるような設計はしないよう心がけます。
今回たくさんご教示いただけたので、オブジェクト指向が馴染んできたように思います。
そして、 2022年11月3日21:45に小川先生がリライトくださったクラスのコードをあらためて書いて動かしました。
https://forum.pc5bai.com/note/entry/cae64c89-0664-4dbf-bbac-6abdea8b61e0/
クラスの講座を一通り勉強して、読めるようになると、感動がすごいです。
__call__メソッドで、インスタンス名を関数のようにして呼び出すメリットが、このコードで実感をもって分かりました。
他にもメソッドの使い方、初期化でまとめて行う処理、どれも大変勉強になりました。
5回繰り返し書いて、染み込ませました。
現時点ではお手本を見ないで書けますが、また忘れてしまわないよう繰り返し書きます。
とても貴重なリライトをしていただけたなぁと、しみじみ思いました。
ありがとうございます。