Python のクラス変数とインスタンス変数って何?




インスタンス
変数
そのインスタンスだけで
使う変数
  
クラス
変数
すべてのインスタンス
共有して使う変数

























f:id:domodomodomo:20171106122644j:plain


さて、これはどう言うことでしょうか?実際に操作をしながらクラス変数とインスタンス変数の違いを、もう少し追いかけてみたいと思います。


表. クラス変数とインスタンス変数の違い
違い クラス変数 インスタンス変数
1. 使い分け 全てのインスタンス
共有する値
そのインスタンス
だけで使う値
2. 定義場所 クラス定義文の直下で
代入された変数
関数定義文の直下で
代入された self の属性
3. クラス
オブジェクト
から参照
できる できない
4. インスタンス
オブジェクト
から参照
できる できる
5. 変更すると 全ての
インスタンスの属性
が変更される。
その
インスタンスの属性
だけが変更される。




違い 2. 定義する場所
クラス変数とインスタンス変数は、どこで定義するの?

例えば、恋愛ゲームについて考えましょう。彼女 GirlFriend には名前 name, 彼氏への親密度 intimacy があり最小値 0, 最大値 100 とします。

× 間違った書き方

class GirlFriend(object):                                                                
  max_intimacy = 100
  min_intimacy = 0
  intimacy = 0
  name = ''

ο 正しい書き方

class GirlFriend(object):
  # インスタンスオブジェクト間で共有される変数
  max_intimacy = 100
  min_intimacy = 0
  
  # インスタンスオブジェクト間で共有しない変数
  def __init__(self, name):
    self.name =  name
    self.intimacy = 0

2.1. クラス変数

クラス定義文の直下で変数に代入。各インスタンスオブジェクトで共有しない変数は、クラス変数として定義します。

class GirlFriend(object):
  # クラス変数
  max_intimacy = 100                                                               
  min_intimacy = 0                                                                 

2.2. インスタンス変数

関数定義文の直下で代入された self の属性に代入。各インスタンスで共有しない値は、名前 name と親密性 intimacy は、__init__ 関数の中で定義してインスタンス変数に代入します。

class GirlFriend(object):
  def __init__(self, name):
    # インスタンス変数
    self.name =  name
    self.intimacy = 0




違い 3. 参照
クラスブジェクトから参照できるか、できないか

引き続き、このような GirlFriend クラスについて考えます。

class GirlFriend(object):
  # インスタンスオブジェクト間で共有される変数
  max_intimacy = 100
  min_intimacy = 0
  
  # インスタンスオブジェクト間で共有しない変数
  def __init__(self, name):
    self.name =  name
    self.intimacy = 0

3.1. クラスオブジェクトからクラス変数を参照できるか?

答え: できる

クラスオブジェクトは、クラス定義の内部で属性に代入されたインスタンスオブジェクトを、外部から参照できる仕様になっています。

# 参照できる
GirlFriend.max_intimacy
GirlFriend.min_intimacy
>>> # 参照できる
>>> GirlFriend.max_intimacy
100
>>> GirlFriend.min_intimacy
0
>>>

3.2. クラスオブジェクトからインスタンス変数を参照できるか?

答え: できない

# 参照できない
GirlFriend.name
GirlFriend.intimacy
>>> # 参照できない
... GirlFriend.name
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: type object 'GirlFriend' has no attribute 'name'
>>> GirlFriend.intimacy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'GirlFriend' has no attribute 'intimacy'
>>> 

◯ 何でインスタンス変数には頭に self が必要なの?

答え: 関数の中で使われた "値" は、"オブジェクトの属性" に代入してもらわないと外から見えないから。

class GirlFriend(object):
  # self は不要
  max_intimacy = 100
  min_intimacy = 0
  
  # self が必要
  def __init__(self, name):
    self.name =  name
    self.intimacy = 0


関数定義文とクラス定義文は、異なるものです。 関数定義文とクラス定義文は、パッと見の構造が似ているので混同しがちですが、全く動作が違います。

#
# クラス定義文
#
class GirlFriend(object):
  max_intimacy = 100
  min_intimacy = 0

# クラス定義文の中で書かれた値 100, 0 は
# 外から見える。
GirlFriend.max_intimacy == 100
GirlFriend.min_intimacy == 0


