Python のクラス変数とインスタンス変数ってなに?
さて、これはどう言うことでしょうか?実際に操作をしながらクラス変数とインスタンス変数の違いを、もう少し追いかけてみたいと思います。
違い | クラス変数 | インスタンス変数 |
---|---|---|
1. 使い分け | 全てのインスタンスで 共有する値 |
そのインスタンス だけで使う値 |
2. 定義場所 | クラス定義文の直下で 代入された変数 |
関数定義文の直下で 代入された self の属性 |
3. クラス オブジェクト から参照 |
できる | できない |
4. インスタンス オブジェクト から参照 |
できる | できる |
5. 変更すると | 全ての インスタンスの属性 が変更される。 |
その インスタンスの属性 だけが変更される。 |
変数 をキーワードにして、検索された方へ
なおクラス変数、インスタンス変数は、属性です。
変数という名前がついてるのに実際は属性なので少しややこしいんですよね。
変数そのものについては、ここで記載させていただきました。
Python の変数と属性、代入とコピー
クラス変数とインスタンス変数の違い
違い 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 を習いたてで、すでに、お腹いっぱいだったり、まだ興味がわかなければ、ここまでで十分だと思います。 何故なら、ここから先のことを知らなくても、十分にアプリは作れるからです。
確かに、知っていれば、もう少し複雑なことができるようになりますが、 いろんなアプリを作ってみて、クラス変数とインスタンス変数の仕組みがどうなってるのか気になったら、また来ていただけると嬉しいです。
頑張って書いては見ましたが、オブジェクトについて具体的なイメージがない時にここから先の文章を読んでも、 抽象的すぎて役に立たないどころか、苦痛になってしまうと思います。
いまやらないで、学ぶタイミングを見計らうことは、きっと効率的に学習する上で大切だと思っています。
ここで伝えたいことは...
辞書をつなぎ合わせただけで
表現されています。
いよいよ、ここからは、
もう少し突っ込んだ動作原理を見ていきたいと思います。
実は、この記事は以下の記事からの続きになります。
インスタンスオブジェクトとクラスオブジェクトって
なにかをざっくりとで構わないので押さえておくと理解しやすいかと思います。
クラス変数の仕組み
前章では、クラスオブジェクトの属性を変更すると、そこから生成された全てのインスタンスオブジェクトのクラス変数も変更されました。 どうやってこのような動作を実装しているのでしょうか?
答え:
インスタンスオブジェクトの属性
→ クラスオブジェクトの属性
→ クラスオブジェクトの親クラスオブジェクトの属性
→ クラスオブジェクトの親クラスオブジェクトの親クラスオブジェクトの...
といった順番に属性にアクセスしてるから。
属性アクセスのデフォルトの振る舞いは、オブジェクトの辞書の属性の取得、設定、削除です。例えば 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: 図にすると...
◯ まとめ
辞書をつなぎ合わせただけで
表現されています。
辞書をつなぎ合わせただけで表現されています。
どうやってつなぎ合わせているのでしょうか?
インスタンスオブジェクトの __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__ 属性
このような構造をしているので
__class__ 属性に別のクラスオブジェクトを代入すると、クラスキャストのような動作をします。
Python でクラスキャスト - いっきに Python に詳しくなるサイト
また 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 チュートリアル
ちなみに PEP 20 で名前空間について言及された箇所があります。
もっと使っていきましょうと言われても、これはどういう意味でしょうか?
名前空間 は、素晴らしいアイディアです。-- もっとこれを使っていきましょう。
Namespaces are one honking great idea -- let's do more of those!
The Zen of Python - PEP 20
平たく言えば名前空間とはオブジェクトをひとまとまりにしたものです。
例えば標準ライブラリ math には自然対数を返す関数 log , 円周率を返す変数 pi などが保存されています。
そして math という名前空間でひとくくりにされています。
import math math.log(123, 123) # 1.0 math.pi # 3.141592653589793
1. import したモジュール、パッケージを参照するとき
ひとつ目の具体例は PEP 8 で簡単に言及されています。
ワイルドカードを使った import (from モジュール名 import *) は、避けなければなりません。 何故なら、どの変数が import した 名前空間 に存在するか不明瞭になり、 コードを読む人間やコードを解析する自動化ツールを混乱させるからです。
Wildcard imports (from <module> import *) should be avoided, as they make it unclear which names are present in the namespace, confusing both readers and many automated tools.
Style Guide for Python Code - PEP 8
# # OK # import math print(math.pi) # <--- 名前空間を使って pi を参照 # # 名前空間は使ってないけど # これも OK # from math import pi # <--- 名前空間を明示してから print(pi) # <--- 名前空間を使わずに参照 # # 動くけど PEP 8 的には # これは NG # from math import * # <--- 名前空間を明示しないし print(pi) # <--- 使いもしない # プログラマが持つべき心構え (The Zen of Python) - Qiita # https://qiita.com/IshitaTakeshi/items/e4145921c8dbf7ba57ef
逆に言えば「名前空間を使わない」とは、
「ある名前空間の配下にある名前を、ローカルスコープまたはグローバルスコープに、そのまま叩き込む」行為だと言えます。
2. クラス変数を参照するとき
実は私たちは Python でクラス変数を参照する時は、 名前空間を明示しないといけない仕様になっています。 いまいち何言ってるんだ?って感じですよね。
普通はローカルスコープで変数が定義されていなかった時、 1つ外側のスコープの名前が参照されます。 例えば、関数定義時は、ローカル変数が定義されていなかった場合、 1つ外側の名前空間を参照されるようになっています。
# 普通は... x = 0 def g(): x = 1 # <-- こっちが参照される def f(): return x # 1 return f f = g() f() # 1
しかし、メソッドを定義する時は、1つ外側の名前空間は、何もしないと参照されません。
もしクラス変数を参照したければ、 self.x あるいは C.x と書かないといけません。
# クラス定義時は... x = 0 # <-- こっちが参照される class C(object): x = 1 def f(self): return x # 0 # 1 を参照したければ self.x と # 名前空間を明示しないといけない。 # # あるいは C.x でもいけますが、 # 個人的には self.x が一番自然かなと感じたりもします。 obj = C() obj.f() # 0
もちろんクラス定義内であれば、名前空間を明示しなくても使えます。
むしろ明示するとエラーになります。
class C(object): x = 0 print(x) # 0 y = 1 print(C.y) # NameError, クラス C の定義が終わっていないから
このような挙動は PEP 227 で議論、言及されたようです。
クラス変数の参照を、例外的に取り扱う旨の記述が見えます。
箇条書きの文章は、パッと見てもわかりづらいですが、
PEP 227 の中で、なんで例外的に取り扱ったかも含めて丁寧に説明してくれているようです。
自分はまだ、ちゃんと読めてません。
議論
... 名前解決のルールは、静的スコープの言語に典型的なものです。ただし、3つの例外があります:
- クラススコープの名前は、参照できません。
- global 文は通常のルールをショートカットします。
- 変数は宣言されません。
Discussion
... The name resolution rules are typical for statically scoped languages, with three primary exceptions:
- Names in class scope are not accessible.
- The global statement short-circuits the normal rules.
- Variables are not declared.
静的スコープは、レキシカルスコープとも呼ばれたりします。
レキシカルスコープの反対は、ダイナミックスコープと呼ばれます。
レキシカルスコープとダイナミックスコープ - すぐに忘れる脳みそのた...
PEP 227 については、この記事から知ることができました。
ハヤタカ先生、ありがとうございます。
【python】クラス変数のスコープには注意が必要
ttps://hayataka2049.hatenablog.jp/entry/2018/04/07/145324
◯ まとめ 確認問題
1.「インスタンス化」とは何ですか?
正確な説明ではないですが。 「クラスオブジェクト(辞書)に、新しい名前空間(別の辞書)を付け足すこと。」と説明できたりするのかなと思ったりもします。
2.「継承」とは何ですか?
正確な説明ではないですが。 「クラスオブジェクト(辞書)に、新しい名前空間(別の辞書)を付け足すこと。」と説明できたりするのかなと思ったりもします。 そこから、インスタンス→クラス→親クラスと、属性を順番に参照する機能をつけ加えて、クラスそして継承、多態性を表現しています。
現在、記事、移転中です。ここから下の記事は、こちらのページに写しました。
ご不便お掛け致します。今後ともよろしくお願いいたします。
変数と属性の命名規則
PEP 8 で定められた2つの命名規則について説明します。
- システムで定義された名前
- 公開されていない識別子
1. システムで定義された名前 (system-defined name)
__bases__, __class__, __dict__ とかアンダースコア _ で括られた変数が出てきました。 これは何でしょうか?システムで定義された (system-defined) 名前です。 Python が使っているか、あるいは Python が使い方を指定した変数です。 __init__ なんかもこれの仲間に入ります。 クラス変数を 特殊属性 special attribute、 メソッドを 特殊メソッド special method と呼びます。
__double_leading_and_trailing_underscore__:
ユーザが扱う名前空間にある "マジック" オブジェクトもしくは属性です。 例えば、__init__, __import__ or __file__ が該当します。 このような名前を決して考案しないでください; ドキュメントに記載されたもののみ使ってください。(意訳)__double_leading_and_trailing_underscore__:
特別な意味を持つ変数名あるいは属性名です。 例えば、__init__, __import__ or __file__ が該当します。 このように二重のアンダースコア __ で囲まれた変数名、属性名は、公式のドキュメントで定められた通り使い、 自分で定義たりしないでください。__double_leading_and_trailing_underscore__:
"magic" objects or attributes that live in user-controlled namespaces. E.g. __init__, __import__ or __file__. Never invent such names; only use them as documented.
Style Guide for Python Code - PEP 8
システムで定義された (system-defined) 名前です。 これらの名前はインタプリタと (標準ライブラリを含む) 実装上で定義されています; ... (中略) ... このドキュメントで明記されている用法に従わない、 あらゆる __*__ の名前は、いかなる文脈における利用でも、 警告無く損害を引き起こすことがあります。
2.3.2. 予約済みの識別子 - Python 言語リファレンス
なんで使っちゃ行けないの?
__*__ という名前を勝手に使わないでね、という命名規則がなぜあるのでしょうか?例えば、使っていない名前があるなら、使いたくもなります。__*__ という名前を勝手に使われてしまうと、Python が、新しくシステムが使う変数やメソッドを定義しようと思ったときに、問題が起こります。Python がバージョンアップするときに、新しい変数やメソッドを追加できなくなってしまうからです。
例えば Python 3.5 では新しく __matmul__, __rmatmul__, __imatmul__ が追加されました。これらの属性をあるコードが勝手に別の用途で使っていたら Python 3.5 では、そのコードが動かなくなってしまいます。つまり、Python 3.5 は、Python 3.4 以前のバージョンのコードに対して、後方互換性を失ってしまうことになります。
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.)
2. 公開されていない識別子
多人数で開発するときは、勝手に使って欲しくない属性、メソッドがあります。 例えば、自分専用に頻繁に機能を書き換えたりするメソッド、その場しのぎで作ったすぐに消すメソッドが想定されます。 そう言った言わば自分だけで使いたい属性、メソッドには先頭にアンダーバー _ をつけます。
公開されていない識別子は大きく2つに分けられます。アンダーバーが1つのものと、2つのものです。 アンダーバーが1つ _ の場合、すなわち継承した他のクラスやインスタンスからなら見える属性を protected と表現されることがあります。 アンダーバーが2つ __ の場合、すなわち継承した他のクラスやインスタンスからは見えないより重たい制限のかけられた属性を private と表現されることがあります。
ただ、標準ライブラリのコードなどを見ているとアンダーバー2つ __ の private な属性をあまり、見かけたことがありません。 書籍「Effective Python」の 「Item 27: プライベート属性よりもパブリック属性を使おう」で そう言ったところの温度感について触れられています。
# モジュール定義文 # 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 の属性(インスタンス変数) self._val4 = 1000 self.__val5 = 10000 """ クラスのスコープの「ローカル変数」(クラス変数)と同じ """ # 4. 関数のスコープの「ローカル変数」 val6 = 100000 """ # 参照 参照できない # 機能 参照できない # 説明 _val6 = 100000 とはしません。 1つ目の理由は、 関数内のローカル変数なら、 スコープが小さいので大抵自明だから。 2つ目の理由は、 関数内のローカル変数は func.val という様に外部から参照できないから。 ただ関数内で func.val = 10 として関数の属性に値を代入できて 関数外から参照することができたりします。 """
クラスのメソッドやインスタンス変数 (まとめて "属性" といいます) を 公開するかどうかをいつも決めるようにしましょう。
よくわからないなら、公開しないでおきます。 なぜなら、公開されている属性を非公開にすることよりも、 非公開の属性を公開することの方がずっと簡単だからです。
公開されている(public)属性に対して、開発者が後方互換性を壊す変更をしないことを期待します。 公開されていない(non-public)属性は、サードパーティに使われてることを意図していないものです。 つまり、非公開の属性に変更されない保証はありませんし、削除されない保証すらありません。
項番 | レベル | 命名規則 | 機能/規約 | 参照の仕方 |
---|---|---|---|---|
1 | モジュール | _val1 | 機能 | 参照できない |
2 | クラス | _val2 | 規約 | obj._val2 |
3 | クラス | __val3 | 機能 | obj._クラス名__val3 |
4 | インスタンス | _val4 | 規約 | obj._val4 |
5 | インスタンス | __val5 | 機能 | obj._クラス名__val5 |
6 | 関数 | val6 | 機能 | 参照できない |
ここで厄介なのは「機能」と「規約」がごっちゃになっていること。
「機能」についてはこちらで記載されています。
2.3.2. 予約済みの識別子種 - Python 言語リファレンス
反対に「規約」、PEP 8 についてはこちらに記載されています。
実践されている命名方法 - PEP 8
継承よりも合成
属性を順番に参照する機能は、実装による「継承」を実現できるというメリットがあります。 しかし、実装による「継承」そのものがデメリットだったりもするらしいです。
「継承」は、避けるべきだとされているらしいです。 せっかく、「継承」がどのように表現されているかを見てきたのに、何だか残念な感じですね。
継承よりも合成 - Wikipedia
オブジェクト指向プログラミング(OOP)における継承よりも合成(もしくは、合成による再利用の原則)とは クラスは多態性やコードの再利用性を、基底クラスもしくは親クラスからの継承ではなく、 合成によって(ほしいと思っている機能を実装した他のクラスのインスタンスを保持させることによって) 獲得しなければならないという原則である[2]。 これは例えば [3] のような影響のあるデザインパターンの本の中などで、よく言及される OOP の原則である。[3]。composition over inheritance - Wikipedia
Composition over inheritance (or composite reuse principle) in object-oriented programming (OOP) is the principle that classes should achieve polymorphic behavior and code reuse by their composition (by containing instances of other classes that implement the desired functionality) rather than inheritance from a base or parent class.[2] This is an often-stated principle of OOP, such as in the influential book Design Patterns.[3]
1. 合成と委譲
ところで「合成(コンポジション)」とは、何でしょうか?
# 継承 inheritance class Team(list): pass team = Team() team.append('諸葛亮') team #
# 合成 composition class Team(object): def __init__(self): self.list = [] # 委譲 def add(self, general): self.list.append(generarl) team = Team() team.add('諸葛亮')
コンポジション(Composition)は、日本語で「混合物」を意味する単語である。あるクラスの機能を持つクラスのことを指す。 特定のクラスの機能を、自分が作るクラスにも持たせたい場合に、継承を使わずフィールドとしてそのクラスを持ち、 そのクラスのメソッドを呼び出すメソッドを持たせること。そうすることで、クラスに他のクラスの機能を組み込むことができる。
継承とコンポジションをどう使い分けるか - いぬごやねっと
合成したオブジェクトのメソッドを、合成元のオブジェクトのメソッドから呼び出すことを委譲と呼びます。
2. どのくらいやったらいけないの?
Java に関する記事です。
なぜ extends は悪なのか; 具象基底クラスをインターフェイスに置き換えてコードを改善する
Why extends is evil; Improve your code by replacing concrete base classes with interfacesかつて私は 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.
どのくらい避けた方の良いのでしょうか?
なぜ extends は悪なのか; 具象基底クラスをインターフェイスに置き換えてコードを改善する
Why extends is evil; Improve your code by replacing concrete base classes with interfacesJava の 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.
これにしても別の言語の話になりますが、最近生まれた Go や Rust に、いたっては実装による継承という機能そのものを切ってしまっています。
RustやGoで継承がないのはそれが不要だから。僕もそう思うのだけど、もっといえば使っちゃいけないと思っている。理由は、継承は実装を伴い、共通処理を呼び出したりするのに使うけど、メソッドが依存しあうことは、独立性の観点からダメ。オブジェクトを結合点にすべき。実装共有するのは依存性を生む
— west_coder (@sntjnk88) March 18, 2018
3. なんで「継承」はいけないの?
自分はなぜ「実装による継承がいけないのか」と、 なぜ「合成ならそれを避けることができるのか」を、あまり理解していません。 なんとなく雰囲気で理解したつもりになっています。
3.1. 名前空間を適切に分けられなくなる
オブジェクト指向とは、名前空間を適切にわけることだと考えています。 その名前空間をわける手段、方針として、現実の物をベースに考えてクラス設計を行いますが、必ずしもそうでない時もあります。
名前空間を元に適切に責任分解点を明確に分担することによって、多人数でも開発できるようにします。
この記事の次にあたる以下の記事で、もう少し書きました。
Python の関数とメソッドの違いってなに?
オブジェクト指向を、名前空間を適切にわけることだと考えた時に、 実装を伴う継承をしてしまうと、親クラスと子クラスで名前空間をごっちゃにしてしまうことになります。
つまり親クラスを開発する人と子クラスを開発する人が同じ self という名前空間を共有してしまいます。 結果としてコードを書く人同士の責任の分解点があいまりになり、開発しにくくなります。
もちろん __private
な属性を使えばいいんじゃないかなと思ったのですが、
それだけでは不十分ということなのでしょうか。
クラスを設計するときは合成して、データを処理をするときは委譲すれば、 名前空間を明確に分割したまま、継承と同じ効果を期待することができます。
3.2. 機能的な柔軟性を欠いてしまう
これがいい例かなと感じます。
継承よりも合成の方が機能的にもいろんなことができます。
クラスの「継承」より「合成」がよい理由とは? - POSTD
多重継承
実はさらに悪いことに Java でさえ意図的に切った実装を伴う多重継承という機能についても、 Python では、しっかり実装されてしまっています。 Python は、可読性をかなり重視して、設計をしているにも関わらず。
なぜなら多重継承は問題点が多いと思われたためである。
- 継承関係が複雑になるため全体の把握が困難になる。
- 名前の衝突。同じ名前を複数の基底クラスがそれぞれ別の意味で用いていた場合、その両方を派生クラスでオーバーライドするのが困難。
- 処理系の実装が複雑になってしまう。
- ... 省略 ...
しかしながら多重継承を使う方が直感的になる場合もあるとの主張もあり、どちらが正しいとは言えない状況である。
継承 (プログラミング) - Wikipedia
なんで多重継承を許してしまったのでしょうか?
1. わかっていなかったから
わかっていながらなんで「実装による継承」よりも、さらに極悪な「実装による多重継承」なんていう機能を許してしまったのかというと、完全に推測ですが、恐らく、わかっていなかったからです。
私 (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
2. そんなに問題ではないから
ちゃぶ台返しになりますが。そうではないという意見もあります。
Lennart Regebroは非常に経験豊富なPythonistaで、本書のテクニカルレビューアの一人でもあるのですが、 Djangoのmixinビュー階層の設計は複雑だと考えています。しかし、彼は次のようにも述べています。
多重継承の危険性と害悪はかなり誇張されていて、実際のところ、 私はこれまでにそれほど大きな問題を経験したことはありません。
結局、多重継承の使い方や、自分のプロジェクトで多重継承をまったく使わないようにするかについては、 さまざまな意見があります。しかし、使おうとしているフレームワークがその選択を強制してくることもあり、 他にチョイスがないこともあります。
実際 Python 3.4 で新しく標準ライブラリに追加された pathlib も、図を見る限り多重継承を使い実装されているようです。
pathlib の経緯とかは、この記事がわかりやすかったです。
最近追加されたPythonの便利機能とこれからのPython
ttp://cocodrips.hateblo.jp/entry/2018/08/26/172938
規模が小さいうちは、別に使ってもいいのかなと..
いくら継承しようが多重継承しようが、全体が把握できているなら問題がない気がします。
拡張の容易さみたいなのは削がれるかもしれませんが、実装を伴う継承をした方が楽ですしね。
ただ、規模が大きくなって堅牢なものを作ろうとする Go, Rust が取り扱うような範囲になってくると切らざる得なくなってるという感覚かなと思います。
それはちょうど、動的型付けで型なんか明示しなくてもいいやろって言ってたけど、
やっぱり取り扱うものの規模が大きくなると、アノテーションとしての型が採用されたように。
漸進的型付け言語の時代に必要なもの - mizch's blog
ttps://mizchi.hatenablog.com/entry/2018/07/05/180219
また、Python では、なぜ制限のきつい __ private ではなく中途半端な _ protected が好まれるのかも疑問だったのですが、 この辺りに答えがあるのかなと思ったり思わなかったりします。
Kotlin や Ruby では委譲をするための便利な機能があるそうです。 しかし上記のような private を好まない Python の文化と下記の PEP 8 の文章を読む限りでは、 Python に委譲の機能が追加されることはないかなと思ったりもします。
ここでは "private" という用語を使っていません。 なぜなら、Python の世界で本当の意味で private なものは存在しない (実現するには通常は不要なほどの多くの作業が必要です) からです。
継承の設計 - PEP 8
まとめ
インスタンスオブジェクトは、辞書をつなぎ合わせただけで表現されていました。 このことを理解することで、「継承」、「インスタンス化」といった抽象的な概念についても、より具体的な形で理解することができます。
また実装を伴う継承は、親クラスと子クラスが取り扱う名前空間がごっちゃになり責任の分解点が曖昧になるので、 多人数で大規模なものを作るときには避けた方が望ましいかもしれません。
- クラス変数とインスタンス変数の違い
- クラス変数の仕組み
- 名前空間
- 変数と属性の命名規則
- 継承よりも合成
- 多重継承
- まとめ
こんなに長い文章、もし読んでいただいた方がいたら幸いです。
次は、関数とメソッドの違いを明確にして、さらにクラスの理解を深めます。