はじめに
Python のオブジェクトシステムは、主に辞書を含む実装に基づいています。このセクションではそれについて説明します。
This tutorial is from open-source community. Access the source code
💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください
Python のオブジェクトシステムは、主に辞書を含む実装に基づいています。このセクションではそれについて説明します。
辞書は、名前付きの値のコレクションであることを覚えておきましょう。
stock = {
'name' : 'GOOG',
'shares' : 100,
'price' : 490.1
}
辞書は、単純なデータ構造に一般的に使用されます。ただし、インタプリタの重要な部分にも使用されており、Python で最も重要なデータ型かもしれません。
モジュール内では、辞書がすべてのグローバル変数と関数を保持します。
## foo.py
x = 42
def bar():
...
def spam():
...
foo.__dict__
または globals()
を調べると、辞書が見えます。
{
'x' : 42,
'bar' : <function bar>,
'spam' : <function spam>
}
ユーザ定義オブジェクトも、インスタンスデータとクラスの両方に辞書を使用します。実際、オブジェクトシステム全体は、主に辞書の上に置かれる追加の層に過ぎません。
辞書はインスタンスデータである __dict__
を保持します。
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.1)
>>> s.__dict__
{'name' : 'GOOG','shares' : 100, 'price': 490.1 }
この辞書(およびインスタンス)は、self
に代入することで埋めます。
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
インスタンスデータである self.__dict__
は、次のようになります。
{
'name': 'GOOG',
'shares': 100,
'price': 490.1
}
各インスタンスは独自のプライベート辞書を持ちます。
s = Stock('GOOG', 100, 490.1) ## {'name' : 'GOOG','shares' : 100, 'price': 490.1 }
t = Stock('AAPL', 50, 123.45) ## {'name' : 'AAPL','shares' : 50, 'price': 123.45 }
あるクラスのインスタンスを100個作成した場合、データを保持する辞書が100個あります。
別の辞書がメソッドを保持します。
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
def cost(self):
return self.shares * self.price
def sell(self, nshares):
self.shares -= nshares
この辞書は Stock.__dict__
にあります。
{
'cost': <function>,
'sell': <function>,
'__init__': <function>
}
インスタンスとクラスは互いに関連付けられています。__class__
属性はクラスを指し戻します。
>>> s = Stock('GOOG', 100, 490.1)
>>> s.__dict__
{ 'name': 'GOOG','shares': 100, 'price': 490.1 }
>>> s.__class__
<class '__main__.Stock'>
>>>
インスタンス辞書は各インスタンス固有のデータを保持しますが、クラス辞書はすべてのインスタンスによって共有されるデータをまとめて保持します。
オブジェクトを操作する際、.
演算子を使ってデータとメソッドにアクセスします。
x = obj.name ## 取得
obj.name = value ## 設定
del obj.name ## 削除
これらの操作は、内部にある辞書に直接関連付けられています。
オブジェクトを変更する操作は、その下にある辞書を更新します。
>>> s = Stock('GOOG', 100, 490.1)
>>> s.__dict__
{ 'name':'GOOG','shares': 100, 'price': 490.1 }
>>> s.shares = 50 ## 設定
>>> s.date = '6/7/2007' ## 設定
>>> s.__dict__
{ 'name': 'GOOG','shares': 50, 'price': 490.1, 'date': '6/7/2007' }
>>> del s.shares ## 削除
>>> s.__dict__
{ 'name': 'GOOG', 'price': 490.1, 'date': '6/7/2007' }
>>>
インスタンスの属性を読み取るとしましょう。
x = obj.name
属性は2つの場所に存在する可能性があります。
両方の辞書を確認する必要があります。まず、ローカルの __dict__
を確認します。見つからなかった場合は、__class__
を通じてクラスの __dict__
を確認します。
>>> s = Stock(...)
>>> s.name
'GOOG'
>>> s.cost()
49010.0
>>>
この検索スキームが、クラスのメンバーがすべてのインスタンスによって共有される仕組みです。
クラスは他のクラスから継承することができます。
class A(B, C):
...
基底クラスは各クラスのタプルに格納されています。
>>> A.__bases__
(<class '__main__.B'>, <class '__main__.C'>)
>>>
これは親クラスへのリンクを提供します。
論理的には、属性を見つけるプロセスは以下の通りです。まず、ローカルの __dict__
を確認します。見つからなかった場合は、クラスの __dict__
を確認します。クラス内で見つからなかった場合は、__bases__
を通じて基底クラスを確認します。ただし、次に説明するこのプロセスにはいくつかの微妙な点があります。
継承階層では、属性は順番に継承木を辿って見つけられます。
class A: pass
class B(A): pass
class C(A): pass
class D(B): pass
class E(D): pass
単一継承の場合、最上位までのパスは1つだけです。最初の一致が見つかったときに探索を停止します。
Python は継承チェーンを事前に計算し、それをクラスの _MRO_
属性に格納します。これを確認することができます。
>>> E.__mro__
(<class '__main__.E'>, <class '__main__.D'>,
<class '__main__.B'>, <class '__main__.A'>,
<type 'object'>)
>>>
このチェーンは メソッド解決順序 と呼ばれます。属性を見つけるために、Python はこの MRO を順に辿ります。最初の一致が採用されます。
多重継承の場合、最上位までの単一のパスはありません。例を見てみましょう。
class A: pass
class B: pass
class C(A, B): pass
class D(B): pass
class E(C, D): pass
属性にアクセスしたときに何が起こりますか?
e = E()
e.attr
属性検索プロセスが行われますが、その順序は何でしょうか? それが問題です。
Python は 協調的多重継承 を使用しており、クラスの順序付けに関するいくつかの規則を遵守しています。
MRO は、それらの規則に従って階層内のすべてのクラスをソートすることによって計算されます。
>>> E.__mro__
(
<class 'E'>,
<class 'C'>,
<class 'A'>,
<class 'D'>,
<class 'B'>,
<class 'object'>)
>>>
根本的なアルゴリズムは「C3 線形化アルゴリズム」と呼ばれます。家が火事になり避難しなければならない場合に従うのと同じ順序付けの規則をクラス階層が遵守している限り、正確な詳細は重要ではありません。子供たちが先で、その後に親が続きます。
2つのまったく関係のないオブジェクトを考えてみましょう。
class Dog:
def noise(self):
return 'Bark'
def chase(self):
return 'Chasing!'
class LoudDog(Dog):
def noise(self):
## LoudBike(以下)とのコードの共通性
return super().noise().upper()
そして
class Bike:
def noise(self):
return 'On Your Left'
def pedal(self):
return 'Pedaling!'
class LoudBike(Bike):
def noise(self):
## LoudDog(上記)とのコードの共通性
return super().noise().upper()
LoudDog.noise()
と LoudBike.noise()
の実装にはコードの共通性があります。実際、コードはまったく同じです。自然なことながら、そのようなコードはソフトウェアエンジニアを引き付けるはずです。
「ミックスイン」パターンは、コードの断片を持つクラスです。
class Loud:
def noise(self):
return super().noise().upper()
このクラスは単独では使用できません。継承を通じて他のクラスと混合します。
class LoudDog(Loud, Dog):
pass
class LoudBike(Loud, Bike):
pass
不思議なことに、大音量の機能が今度は一度だけ実装され、2つのまったく関係のないクラスで再利用されました。このようなトリックは、Pythonにおける多重継承の主な用途の1つです。
super()
を使うのかメソッドをオーバーライドする際は常に super()
を使用します。
class Loud:
def noise(self):
return super().noise().upper()
super()
は、MRO 上の 次のクラス に委譲します。
厄介なことに、それが何であるかはわかりません。特に多重継承が使われている場合、それが何であるかはわかりません。
多重継承は強力なツールです。力には責任が伴うことを忘れないでください。フレームワークやライブラリは時々、コンポーネントの組み合わせに関する高度な機能にそれを使用します。さて、それを見たことを忘れましょう。
セクション4では、株式保有を表す Stock
クラスを定義しました。この演習では、そのクラスを使用します。インタプリタを再起動していくつかのインスタンスを作成しましょう:
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> goog = Stock('GOOG',100,490.10)
>>> ibm = Stock('IBM',50, 91.23)
>>>
対話型シェルで、作成した2つのインスタンスの内部辞書を調べましょう:
>>> goog.__dict__
... 出力を見てください...
>>> ibm.__dict__
... 出力を見てください...
>>>
上記のインスタンスの1つに新しい属性を設定してみましょう:
>>> goog.date = '6/11/2007'
>>> goog.__dict__
... 出力を見てください...
>>> ibm.__dict__
... 出力を見てください...
>>>
上記の出力では、goog
インスタンスに date
という属性があるのに対し、ibm
インスタンスにはないことに気付くでしょう。重要なことは、Pythonは実際に属性に対して何の制限も設けていないということです。たとえば、インスタンスの属性は __init__()
メソッドで設定されたものに限定されません。
属性を設定する代わりに、新しい値を直接 __dict__
オブジェクトに入れてみましょう:
>>> goog.__dict__['time'] = '9:45am'
>>> goog.time
'9:45am'
>>>
ここで、インスタンスが辞書の上にある単なる層であるという事実に気付きます。注:辞書の直接操作はまれであることを強調する必要があります。常にコードを (.) 構文を使うように書く必要があります。
クラス定義を構成する定義は、そのクラスのすべてのインスタンスによって共有されます。すべてのインスタンスがその関連するクラスに戻るリンクを持っていることに注意してください:
>>> goog.__class__
... 出力を見てください...
>>> ibm.__class__
... 出力を見てください...
>>>
インスタンスでメソッドを呼び出してみましょう:
>>> goog.cost()
49010.0
>>> ibm.cost()
4561.5
>>>
名前 'cost' は goog.__dict__
または ibm.__dict__
のどちらにも定義されていないことに注意してください。代わりに、それはクラス辞書によって提供されています。これを試してみましょう:
>>> Stock.__dict__['cost']
... 出力を見てください...
>>>
辞書を介して直接 cost()
メソッドを呼び出してみましょう:
>>> Stock.__dict__['cost'](goog)
49010.0
>>> Stock.__dict__['cost'](ibm)
4561.5
>>>
クラス定義で定義された関数をどのように呼び出しているかと、self
引数がインスタンスをどのように取得するかに注意してください。
Stock
クラスに新しい属性を追加してみましょう:
>>> Stock.foo = 42
>>>
この新しい属性がすべてのインスタンスにどのように表示されるかに注意してください:
>>> goog.foo
42
>>> ibm.foo
42
>>>
ただし、それはインスタンス辞書の一部ではないことに注意してください:
>>> goog.__dict__
... 出力を見て、'foo' 属性がないことに注意してください...
>>>
インスタンスで foo
属性にアクセスできる理由は、Pythonがインスタンス自体に何かを見つけられない場合、常にクラス辞書をチェックするからです。
注:この演習のこの部分は、クラス変数として知られるものを示しています。たとえば、次のようなクラスがあるとしましょう:
class Foo(object):
a = 13 ## クラス変数
def __init__(self,b):
self.b = b ## インスタンス変数
このクラスでは、クラス自体の本体で割り当てられた変数 a
は「クラス変数」です。作成されるすべてのインスタンスによって共有されます。たとえば:
>>> f = Foo(10)
>>> g = Foo(20)
>>> f.a ## クラス変数を調べる(両方のインスタンスで同じ)
13
>>> g.a
13
>>> f.b ## インスタンス変数を調べる(異なる)
10
>>> g.b
20
>>> Foo.a = 42 ## クラス変数の値を変更する
>>> f.a
42
>>> g.a
42
>>>
Pythonの微妙な機能の1つは、メソッドを呼び出すことが実際には2つのステップと束縛メソッドと呼ばれるものを含んでいることです。たとえば:
>>> s = goog.sell
>>> s
<bound method Stock.sell of Stock('GOOG', 100, 490.1)>
>>> s(25)
>>> goog.shares
75
>>>
束縛メソッドは実際にはメソッドを呼び出すために必要なすべての要素を含んでいます。たとえば、メソッドを実装する関数の記録を保持しています:
>>> s.__func__
<function sell at 0x10049af50>
>>>
これは、Stock
辞書にあるものと同じ値です。
>>> Stock.__dict__['sell']
<function sell at 0x10049af50>
>>>
束縛メソッドはまた、self
引数であるインスタンスを記録します。
>>> s.__self__
Stock('GOOG',75,490.1)
>>>
()
を使用して関数を呼び出すと、すべての要素がまとまります。たとえば、s(25)
を呼び出すと、実際にはこれが行われます:
>>> s.__func__(s.__self__, 25) ## s(25) と同じ
>>> goog.shares
50
>>>
Stock
から継承する新しいクラスを作成します。
>>> class NewStock(Stock):
def yow(self):
print('Yow!')
>>> n = NewStock('ACME', 50, 123.45)
>>> n.cost()
6172.50
>>> n.yow()
Yow!
>>>
継承は、属性の検索プロセスを拡張することによって実装されます。__bases__
属性は、直近の親クラスのタプルを持っています:
>>> NewStock.__bases__
(<class'stock.Stock'>,)
>>>
__mro__
属性は、属性の検索対象となるすべての親クラスのタプルを持っています。
>>> NewStock.__mro__
(<class '__main__.NewStock'>, <class'stock.Stock'>, <class 'object'>)
>>>
上記のインスタンス n
の cost()
メソッドが見つけられる方法は次の通りです:
>>> for cls in n.__class__.__mro__:
if 'cost' in cls.__dict__:
break
>>> cls
<class '__main__.Stock'>
>>> cls.__dict__['cost']
<function cost at 0x101aed598>
>>>
おめでとうございます! 辞書の復習の実験を完了しました。 スキルを向上させるために、LabExでさらに実験を行って練習できます。