#
# 関数定義文
#
def __init__(self):
  self.name =  '岩倉玲音'
  intimacy = 0

# 関数定義文の中で書かれた値 '岩倉玲音', 0 は
# 属性に代入しないと
# 外から見えない。
girl_friend = GirlFriend()
__init__(girl_friend, '岩倉玲音')
girl_friend.name == '岩倉玲音'
girl_friend.intimacy == 0  # AttributeError



違い 4. 参照
インスタンスブジェクトから参照できるか、できないか


# インスタンスオブジェクトとは
# 変数に代入されたもの
girl_friend = GirlFriend('サーバルちゃん')

4.1 インスタンスオブジェクトからクラス変数を参照できるか?

答え: できる

# 参照できる
girl_friend.max_intimacy
girl_friend.min_intimacy
>>> # 参照できる
>>> girl_friend.max_intimacy
100
>>> girl_friend.min_intimacy
0
>>> 

4.2. インスタンスオブジェクトからインスタンス変数を参照できるか?

答え: できる

# 参照できる
girl_friend.name
girl_friend.intimacy
>>> # 参照できる
>>> girl_friend.name
'サーバルちゃん'
>>> girl_friend.intimacy
0
>>> 

違い 5. 変更
クラス変数とインスタンス変数を変更してみる。

5.1. クラス変数を変更する。

クラス変数を変更すると、そのクラスからインスタンス化された、すべてのインスタンスオブジェクトのクラス変数が変更されます。

girl_friend1 = GirlFriend('サーバルちゃん')
girl_friend2 = GirlFriend('岩倉玲音')
girl_friend1.max_intimacy
girl_friend2.max_intimacy

# クラス変数を変更する。
GirlFriend.max_intimacy = 50
girl_friend1.max_intimacy
girl_friend2.max_intimacy
>>> girl_friend1 = GirlFriend('サーバルちゃん')
>>> girl_friend2 = GirlFriend('岩倉玲音')
>>> girl_friend1.max_intimacy
100
>>> girl_friend2.max_intimacy
100
>>> 
>>> 
>>> # クラス変数を変更する。
>>> girl_friend1.max_intimacy
50
>>> girl_friend2.max_intimacy
50
>>> 

5.2. インスタンス変数を変更する。

インスタンス変数を変更すると、そのインスタンスインスタンス変数だけが変更されます。

girl_friend1 = GirlFriend('サーバルちゃん')
girl_friend2 = GirlFriend('岩倉玲音')
girl_friend1.intimacy
girl_friend2.intimacy


# インスタンス変数を変更する
girl_friend1.intimacy = 70
girl_friend2.intimacy = 20
girl_friend1.intimacy
girl_friend2.intimacy
>>> girl_friend1 = GirlFriend('サーバルちゃん')
>>> girl_friend2 = GirlFriend('岩倉玲音')
>>> girl_friend1.intimacy
0
>>> girl_friend2.intimacy
0
>>> 
>>> 
>>> # インスタンス変数を変更する
... girl_friend1.intimacy = 70
>>> girl_friend2.intimacy = 20
>>> girl_friend1.intimacy
70
>>> girl_friend2.intimacy
20
>>> 


まとめ


インスタンス
変数
そのインスタンスだけで
使う変数
  
クラス
変数
すべてのインスタンス
共有して使う変数







ここまでは、クラス変数とインスタンス変数の動作の違いを確認しました。

もし、Python を習いたてで、すでに、お腹いっぱいだったり、まだ興味がわかなければ、ここまでで十分だと思います。 何故なら、ここから先のことを知らなくても、十分にアプリは作れるからです。

確かに、知っていれば、もう少し複雑なことができるようになりますが、 いろんなアプリを作ってみて、クラス変数とインスタンス変数の仕組みがどうなってるのか気になったら、また来ていただけると嬉しいです。

頑張って書いては見ましたが、オブジェクトについて具体的なイメージがない時にここから先の文章を読んでも、 抽象的すぎて役に立たないどころか、苦痛になってしまうと思います。

いまやらないで、学ぶタイミングを見計らうことは、きっと効率的に学習する上で大切だと思っています。

山口県萩市
















