7. Python オブジェクト モデル
#7. Python オブジェクト モデル
Python オブジェクト モデルは CPython の基礎です。 Python で実行されるものはすべて、整数、文字列、リスト、モジュール、関数、クラス、例外、フレーム、さらにはコンパイルされたコードなど、最終的にはオブジェクトに対する操作になります。
言語レベルでは、Python はすべてのオブジェクトに ID、型、および値があると言います。オブジェクトのアイデンティティは作成後も固定されます。isアイデンティティを比較し、id()その ID を表す整数を返します。 ([Python ドキュメント][1])
CPython は、C 構造体、オブジェクト ヘッダー、参照カウント、型オブジェクト、および操作スロットを使用してこのモデルを実装します。
7.1 オブジェクト、値、およびアイデンティティ
Python オブジェクトには 3 つのコア プロパティがあります。
| プロパティ | 意味 | 例 |
|---|---|---|
| アイデンティティ | オブジェクトの安定したアイデンティティ | id(x) |
| タイプ | オブジェクトのランタイムタイプ | type(x) |
| 値 | オブジェクト | によって表されるデータ42、"abc"、[1, 2] |
例:```python x = [1, 2, 3] y = x
print(x is y) # True print(type(x)) # <class 'list'> print(x) # [1, 2, 3]
`x`そして`y`は、同じオブジェクトにバインドされた 2 つの名前です。彼らは同じアイデンティティを持っています。```python
x.append(4)
print(y) # [1, 2, 3, 4]
```リストが変わりました。バインディングはコピーを作成しませんでした。
CPython では、オブジェクト ID は通常のオブジェクトのオブジェクト アドレスと密接に関係しています。この言語は安定したアイデンティティのみを保証しており、アイデンティティがメモリアドレスでなければならないというわけではありません。
## 7.2 オブジェクトにバインドされる名前
Python 変数はバインディングであり、ストレージ ボックスではありません。
このコード:```python
a = 10
b = a
```整数値をコピーしません`b`。それは結合します`b`によって参照される同じオブジェクトに`a`。
不変オブジェクトの場合、これは多くの場合、値のコピーのように感じられます。```python
a = 10
b = a
a = 20
print(b) # 10
```しかし、そのオブジェクトは`10`変わらなかった。名前`a`別のオブジェクトにリバウンドしました。
可変オブジェクトの場合、違いは明らかです。```python
a = []
b = a
a.append("x")
print(b) # ['x']
```名前`a`そして名前`b`同じリストを参照してください。 1 つの参照を介した変更は、もう 1 つの参照を介して表示されます。
この名前からオブジェクトへのモデルは CPython の中心です。バイトコード命令は主に、オブジェクト参照の読み込み、オブジェクト参照の保存、オブジェクト参照の受け渡し、オブジェクト参照に対する操作の呼び出しを行います。
## 7.3 すべての実行時の値は`PyObject *`CPython 内部では、オブジェクトは通常、次の型のポインタを通じて処理されます。`PyObject *`。公式の C API ドキュメントには、Python オブジェクトへのすべてのポインターをキャストできると記載されています。`PyObject *`、通常のリリース ビルドでは、参照カウントと、基本オブジェクト構造内の対応する型オブジェクトへのポインターが格納されます。 ([Python ドキュメント][2])
概念的には:```c
typedef struct {
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;
```これは簡略化されていますが、本質的なモデルを捉えています。
すべてのオブジェクトは共有ヘッダーで始まります。```text
+--------------------+
| reference count |
+--------------------+
| type pointer |
+--------------------+
| object-specific |
| payload |
+--------------------+
```整数の場合、ペイロードには整数が格納されます。
リストの場合、ペイロードにはサイズ情報と要素参照の配列へのポインターが格納されます。
関数の場合、ペイロードにはコード オブジェクト、グローバル、デフォルト、クロージャ セル、および関連するメタデータが格納されます。
共通ヘッダーを使用すると、インタープリターはすべてのオブジェクトをトップレベルで均一に扱うことができます。
## 7.4 固定サイズおよび可変サイズのオブジェクト
CPython は、固定サイズのオブジェクトと可変サイズのオブジェクトを区別します。
固定サイズのオブジェクトは、基本オブジェクト ヘッダーを使用します。
可変サイズのオブジェクトには、追加のサイズ フィールドが含まれます。 C API ドキュメントでは次のように説明されています。`PyVarObject`の延長として`PyObject`それは追加します`ob_size`可変サイズのオブジェクトのフィールド。 ([Python ドキュメント][2])
概念的には:```c
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size;
} PyVarObject;
```長さ 2 のタプルと長さ 100 のタプルでは異なるストレージが必要なため、タプルは可変サイズです。
bytes オブジェクトは可変サイズです。
CPython は一連の数字を使用して任意精度の整数を格納するため、long 整数も可変サイズです。
おおよその形状:```text
PyObject
used by many fixed-size objects
PyVarObject
used by objects whose logical size is known at allocation time
```例:
|オブジェクトの種類 |ヘッダーの種類 |理由 |
| ----------- | ------------------------- | ------------------------------- |
|`float` | `PyObject`|固定サイズのペイロード |
|`tuple` | `PyVarObject`|要素の数は異なります |
|`bytes` | `PyVarObject`|バイト数は異なります |
|`int` | `PyVarObject`| Number of integer digits varies |
|`str`|特殊な可変レイアウト |文字列の長さは異なります |
## 7.5 オブジェクトタイプによる動作の定義
オブジェクトのタイプによって、サポートされる操作が決まります。 Python のデータ モデルはこれを言語レベルで定義します。オブジェクトには型があり、型によってサポートされる操作が決まります。 ([Python ドキュメント][1])
CPython レベルでは、オブジェクト ヘッダーの型ポインターは、`PyTypeObject`。
概念的には:```text
object
ob_refcnt
ob_type --------> type object
name
size
base classes
method table
number slots
sequence slots
mapping slots
call slot
attribute slots
```Python が評価するとき:```python
x + y
```CPython は自立型を検索しません`add`関数。関連する型機構に、それらのオブジェクトに対して加算がどのように機能するかを問い合わせます。
整数の場合、加算には整数固有のコードが使用されます。
文字列の場合、加算には文字列の連結が使用されます。
リストの場合、加算により連結リストが作成されます。
ユーザー定義クラスの場合、追加で呼び出すことができます。`__add__`。
構文は統一されています。実装は型指向です。
## 7.6 型はオブジェクトである
型はそれ自体がオブジェクトです。```python
print(type(42)) # <class 'int'>
print(type(int)) # <class 'type'>
print(type(type)) # <class 'type'>
```この再帰的な構造は意図的なものです。`int`オブジェクトです。`list`オブジェクトです。
ユーザー定義クラスはオブジェクトです。
ほとんどの型オブジェクトの型は次のとおりです。`type`。```python
class User:
pass
print(type(User)) # <class 'type'>
print(type(User())) # <class '__main__.User'>
```これは、クラスが動的に割り当て、受け渡し、保存、修飾、および作成できる理由を説明します。```python
def make_class():
class Item:
pass
return Item
C = make_class()
obj = C()
```class ステートメントはクラス オブジェクトを作成します。クラス名をそのオブジェクトにバインドします。
## 7.7 オブジェクトのレイアウトと型のレイアウト
CPython オブジェクトのレイアウトは、その型オブジェクトが期待するものと一致する必要があります。
CPython ソースのコメントは次のとおりです。`Include/object.h`オブジェクトには次の方法でアクセスすることに注意してください。`PyObject *`オブジェクトの移動またはサイズ変更には参照の更新が必要になるため、オブジェクトのサイズは割り当て後に変更されません。 ([GitHub][3])
その制約が重要なのです。
リストは拡大する可能性がありますが、リスト オブジェクト自体は、すべての要素を直接保持するために所定の位置に展開されるわけではありません。代わりに、再割り当て可能な個別の要素配列を所有します。
タプルは成長できません。その項目参照は、タプル割り当ての一部として保存されます。
概念的には:```text
list object
header
current length
allocated capacity
pointer to item array ---> [PyObject*, PyObject*, PyObject*, ...]
tuple object
header
length
inline item references ---> [PyObject*, PyObject*, PyObject*, ...]
```この違いは、タプルのサイズが固定されているにもかかわらず、リストの追加が効率的に償却できる理由を説明しています。
## 7.8 可変性
可変性とは、オブジェクトのアイデンティティは同じままでも、オブジェクトの値が変化する可能性があることを意味します。```python
xs = [1, 2]
before = id(xs)
xs.append(3)
after = id(xs)
print(before == after) # True
```リスト オブジェクトは同じオブジェクトのままです。その内容が変わりました。
不変オブジェクトは、その場で値を変更する操作を公開しません。```python
s = "abc"
t = s.upper()
print(s) # abc
print(t) # ABC
```文字列操作は別のオブジェクトを返します。
一般的な変更可能オブジェクトと不変更オブジェクト:
|可変 |不変 |
| -------------------- | ------------------------------------------------ |
|`list` | `int` |
| `dict` | `float` |
| `set` | `str` |
| `bytearray` | `bytes`|
|ほとんどのクラス インスタンス |`tuple`、含まれる参照が修正されている場合 |
タプルはコンテナとしては不変ですが、変更可能なオブジェクトを含めることができます。```python
t = ([],)
t[0].append(1)
print(t) # ([1],)
```タプルは依然として同じリストを指します。リストが変わりました。
## 7.9 参照セマンティクス
ほとんどの CPython ランタイム操作は、オブジェクト自体ではなく、オブジェクトへの参照を移動します。
関数呼び出しはオブジェクト参照を渡します。```python
def add_item(xs):
xs.append(1)
items = []
add_item(items)
print(items) # [1]
```関数は同じリスト オブジェクトへの参照を受け取ります。
ローカル名を再バインドしても呼び出し元には影響しません。```python
def replace(xs):
xs = [1, 2, 3]
items = []
replace(items)
print(items) # []
```名前`xs`関数内はリバウンドでした。元のリストは変更されていません。
この区別は API 設計にとって重要です。オブジェクトの変更とローカル変数の再バインドは別の操作です。
## 7.10 属性
オブジェクトは属性を公開できます。```python
class User:
pass
u = User()
u.name = "Ada"
print(u.name)
```通常のユーザー定義オブジェクトの場合、インスタンス属性は通常、インスタンス ディクショナリに保存されます。
概念的には:```text
u
type pointer ---> User
__dict__ -----> {"name": "Ada"}
```属性の検索は、辞書の直接検索よりも複雑です。 CPython では以下を考慮する必要があります。```text
data descriptors
instance dictionary
non-data descriptors
class attributes
base classes
__getattribute__
__getattr__
method binding
```例:```python
class User:
species = "human"
u = User()
u.name = "Ada"
print(u.name) # instance attribute
print(u.species) # class attribute
```属性がインスタンスに存在しない場合、CPython はクラスとそのベースを検索します。
## 7.11 メソッドは記述子です
クラスに格納された関数は、インスタンスを通じてアクセスされるとメソッドのように動作します。```python
class Counter:
def inc(self):
return 1
c = Counter()
print(c.inc)
c.incバインドされたメソッドです。関数オブジェクトとインスタンスを組み合わせますc。
この動作は記述子プロトコルに由来します。
記述子は、次の 1 つ以上を定義するオブジェクトです。python __get__(self, obj, objtype=None) __set__(self, obj, value) __delete__(self, obj) 関数の実装__get__したがって、インスタンスから取得されるときに自動的にバインドされます。
概念的な検索:```text Counter.inc raw function object
c.inc bound method: function = Counter.inc self = c
## 7.12 特別なメソッドとスロット
Python 構文は特別なメソッドにマップされます。
|構文 |特別な方法 |
| --------- | -------------- |
|`x + y` | `__add__` |
| `x[i]` | `__getitem__` |
| `x()` | `__call__` |
| `len(x)` | `__len__` |
| `iter(x)` | `__iter__` |
| `next(x)` | `__next__` |
| `x in y` | `__contains__` |
| `str(x)` | `__str__` |
| `repr(x)` | `__repr__`|
CPython レベルでは、これらの操作の多くは、`PyTypeObject`。
たとえば、タイプは、番号スロット、シーケンス スロット、マッピング スロット、および呼び出しスロットを提供する場合があります。
これは、次のような Python コードを意味します。```python
len(obj)
```単に呼び出すだけではありません`obj.__len__()`通常のインスタンス検索を通じて。 CPython は型レベルのプロトコル機構を使用します。これにより、一般的な操作がより速く、より一貫性のあるものになります。
## 7.13 組み込み型は特権実装を備えた通常のオブジェクトです
組み込み型は同じオブジェクト モデルに参加しますが、その実装は C で行われます。```python
print(type([])) # <class 'list'>
print(type({})) # <class 'dict'>
print(type("abc")) # <class 'str'>
```公式の組み込み型のドキュメントでは、主要な組み込み型が数値、シーケンス、マッピング、クラス、インスタンス、例外としてグループ化されています。 ([Python ドキュメント][4])
の違い`list`また、ユーザー定義クラスは、一方がオブジェクトでもう一方がオブジェクトではないというものではありません。どちらもオブジェクトです。違いは、`list`には、固定内部レイアウトと特殊なスロットを備えた C 実装があります。
Python リスト オブジェクトには、次のような実装の詳細が含まれます。```text
object header
logical length
allocated capacity
pointer to element storage
```dict にはハッシュ テーブルの実装が含まれます。
文字列には、Unicode 固有のレイアウトとキャッシュされたメタデータが含まれます。
これらの内部レイアウトは、CPython の実行時の動作に合わせて最適化されています。
## 7.14 クラスとインスタンス
クラスは、そのインスタンスによって共有される動作を定義します。```python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(10, 20)
```実行時:```text
Point
class object
type: type
attributes:
__init__
...
p
instance object
type: Point
attributes:
x = 10
y = 20
```クラスを呼び出すと建設機械が呼び出されます。```text
Point(10, 20)
call type object
allocate instance through __new__
initialize instance through __init__
return instance
```これが、構築をカスタマイズできる理由です。```python
class OnlyOne:
def __new__(cls):
print("allocate")
return super().__new__(cls)
def __init__(self):
print("initialize")
__new__オブジェクトを作成または返します。__init__それを初期化します。
7.15 継承とメソッドの解決
Python はクラスを介した継承をサポートしています。```python class Animal: def speak(self): return "?"
class Dog(Animal):
def speak(self):
return "woof"
インスタンスの属性を解決するとき、CPython はクラス メソッドの解決順序 (通常は MRO と呼ばれます) に従って検索します。python
print(Dog.mro)
単一継承の場合:text
Dog
Animal
object
多重継承の場合、Python は C3 線形化を使用します。python
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.mro)
## 7.16 属性の検索順序
通常の式の場合:```python
obj.name
```CPython は構造化検索を実行します。
簡略化した順序:```text
1. Look for data descriptors on the type or its bases.
2. Look in the instance dictionary.
3. Look for non-data descriptors or other class attributes.
4. If still missing, call __getattr__ if defined.
5. Otherwise raise AttributeError.
```データ記述子は以下を定義します`__set__`または`__delete__`。
非データ記述子は、次のことのみを定義します。`__get__`。
これはその理由を説明します`property`インスタンス辞書エントリをオーバーライドできます。```python
class User:
@property
def name(self):
return "computed"
u = User()
print(u.name)
```プロパティはデータ記述子です。通常のインスタンスのストレージよりも優れています。
## 7.17 オブジェクトの作成
オブジェクトの作成には通常 2 つの段階があります。```text
allocation
reserve memory for the object
initialization
set the initial object state
```Python レベルでは:```python
obj = cls.__new__(cls)
cls.__init__(obj)
```通常のコードでは、クラスを呼び出すと両方の手順が実行されます。
C レベルでは、型オブジェクトは次のようなスロットを通じて割り当てと初期化を制御します。```text
tp_new
tp_init
tp_alloc
tp_dealloc
```不変オブジェクトは作成時に値が必要となるため、この分離は重要です。
たとえば、タプルや int を空に作成し、パブリック操作を通じて最終値に自由に変更することはできません。最終的な値は、割り当てまたは構築中に確立されます。
## 7.18 オブジェクトの破壊
CPython でのオブジェクトの破棄は、主に参照カウントによって行われます。
オブジェクトの参照カウントがゼロになると、CPython は割り当て解除関数を呼び出します。
概念的には:```text
Py_DECREF(obj)
decrement reference count
if reference count == 0:
call type-specific deallocator
```デアロケータは、オブジェクトが所有する参照を解放し、補助メモリを解放し、オブジェクトのメモリをアロケータに返します。
コンテナの場合、割り当て解除は次のようにカスケードされる可能性があります。```python
xs = [[1], [2], [3]]
del xs
```外側のリストを削除すると、内側のリストへの参照が減ります。これらの内部リストに他の参照がない場合、それらも破棄されます。
参照カウントだけでは相互に存続させるオブジェクトを再利用できないため、サイクルでは循環ガベージ コレクションが必要です。
## 7.19 平等とアイデンティティ
アイデンティティと平等は違います。```python
a = [1, 2]
b = [1, 2]
print(a == b) # True
print(a is b) # False
==オブジェクトが等しいかどうかを尋ねます。is2 つの参照が同じオブジェクトを指しているかどうかを尋ねます。
CPython レベルでは次のようになります。```text is compare object pointers
==
dispatch rich comparison through type machinery
カスタム クラスは等価性を定義できます。python
class Point:
def init(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return (
isinstance(other, Point)
and self.x == other.x
and self.y == other.y
)
## 7.20 ハッシュ化
ハッシュでは、辞書キーと要素のセットがサポートされています。
辞書キーとして使用されるオブジェクトは、辞書に残っている間、安定したハッシュを持っている必要があります。```python
d = {}
d["name"] = "Ada"
```文字列は不変であるため、ハッシュ可能です。
リストは内容が変更される可能性があるため、ハッシュ化できません。```python
hash("abc") # works
hash((1, 2)) # works
hash([1, 2]) # TypeError
```カスタム クラスでは以下を定義できます。```python
__hash__
__eq__
```このルールは実際的です。等価性が変わる可能性がある場合、ハッシュ テーブルが壊れる可能性があります。可変オブジェクトは通常、値ベースのハッシュを避ける必要があります。
## 7.21 コンテナストアの参照
Python コンテナはオブジェクトへの参照を保存します。```python
xs = [object(), object(), object()]
```リストには 3 つのオブジェクトへの参照が保存されます。完全なオブジェクト データはインライン化されません。
概念的には:```text
list
items[0] ---> object A
items[1] ---> object B
items[2] ---> object C
```これは浅いコピーの動作を説明します。```python
a = [[1], [2]]
b = a.copy()
b[0].append(99)
print(a) # [[1, 99], [2]]
```外側のリストがコピーされました。内部リスト オブジェクトは共有されました。
ディープ コピーは、含まれるオブジェクトを再帰的にコピーします。```python
import copy
a = [[1], [2]]
b = copy.deepcopy(a)
```## 7.22 オブジェクトプロトコル
Python は、明示的なインターフェイスではなくプロトコルに依存します。
オブジェクトは、適切なメソッドを実装することによってプロトコルに参加します。
反復プロトコル:```python
class Count:
def __init__(self, stop):
self.current = 0
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.current >= self.stop:
raise StopIteration
value = self.current
self.current += 1
return value
```コンテナプロトコル:```python
class Bag:
def __init__(self):
self.items = []
def __contains__(self, item):
return item in self.items
```コンテキストマネージャープロトコル:```python
class Resource:
def __enter__(self):
return self
def __exit__(self, exc_type, exc, tb):
return False
```これらのプロトコルを使用すると、ユーザー定義オブジェクトが次のような構文で動作できるようになります。```python
for x in obj:
...
with obj:
...
if x in obj:
...
```オブジェクト モデルは、構文と動作の間の架け橋です。
## 7.23 便利な内部モデル
次のような Python 式:```python
result = obj.method(x) + y
```オブジェクト モデルの操作として読み取ることができます。```text
load obj
look up attribute "method"
bind method to obj if descriptor rules apply
load x
call bound method
load y
perform binary addition through type slots
bind result name to returned object
```すべてのステップでオブジェクト参照が移動または作成されます。
すべての操作は型情報によって仲介されます。
すべての結果は別のオブジェクト参照です。
## 7.24 最小限の C レベルのスケッチ
簡略化された拡張タイプは、CPython オブジェクト ヘッダーで始まります。```c
typedef struct {
PyObject_HEAD
long value;
} CounterObject;
```の`PyObject_HEAD`マクロは、CPython がこのメモリを Python オブジェクトとして扱うために必要な標準オブジェクト ヘッダーを提供します。次に、型オブジェクトは、このオブジェクトがどのように動作するかを記述します。```c
static PyTypeObject CounterType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "example.Counter",
.tp_basicsize = sizeof(CounterObject),
.tp_flags = Py_TPFLAGS_DEFAULT,
};
```実際の拡張タイプには、より多くのフィールド、初期化、メソッド、エラー処理、モジュールのセットアップ、および参照の所有権が必要です。しかし、形は同じです:```text
instance memory starts with object header
type object describes behavior
runtime manipulates the object through PyObject *
```## 7.25 概要
Python オブジェクト モデルでは、すべての値には ID、型、および値があるとされています。 CPython はこのモデルを次のように実現します。`PyObject *`pointers, object headers, reference counts, type objects, and operation slots.
A name binds to an object. A container stores references to objects.タイプは動作を定義します。クラスはオブジェクトです。 An instance points to its class. Syntax such as addition, calls, indexing, iteration, and attribute access is implemented through type-directed protocol machinery.
このモデルは、CPython の残りの部分 (メモリ管理、バイトコードの実行、属性検索、関数呼び出し、クラス、記述子、拡張モジュール、C API) の基礎となります。