#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) の基礎となります。