(後編) クラス変数とインスタンス変数の仕組み



ここで伝えたいことは...

Python のオブジェクトは
辞書をつなぎ合わせただけで
表現されています。

























f:id:domodomodomo:20180113154538j:plain





いよいよ、ここからは、
もう少し突っ込んだ動作原理を見ていきたいと思います。

実は、この記事は以下の記事からの続きになります。
インスタンスオブジェクトとクラスオブジェクトって
何かをざっくりと押さえておくと理解しやすいかと思います。

疑問: なんでクラスオブジェクトの属性を変更すると、
そこから生成された全てのインスタンスオブジェクトの属性も変更されるの?


f:id:domodomodomo:20180618162603j:plain


答え:
インスタンスオブジェクトの属性
→ クラスオブジェクトの属性
→ クラスオブジェクトの親クラスオブジェクトの属性
→ クラスオブジェクトの親クラスオブジェクトの親クラスオブジェクトの...
といった順番に属性にアクセスしてるから。

属性アクセスのデフォルトの振る舞いは、オブジェクトの辞書の属性の取得、設定、削除です。例えば a.x は、まず a.__dict__['x']、それから type(a).__dict__['x']、さらに type(a) のメタクラスを除く基底クラスへと続くというように探索が連鎖します。
デスクリプタ HowTo ガイド — Python 3.6.5 ドキュメント

問題 1. 復習

実行結果 1, 2 には何が表示されるでしょうか?

class GirlFriend(object):
    max_intimacy = 100
    min_intimacy = 0
    
    def __init__(self, name):
        self.name = name
        self.intimacy = 0


girl_friend1 = GirlFriend('サーバルちゃん')
girl_friend2 = GirlFriend('岩倉玲音')


# 1. クラスオブジェクトの属性を
girl_friend1.max_intimacy
girl_friend2.max_intimacy

# 2. 変更すると
GirlFriend.max_intimacy = 50

# 3. そこから生成された全ての
#    インスタンスオブジェクトの属性も変更される。
girl_friend1.max_intimacy  # 実行結果 1
girl_friend2.max_intimacy  # 実行結果 2
>>> # 答え(実行結果を抜粋したもの)
>>> girl_friend1.max_intimacy  # 実行結果 1
50
>>> girl_friend2.max_intimacy  # 実行結果 2
50
>>> 


問題 2. 本題

実行結果 3, 4, 5, 6 には何が表示されるでしょうか?

class GirlFriend(object):
    max_intimacy = 100
    min_intimacy = 0
    
    def __init__(self, name):
        self.name = name
        self.intimacy = 0


girl_friend1 = GirlFriend('サーバルちゃん')
girl_friend2 = GirlFriend('岩倉玲音')


# step1. girl_friend1 の属性 max_intimacy に
#          インスタンスオブジェクトは代入されてるかな?
# -> されてない...

# step2. GirlFriend の属性 max_intimacy に
#          インスタンスオブジェクトは代入されてるかな?
# -> 100 があった!
girl_friend1.max_intimacy


#
# インスタンスオブジェクトの属性を変えても...
#
girl_friend1.max_intimacy = 1000

# そのインスタンスオブジェクトは変わるが
girl_friend1.max_intimacy  # 実行結果 3

# クラスオブジェクトの属性にも
GirlFriend.max_intimacy  # 実行結果 4

# 他のインスタンスオブジェクトにも影響はない
girl_friend2.max_intimacy  # 実行結果 5


#
# インスタンスオブジェクトの属性を消せば...
#
del girl_friend1.max_intimacy

# またクラスオブジェクトの属性が参照される。
girl_friend1.max_intimacy  # 実行結果 6
>>> # 答え(実行結果を抜粋したもの)
... girl_friend1.max_intimacy  # 実行結果 3
1000
>>> 
>>> GirlFriend.max_intimacy  # 実行結果 4
100
>>> 
>>> girl_friend2.max_intimacy  # 実行結果 5
100
>>>
>>> girl_friend1.max_intimacy  # 実行結果 6
100
>>> 

いまいちわからない...

マニュアルの文章がいまいちよくわかりません。 理解につなげるために、関数呼び出し、日本語の表記を除いて、最終的に obj.attr のような属性参照だけに書き換えていきます。 厳密に書いてるわけではないので、ざっくり眺めてもらえると嬉しいです。

属性アクセスのデフォルトの振る舞いは、オブジェクトの辞書の属性の取得、設定、削除です。例えば a.x は、まず a.__dict__['x']、それから type(a).__dict__['x']、さらに type(a) のメタクラスを除く基底クラスへと続くというように探索が連鎖します。
デスクリプタ HowTo ガイド — Python 3.6.5 ドキュメント

Step1: マニュアルの文章を、ちょっと書き換えてみます。

a.x
→ a.__dict__['x']
→ type(a).__dict__['x']
→ type(a) のメタクラスを除く基底クラスの __dict__['x']
→ type(a) のメタクラスを除く基底クラスの メタクラスを除く基底クラス..
...

Step2: class.__bases__

「type(a) のメタクラスを除く基底クラス」→ type(a).__bases__[0]

a.x
→ a.__dict__['x']
→ type(a).__dict__['x']
type(a).__bases__[0].__dict__['x']
type(a).__bases__[0].__bases__[0].__dict__['x']
...


「type(a) のメタクラスを除く基底クラス」とは、オブジェクト a を生成したクラスの親クラスのことを指しています。だいたいの場合 type(a).__bases__[0] と同じです。__bases__ がタプルになっているのは、多重継承している場合があるからです。

クラスオブジェクトの基底クラスのタプルです。
class.__bases__


Step3: type 関数, instance.__class__

type(a) → a.__class__

a.x
→ a.__dict__['x']
a.__class__.__dict__['x']
a.__class__.__bases__[0].__dict__['x']
a.__class__.__bases__[0].__bases__[0].__dict__['x']
...

tyep(a) は、だいたいの場合 __class__ と同じです。

返り値は型オブジェクトで、一般に object.__class__ によって返されるのと同じオブジェクトです。
type 関数

クラスインスタンスが属しているクラスです。
__class__ 属性


Step4: 図にすると...

f:id:domodomodomo:20180618162603j:plain f:id:domodomodomo:20180623013911j:plain

ポイント


Python のオブジェクトは
辞書をつなぎ合わせただけで
表現されています。


辞書をつなぎ合わせただけで表現されています。 どうやってつなぎ合わせているのでしょうか?

インスタンスオブジェクトの __class__ 属性には、クラスオブジェクトが代入しているだけです。 クラスオブジェクトの __bases__ 属性には、親クラスオブジェクトが代入しているだけです。

a.x
→ a.__dict__['x']
→ a.__class__.__dict__['x']
→ a.__class__.__bases__[0].__dict__['x']
→ a.__class__.__bases__[0].__bases__[0].__dict__['x']
...

オブジェクトの (書き込み可能な) 属性を保存するために使われる辞書またはその他のマッピングオブジェクトです。
__dict__ 属性


Python では、属性だけではなく変数も辞書の中に保存されています。

# var = 0
locals().update({'var': 0})
print(var)  # 0


# obj.attr = 1
class Cls(object):
    pass


obj = Cls()
obj.__dict__.update({'obj_attr': 1})
print(obj.obj_attr)  # 1


難しい言葉で言い換えると「名前空間そのものが辞書で表現されている」と言えます。

名前空間 (namespace) とは、名前からオブジェクトへの対応付け (mapping) です。ほとんどの名前空間は、現状では Python の辞書として実装されています
9.2. Python のスコープと名前空間 - Python チュートリアル

確認問題

◯「インスタンス化」とは何ですか?

正確な説明ではないですが。 「クラスオブジェクト(辞書)に、新しい名前空間(別の辞書)を付け足すこと。」と説明できたりするのかなと思ったりもします。

◯「継承」とは何ですか?

正確な説明ではないですが。 「クラスオブジェクト(辞書)に、新しい名前空間(別の辞書)を付け足すこと。」と説明できたりするのかなと思ったりもします。 そこから、インスタンス→クラス→親クラスと、属性を順番に参照する機能をつけ加えて、クラスそして継承、多態性を表現しています。

◯ __class__ 属性に代入するとどうなるの?

__class__ 属性に別のクラスオブジェクトを代入することでクラスキャストのような動作をします。
Python でクラスキャスト -

変数, 属性の命名規則

◯ システムで定義された名前 (system-defined name)

__bases__, __class__, __dict__ とかアンダースコア _ で括られた変数が出てきました。これは何でしょうか?システムで定義された (system-defined) 名前です。Python が使っているか、あるいは Python が使い方を指定した変数です。__init__ なんかもこれの仲間に入ります。 クラス変数を 特殊属性 special attribute、 メソッドを 特殊メソッド special method と呼びます。

システムで定義された (system-defined) 名前です。これらの名前はインタプリタと (標準ライブラリを含む) 実装上で定義されています; ... (中略) ... このドキュメントで明記されている用法に従わない、 あらゆる __*__ の名前は、いかなる文脈における利用でも、警告無く損害を引き起こすことがあります。
2. 字句解析 — Python 3.6.5 ドキュメント


__*__ という名前を勝手に使わないでね、という命名規則がなぜあるのでしょうか?例えば、使っていない名前があるなら、使いたくもなります。__*__ という名前を勝手に使われてしまうと、Python が、新しくシステムが使う変数やメソッドを定義しようと思ったときに、問題が起こります。Python がバージョンアップするときに、新しい変数やメソッドを追加できなくなってしまうからです。

例えば Python 3.5 では新しく __matmul__, __rmatmul__, __imatmul__ が追加されました。これらの属性をあるコードが勝手に別の用途で使っていたら Python 3.5 では、そのコードが動かなくなってしまいます。つまり、Python 3.5 は前のバージョンのコードに対して、後方互換性を失ってしまうことになります。
PEP 465 - 行列の乗算専用の中置演算子

Python はなぜいくつかの処理にメソッドではなく、関数を使っているのですか?(例えば len(list) とか?)
Why does Python use methods for some functionality (e.g. list.index()) but functions for other (e.g. len(list))?

(Guido van Rossum) が説明すると約束した2つ目の Python の設計背景は、なぜ特殊メソッドの見た目を単純に special とはせずに __special__ したのかだ。
The second bit of Python rationale I promised to explain is the reason why I chose special methods to look __special__ and not merely special.

私は、多くのクラスがメソッドをオーバーライドするだろうと考えた。例えば、一般的なメソッド(例えば __add__ や __getitem__)、あまり一般的でないメソッド(例えば pickle の __reduce__、これは長いこと C 言語で一切サポートされていませんでした。)
I was anticipating lots of operations that classes might want to override, some standard (e.g. __add__ or __getitem__), some not so standard (e.g. pickle‘s __reduce__ for a long time had no support in C code at all).

私はこれらの特殊メソッドには、一般的なメソッド名を使って欲しくなかった。なぜなら、すでに設計されたクラス、またはこれらの全ての特殊メソッドを覚えていないユーザによって書かれたクラスが、意図せずメソッドをオーバーライドしてしやすく、結果として悲劇的な結果を引き起こす可能性を秘めているからだ("すでに設計されたクラス" というのは、特殊メソッドを追加した時に、古いコードで同じ特殊メソッド名が既に使われてしまうと、後方互換性が失われることを指しているのかな..)
I didn’t want these special operations to use ordinary method names, because then pre-existing classes, or classes written by users without an encyclopedic memory for all the special methods, would be liable to accidentally define operations they didn’t mean to implement, with possibly disastrous consequences.

PEP 3114 - iterator.next() から iterator.__next__() への名称変更
PEP 3114 - Renaming iterator.next() to iterator.__next__()

Python の言語仕様の一部となっている変数、属性にダブルアンダースコアを付加するようにすれば、Python の言語仕様の一部となっている変数、属性のための名前空間を作ることができます。そのためプログラマは、気づかないうちに Python の言語仕様上定義された名前を上書きしてしまうことを気にすることなく、アルファベットから始まる名前を変数、属性、そしてメソッドに使うことができます。(たとえアルファベットから書き始めても、class, import などの予約語と衝突してしまう可能性は残りますが、予約語を変数名に使用した場合にはすぐに syntax error が返されます。)
The use of double underscores creates a separate namespace for names that are part of the Python language definition, so that programmers are free to create variables, attributes, and methods that start with letters, without fear of silently colliding with names that have a language-defined purpose. (Colliding with reserved keywords is still a concern, but at least this will immediately yield a syntax error.)

◯ プライベートな識別子

その他にもアンダースコア _ を使う命名規則として、プライベートな識別子であるかどうかを明示するために使います。プライベートな識別子であるとは、obj.attr のようにオブジェクトの属性として外部から参照されないということです。

項番 レベル 命名規則 参照の仕方
1 モジュール _val1 参照できない
2 クラス _val2 obj._val2
3 クラス __val3 obj._クラス名__val3
4 インスタンス _val4 obj._val4
5 インスタンス __val5 obj._クラス名__val5
6 関数 val6 参照できない
# モジュール定義文

# 1. モジュールのローカル変数
_val1 = 1
"""
# 参照
できない
# 説明
モジュールを import しても
この変数 _val は import されません。
"""


class Person(object):
    # クラス定義文

    # 2. クラスのローカル変数
    _val2 = 10
    """
    # 参照
    obj._val2
    # 機能
    無。命名規則です。
    """
    __val3 = 100
    """
    # 参照
    Person._Person__val3
    # 機能
    Person.__val3 では参照できない。
    Person._Person__val3 として参照します。
    # いつ使うの?
    他のクラスから継承されたときに
    名前が上書きされてしまうのを避けるため。
    """

    def func(self):
        # 関数定義文

        # 3. インスタンスのローカル変数
        self._val4 = 1000
        self.__val5 = 10000
        """
        クラスのローカル変数と同じ
        """

        # 4. 関数のローカル変数
        val6 = 100000
        """
        # 参照
        参照できない
        # 機能
        参照できない
        # 説明
        _val6 = 100000
        とはしないことの方が多い気がします。

        1つ目の理由は、
        関数内のローカル変数なら、
        スコープが小さいので大抵自明だから。

        2つ目の理由は、
        関数内のローカル変数は
        func.val という様に外部から参照できないから。

        ただし関数内で
        func.val = 10 
        として関数の属性に値を代入できて
        関数外から参照することができる。
        """

2.3.2. 予約済みの識別子種 (reserved classes of identifiers)
実践されている命名方法


継承よりも合成
composition over inheritance

実装を伴う「継承」は、避けるべきだとされているらしいです。せっかく、継承がどのように表現されているかを見てきたのに、何だか残念な感じです。
composition over inheritance - Wikipedia
クラスの「継承」より「合成」がよい理由とは? - POSTD


「合成(コンポジション)」とは、何でしょうか?

コンポジション(Composition)は、日本語で「混合物」を意味する単語である。あるクラスの機能を持つクラスのことを指す。 特定のクラスの機能を、自分が作るクラスにも持たせたい場合に、継承を使わずフィールドとしてそのクラスを持ち、 そのクラスのメソッドを呼び出すメソッドを持たせること。そうすることで、クラスに他のクラスの機能を組み込むことができる。
継承とコンポジションをどう使い分けるか - いぬごやねっと


一見すると Python は「継承」が「コンポジション」によって表現されてるように見えます。しかし、動作が実装による「継承」と同じなので、結局、可能なら避けた方が望ましいことに変わりはないのかなと思ったりもします。

Java extends は悪だ; チャールズ・マンソンほどでないかもしれないが、可能ならいつでも避けなければならないほど悪いものだ。GoFデザインパターンの本は、ページ数を割いて、実装による継承 (extends) をインターフェイス (implements) による実装に置き換える方法について議論している。
The extends keyword is evil; maybe not at the Charles Manson level, but bad enough that it should be shunned whenever possible. The Gang of Four Design Patterns book discusses at length replacing implementation inheritance (extends) with interface inheritance (implements).

良い設計者は、ほとんどのコードをインターフェイスについて書いていて、具象ベースのクラスについては書いていない。この記事は、なぜ設計者がそのような奇妙な習慣を持つのかを説明し、2、3のインターフェイスベースのプログラミングの基礎について導入する。
Good designers write most of their code in terms of interfaces, not concrete base classes. This article describes why designers have such odd habits, and also introduces a few interface-based programming basics.
Why extends is evil; Improve your code by replacing concrete base classes with interfaces


これにしても別の言語の話になりますが最近生まれた Go にしても Rust にしても継承という機能そのものを切ってしまっています。


属性を順番に参照する機能は、実装による「継承」を実現できるというメリットもありますが、実装による「継承」そのものがデメリットだったりもするらしいです。

かつて私は Java の開発者である James Gosling Java がゲストスピーカーに呼ばれたユーザミーティングに参加しました。印象的な Q&A セッションの間、誰かが彼に尋ねました。「もし、 Java をもう一度開発し直せるなら、何を変えますか?」。彼は答えました「クラスを除きます。」
I once attended a Java user group meeting where James Gosling (Java's inventor) was the featured speaker. During the memorable Q&A session, someone asked him: "If you could do Java over again, what would you change?" "I'd leave out classes," he replied.

笑いが収まった後、彼は本当の問題がクラスそのものではなく、むしろ(extends の関係である)実装継承にあることを説明しました。(implements の関係である)interface 継承が望ましい。もしできるなら、実装継承は、いつも避けるべきだ。
After the laughter died down, he explained that the real problem wasn't classes per se, but rather implementation inheritance (the extends relationship). Interface inheritance (the implements relationship) is preferable. You should avoid implementation inheritance whenever possible.
Why extends is evil; Improve your code by replacing concrete base classes with interfaces


他にも Java でさえ意図的に切った実装を伴う多重継承という機能についても、Python では、しっかり実装されてしまっています。Python は、可読性をかなり重視して、設計をしているにも関わらず。

なぜなら多重継承は問題点が多いと思われたためである。

  1. 継承関係が複雑になるため全体の把握が困難になる。
  2. 名前の衝突。同じ名前を複数の基底クラスがそれぞれ別の意味で用いていた場合、その両方を派生クラスでオーバーライドするのが困難。
  3. 処理系の実装が複雑になってしまう。
  4. ... 省略 ...

しかしながら多重継承を使う方が直感的になる場合もあるとの主張もあり、どちらが正しいとは言えない状況である。
継承 (プログラミング) - Wikipedia


わかっていながらなんで「実装による継承」よりも、さらに極悪な「実装による多重継承」なんていう機能を許してしまったのかというと、完全に推測ですが、恐らく、わかっていなかったからです。

(Guido van Rossum) は、多くのクラスがメソッドをオーバーライドするだろうと考えた。
I was anticipating lots of operations that classes might want to override, some standard
Why does Python use methods for some functionality (e.g. list.index()) but functions for other (e.g. len(list))?


1995 年に Java が生まれて多くのシステムが開発され、どうも「実装による継承」はあかんらしいという知見が得られる前の 1991 年に Python は生まれました。Python は、日本では最近人気を得た言語ですが、実際には Java よりも Python の方が、ずっとおじいちゃんな言語です。

Pythonは死にかけの言語なのか
「いいえ、そうではありません。」Pythonは”比較的に”古い言語です。最初に登場したのは1990年代初期です(仮に1991年としましょう)。そして、他のプログラミング言語と同様に、Pythonも正しい選択をすることと、妥協することが必要でした。どんなプログラミング言語にもそれぞれのクセがあります。より最近の言語は過去の失敗から学んでいる傾向にあります。これはいい傾向です。
Pythonや機械学習、そして言語の競争について - POSTD


実は Pythonオブジェクト指向は、その大部分が後付けされた機能です。以下の記事は、Guido が Python の開発経緯について話してくれている記事になります。

にわかには信じられない人もいると思うが、オランダ国立情報数学研究所で開発が行われていた最初の一年の間、Python はクラスをサポートしておらず、最初の公開リリースの前にオブジェクト指向をサポートするようになった。どのようにクラスが追加されたかという過去の経緯を理解してもらう手助けになると思うので、現在の Python がどのようにクラスをサポートしているのか、という点から話を始めようと思う。
ユーザ定義クラスのサポートの追加 - The History of Python.jp

おさらい

インスタンスオブジェクトは、辞書をつなぎ合わせただけで表現されていました。 このことを理解することで、「継承」、「インスタンス化」といった抽象的な概念についても、より具体的な形で理解することができます。





次は、関数とメソッドの違いを明確にして、さらにクラスの理解を深めます。



中国 甘粛省