#33. 属性の検索

属性検索は、次のような式を評価するために使用されるランタイム プロセスです。```python id="5ob0m9" obj.name


単純な属性式が大量の機械をトリガーする可能性があります。```text id="b2zmcv"
find the object's type
search the type and its base classes
handle descriptors
check the instance dictionary
call custom attribute hooks
return a value or raise AttributeError
```属性の検索は動的です。結果は、ランタイム オブジェクト、そのクラス、その基本クラス、そのインスタンス ディクショナリ、記述子、メタクラス、およびユーザー定義のフックによって異なります。

## 33.1 基本的な属性アクセス

式:```python id="67bubm"
obj.x
```CPython に、という名前の属性を検索するよう依頼します。`"x"`の上`obj`。

見つかった場合、ルックアップは Python オブジェクトを返します。

欠落している場合は値が上がります`AttributeError`。

例:```python id="l53g0p"
class C:
    pass

obj = C()
obj.x = 10

print(obj.x)
```割り当てストア`x`インスタンス辞書内:```text id="kz7fxs"
obj.__dict__["x"] = 10
```そこにアクセスすると見つかります。

## 33.2 属性検索は辞書検索だけではありません

単純なオブジェクトの場合、属性へのアクセスは辞書検索のようになります。```python id="m2qolp"
obj.__dict__["x"]
```ただし、完全な属性検索はより複雑です。

この表現:```python id="1v72yi"
obj.x
```以下が含まれる可能性があります:```text id="h2b2fw"
data descriptors
instance dictionary
non-data descriptors
class attributes
base classes
__getattribute__
__getattr__
slots
properties
metaclass behavior
```したがって、この等価性は不完全です。```python id="ue4afg"
obj.x == obj.__dict__["x"]
```それは単純な場合にのみ当てはまります。

## 33.3 インスタンス辞書

ほとんどの通常の Python オブジェクトにはインスタンス ディクショナリがあります。```python id="m08lyl"
class User:
    pass

u = User()
u.name = "Ada"
u.age = 37

print(u.__dict__)
```出力:```text id="7fbd65"
{'name': 'Ada', 'age': 37}
```クラスがスロットまたはカスタム レイアウトを使用しない限り、インスタンス属性はこのディクショナリに保存されます。

のために:```python id="v04kyu"
u.name
```CPython は見つけることができます`"name"`で`u.__dict__`。

ただし、インスタンス ディクショナリが常に最初にチェックされるわけではありません。クラスのデータ記述子が優先されます。

## 33.4 クラス属性

属性はクラス上で存続できます。```python id="f16lwf"
class User:
    kind = "human"

u = User()
print(u.kind)
```ここ、`kind`入っていない`u.__dict__`。にあります`User.__dict__`。

概念的には:```text id="lpc7u3"
u.__dict__ does not contain "kind"
User.__dict__ contains "kind"
return "human"
```クラス属性はクラス全体で共有されます。```python id="qoumbj"
a = User()
b = User()

print(a.kind)
print(b.kind)
```インスタンスがそれをシャドウしない限り、両方のインスタンスには同じクラス属性が表示されます。

## 33.5 インスタンス属性のシャドウイング

クラス属性がデータ記述子ではない場合、インスタンス属性はクラス属性をシャドウすることができます。```python id="g0f5q9"
class User:
    kind = "human"

u = User()
u.kind = "admin"

print(u.kind)
print(User.kind)
```出力:```text id="la6r0l"
admin
human
```今:```text id="hoyjwm"
u.__dict__["kind"] = "admin"
User.__dict__["kind"] = "human"
```インスタンス属性が優先されます`u.kind`。

これが、変更可能な値に対してクラス属性を慎重に使用する必要がある理由です。```python id="bfuqvu"
class Bag:
    items = []

a = Bag()
b = Bag()

a.items.append("x")
print(b.items)
```場合を除き、両方のインスタンスに同じリストが表示されます。`items`インスタンス上でオーバーライドされます。

## 33.6 属性の検索順序

通常のインスタンス属性アクセスの場合、検索順序はおおよそ次のとおりです。```text id="vtge5s"
1. Call type(obj).__getattribute__(obj, name)
2. Search the class and base classes for name
3. If a data descriptor is found, call descriptor.__get__
4. Otherwise, check obj.__dict__
5. If found in obj.__dict__, return that value
6. Otherwise, if a non-data descriptor was found, call descriptor.__get__
7. Otherwise, if a class attribute was found, return it
8. Otherwise, call __getattr__ if defined
9. Otherwise, raise AttributeError
```最も重要な優先ルールは次のとおりです。```text id="rkgfxz"
data descriptor
    before instance dictionary

instance dictionary
    before non-data descriptor

non-data descriptor or class attribute
    after instance dictionary
```この順序で、プロパティ、メソッド、スロット、シャドウイングについて説明します。

## 33.7 記述子

記述子は、1 つ以上の特別なメソッドを通じて属性アクセスを制御するオブジェクトです。```text id="hh6upi"
__get__
__set__
__delete__
```記述子はクラスに保存されます。

例:```python id="wgub37"
class Descriptor:
    def __get__(self, obj, cls):
        return "computed"

class C:
    x = Descriptor()

obj = C()
print(obj.x)
```アクセスする`obj.x`呼び出し:```python id="tbvfo8"
C.__dict__["x"].__get__(obj, C)
```記述子を使用すると、クラスは属性アクセスの意味をカスタマイズできます。

彼らは力を与えます:```text id="z7v9od"
methods
staticmethod
classmethod
property
slots
many built-in attributes
ORM fields
validation systems
lazy attributes
```## 33.8 データ記述子

データ記述子は以下を定義します`__set__`または`__delete__`、通常は`__get__`。```python id="lyqqpq"
class DataDescriptor:
    def __get__(self, obj, cls):
        return "from descriptor"

    def __set__(self, obj, value):
        print("set", value)

class C:
    x = DataDescriptor()

obj = C()
obj.__dict__["x"] = "from dict"

print(obj.x)
```出力:```text id="gvfbh3"
from descriptor
```記述子はデータ記述子であるため、インスタンス ディクショナリよりも優先されます。

このようにして`property`動作します。```python id="aqv8hu"
class C:
    @property
    def x(self):
        return 10

obj = C()
print(obj.x)
```プロパティ オブジェクトはデータ記述子です。

## 33.9 非データ記述子

非データ記述子は以下を定義します`__get__`しかしそうではありません`__set__`または`__delete__`。

クラスに格納されている通常の Python 関数は、データ記述子ではありません。```python id="sz0xpl"
class C:
    def f(self):
        return "method"

obj = C()
print(obj.f())
```関数記述子のバインド`obj`として`self`。

ただし、これはデータではないため、インスタンス属性によってシャドウされる可能性があります。```python id="o5g3nx"
obj.f = lambda: "instance function"
print(obj.f())
```これで、インスタンス ディクショナリが優先されます。

このため、インスタンスごとにメソッドを置き換えることができます。

## 33.10 プロパティ

プロパティは、属性アクセス中に関数を呼び出す記述子です。```python id="78465u"
class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return 3.14159 * self.radius * self.radius

c = Circle(10)
print(c.area)
```式:```python id="vxxcx9"
c.area
```プロパティゲッターを呼び出します。

明示的なものはありません`()`呼び出しは記述子アクセス内に隠されているため、ソース内に存在します。

セッターは代入動作を追加します。```python id="b245cc"
class User:
    def __init__(self):
        self._name = ""

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value.strip()
```割り当て:```python id="acg7af"
u.name = " Ada "
```プロパティセッターを呼び出します。

## 33.11 記述子としてのメソッド

クラスに格納される関数は記述子です。```python id="3l3xgm"
class C:
    def f(self):
        return 1

obj = C()
m = obj.f
```ルックアップ:```python id="fp1ymr"
obj.f
```関数オブジェクトの記述子ロジックを呼び出し、バインドされたメソッドを生成します。

概念的には:```text id="1e8hzy"
C.__dict__["f"].__get__(obj, C)
    -> bound method
```バインドされたメソッドには以下が格納されます。```text id="257g0u"
function = C.f
self = obj
```それから:```python id="jh0x78"
obj.f()
```関数を呼び出します`self`自動的に挿入されます。

##33.12`staticmethod`そして`classmethod`

`staticmethod`そして`classmethod`記述子ラッパーです。

静的メソッドはバインディングを無効にします。```python id="hmwv5c"
class Math:
    @staticmethod
    def add(a, b):
        return a + b

Math.add(1, 2)
Math().add(1, 2)
```いいえ`self`または`cls`が挿入されます。

クラス メソッドはクラスをバインドします。```python id="0p2qg1"
class User:
    @classmethod
    def make(cls):
        return cls()
```電話をかける:```python id="jpqkoq"
User.make()
```パスする`User`最初の引数として。

インスタンスを介して呼び出すと、クラスも渡されます。```python id="x77e2w"
User().make()
```## 33.13 スロット`__slots__`インスタンス属性ストレージを変更します。```python id="q0q88q"
class Point:
    __slots__ = ("x", "y")

    def __init__(self, x, y):
        self.x = x
        self.y = y
```のインスタンス`Point`通常、正常はありません`__dict__`明示的に要求された場合を除きます。```python id="jmosbt"
p = Point(1, 2)
print(p.x)
```スロット属性は、クラスに格納されている記述子を使用して実装されます。

概念的には:```text id="cq58r6"
Point.__dict__["x"] = slot descriptor
Point.__dict__["y"] = slot descriptor
```アクセスする`p.x`スロット記述子を呼び出し、オブジェクト レイアウト内の固定位置から読み取ります。

スロットによりメモリ使用量が削減され、一部の属性アクセス パターンが高速化されますが、動的インスタンス属性も制限されます。

## 33.14 欠落している属性

通常の検索が失敗した場合、Python は`__getattr__`。```python id="r0ezoa"
class Dynamic:
    def __getattr__(self, name):
        if name == "answer":
            return 42
        raise AttributeError(name)

d = Dynamic()
print(d.answer)

__getattr__は、通常のルックアップ パスが失敗した後にのみ呼び出されます。

これは次の場合に役立ちます。text id="16oz5d" lazy loading proxy objects RPC clients compatibility layers dynamic APIs mock objects もし__getattr__も失敗します、それは上がるはずですAttributeError

##33.15__getattribute__

__getattribute__すべての通常の属性アクセスをインターセプトします。```python id="k4hcdi" class Traced: def getattribute(self, name): print("lookup", name) return super().getattribute(name)

def f(self):
    return 1

obj = Traced() obj.f() ```すべてのアクセスが通過します__getattribute__、メソッド検索を含む。

カスタム実装では、無限再帰を回避する必要があります。python id="6fn49h" class Bad: def __getattribute__(self, name): return self.__dict__[name] アクセスするself.__dict__電話__getattribute__また。

使用object.__getattribute__またはsuper().__getattribute__:

class Good:
    def __getattribute__(self, name):
        return object.__getattribute__(self, name)
```## 33.16 属性の割り当て

割り当て:```python id="in3koz"
obj.x = value
```属性設定ロジックを使用します

大まかな順序は次のとおりです。```text id="mya0tv"
1. Call type(obj).__setattr__(obj, name, value)
2. If a data descriptor with __set__ exists, use it
3. Otherwise, store into obj.__dict__ if available
4. Otherwise, use slot storage if applicable
5. Otherwise, raise AttributeError
```カスタム`__setattr__`割り当てをインターセプトできる:```python id="x89t2s"
class C:
    def __setattr__(self, name, value):
        print("set", name, value)
        super().__setattr__(name, value)

obj = C()
obj.x = 10
```同様に`__getattribute__`、不注意なコードが再発する可能性があります

## 33.17 属性の削除

削除:```python id="annac7"
del obj.x
```削除ロジックを使用します

以下を呼び出す可能性があります:```text id="wvs69o"
type(obj).__delattr__
descriptor.__delete__
remove from instance dictionary
clear slot value
```記述子の例:```python id="05vatk"
class D:
    def __delete__(self, obj):
        print("delete")

class C:
    x = D()

obj = C()
del obj.x
```削除は同じ記述子および属性プロトコル ファミリの一部です

## 33.18 クラス属性の検索

クラスもオブジェクトです。```python id="n3g515"
class C:
    x = 10

print(C.x)
```見上げる`C.x`クラスオブジェクトの属性アクセスです

の種類`C`通常は`type`したがってクラス属性の検索はメタクラスの動作によって制御されます

概念的には:```text id="qro2hh"
object = C
type(object) = type
lookup attribute "x"
```これがメタクラスがクラスレベルの属性アクセスをカスタマイズできる理由です

## 33.19 メタクラス属性の検索

メタクラスはクラスで表示される属性を定義できます。```python id="2pzc6p"
class Meta(type):
    def meta_method(cls):
        return "meta"

class C(metaclass=Meta):
    pass

print(C.meta_method())
```このメソッドはメタクラス上にありクラス オブジェクトにバインドされています

クラス属性の検索には次のことが含まれます。```text id="3rcosr"
attributes on the class itself
descriptors in the metaclass
metaclass MRO
```メタクラス ルックアップはインスタンス ルックアップとは別のものですが同じ一般的なオブジェクト モデルを使用します

## 33.20 モジュール属性の検索

モジュールには属性辞書があります。```python id="16mk8q"
import math
print(math.pi)
```これはおおよそ次のとおりです。```text id="iw6moh"
math.__dict__["pi"]
```モジュールは定義することもできます`__getattr__`属性が欠落している場合。```python id="a4cpwz"
# module.py
def __getattr__(name):
    if name == "lazy":
        return load_lazy()
    raise AttributeError(name)
```それから:```python id="pme0mb"
module.lazy
```動的に計算することができます

この機能は遅延インポートや互換性シムによく使用されます

## 33.21 属性の検索と継承

たとえばクラスの検索はメソッドの解決順序に従います。```python id="nri7kz"
class A:
    x = "A"

class B(A):
    pass

obj = B()
print(obj.x)
```ルックアップ検索:```text id="3gw1ky"
B
A
object
```注文は次の場所に保存されます。```python id="il7bpl"
print(B.__mro__)
```多重継承ではC3 線形化を使用して一貫した MRO を計算します。```python id="iob9c0"
class A: pass
class B: pass
class C(A, B): pass

print(C.__mro__)
```属性の検索はこの順序に依存します

## 33.22 属性の検索と`super`

`super()`ルックアップが開始される場所が変わります。```python id="plcgfy"
class Base:
    def f(self):
        return "base"

class Child(Base):
    def f(self):
        return super().f()
```内部`Child.f`、`super().f`後検索`Child`MRO 内で見つかったメソッドを元のインスタンスにバインドします

概念的には:```text id="zj3hcg"
MRO: Child, Base, object
super from Child
search Base, then object
bind result to self

super()単純な親クラスへのアクセスではありません。これは、MRO 相対記述子の検索です。

33.23 特別なメソッドの検索

特別なメソッドは、通常のインスタンス属性の検索ではなく、型で検索されることがよくあります。

例:python id="2r7t8j" len(obj) タイプの長さのスロットを使用します。単に次のことを実行するだけではありません。python id="5oiizr" obj.__len__() この区別は重要です。```python id="76o4mf" class C: def len(self): return 10

obj = C() obj.len = lambda: 20

print(obj.len()) print(len(obj)) ```明示的な呼び出しにより、インスタンス属性が見つかる可能性があります。のlen()この操作では、型を介した特別なメソッド検索を使用します。

特別なメソッド ルックアップは、オブジェクト プロトコルの速度と一貫性を考慮して設計されています。

33.24 バイトコードでの属性検索

属性アクセスは、次のようなバイトコード命令にコンパイルされます。text id="wifw5q" LOAD_ATTR STORE_ATTR DELETE_ATTR LOAD_METHOD のために:python id="it1gq1" value = obj.x 概念的なバイトコード:text id="gp7253" LOAD_FAST obj LOAD_ATTR x STORE_FAST value のために:python id="6xdbsd" obj.x = value 概念的なバイトコード:text id="id6s81" LOAD_FAST value LOAD_FAST obj STORE_ATTR x メソッド呼び出しの場合:```python id="qou4id" obj.f(arg)


## 33.25 属性検索でコードを実行できる

属性検索は必ずしも受動的であるとは限りません。

この表現:```python id="i49h4b"
obj.x
```以下を通じてユーザー コードを呼び出すことができます。```text id="tqt8ka"
__getattribute__
descriptor __get__
property getter
__getattr__
metaclass hooks
module __getattr__
```したがって、属性アクセスでは次のことが可能になります。```text id="gq9mxu"
raise exceptions
mutate state
perform I/O
allocate objects
return different values each time
call arbitrary Python code
```例:```python id="lqpgto"
class C:
    @property
    def x(self):
        print("computed")
        return 10

obj = C()
obj.x
obj.x
```ゲッターは毎回実行されます。

## 33.26 属性の検索と例外

属性が欠落している場合、Python `AttributeError````python id="u5hs78"
obj.missing
```ただし、属性の検索では他の例外も発生する可能性があります。

例:```python id="yxnfz8"
class C:
    @property
    def x(self):
        raise RuntimeError("failed")

obj = C()
obj.x
```これにより、`RuntimeError` ない`AttributeError`

通常、欠落している属性のみが発生するはずです`AttributeError`。などのツール`hasattr`この規約に依存します。```python id="glsscx"
hasattr(obj, "x")
```ルックアップとキャッチを試みることで機能します`AttributeError`

## 33.27 属性の検索と`hasattr`

`hasattr(obj, name)`属性検索を呼び出します。```python id="sf1vip"
hasattr(obj, "x")
```おおよそ次のとおりです。```python id="d7n5ls"
try:
    getattr(obj, "x")
except AttributeError:
    return False
else:
    return True
```これはつまり`hasattr`ユーザーコードを実行できます。

プロパティゲッターがレイズした場合`RuntimeError``hasattr`は、それを欠落属性として扱いません。```python id="6ci9p0"
class C:
    @property
    def x(self):
        raise RuntimeError("boom")

hasattr(C(), "x")
````RuntimeError`伝播します。

## 33.28 属性の検索と`getattr`

`getattr`動的属性検索を実行します。```python id="uk5dyw"
getattr(obj, "name")
```は以下と同等です:```python id="qatskj"
obj.name
```属性名が静的にわかっている場合。

デフォルトもサポートしています。```python id="l96rs7"
getattr(obj, "missing", default)
```これは戻ります`default`ルックアップが発生した場合のみ`AttributeError`

記述子またはカスタム検索フックからの任意の例外は抑制されません。

## 33.29 属性の検索と`setattr`

`setattr`動的な属性割り当てを実行します。```python id="j47b3p"
setattr(obj, "x", 10)
```は以下と同等です:```python id="1q0s4t"
obj.x = 10
```静的な名前の場合。

それは依然として以下を尊重します:```text id="amsxsl"
__setattr__
data descriptors
slots
read-only attributes
```それで`setattr`は生の辞書書き込みではありません。

## 33.30 属性の検索と`delattr`

`delattr`動的属性の削除を実行します。```python id="xe7qva"
delattr(obj, "x")
```は以下と同等です:```python id="mbgk3j"
del obj.x
```それは依然として以下を尊重します:```text id="gnvxp9"
__delattr__
descriptor __delete__
slot deletion
instance dictionary deletion
```## 33.31 属性ルックアップとインラインキャッシュ

属性の検索は頻繁に行われるため、CPython によって最適化されます。

繰り返しのアクセス:```python id="xtr2dq"
for obj in objects:
    total += obj.value
```同じ属性レイアウトが何度もヒットする可能性があります。

CPython は、インライン キャッシュ データをバイトコード命令に添付できます。キャッシュには次のような事実が保存される場合があります。```text id="ooumb4"
expected receiver type
type version tag
dictionary version
descriptor result
slot offset
instance dictionary offset
```その後の実行時:```text id="ylpgjg"
if guards still hold:
    use fast path
else:
    fall back to generic lookup
```これにより、動的なセマンティクスが維持され、安定したケースが高速化されます。

## 33.32 キャッシュの無効化

Python ではクラスの変更が可能です。```python id="uqb4tq"
class C:
    x = 1

obj = C()
print(obj.x)

C.x = 2
print(obj.x)
```2 番目の検索では必ず確認する必要があります`2`

したがって、属性キャッシュは古い結果をやみくもに再利用することはできません。変化を防ぐ必要があります。

典型的なガードには次のものが含まれます。```text id="1kbbj7"
type identity
type version
dictionary version
descriptor kind
instance layout
```仮定が失敗すると、CPython は汎用ルックアップに戻り、キャッシュを更新する可能性があります。

## 33.33 インスタンスのレイアウトの安定性

属性キャッシュは、オブジェクトのレイアウトが安定している場合に最適に機能します。

例:```python id="cf08rl"
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

points = [Point(i, i + 1) for i in range(1000)]

for p in points:
    p.x
```それぞれ`p`同じタイプであり、同様の属性レイアウトを持っている可能性があります。これは専門化に適したケースです。

動的な変更により、キャッシュの効率が低下する可能性があります。```python id="nb80hw"
p.z = 10
del p.x
Point.x = property(...)
```インタプリタはまず正確さを維持する必要があります。

## 33.34 属性の検索と辞書

インスタンス ディクショナリは高度に最適化されています。

CPython 辞書はオブジェクト名前空間の中心です。```text id="hr03df"
module namespace
class namespace
instance namespace
globals
builtins
```多くのオブジェクトでは、属性検索は最終的には辞書検索と記述子の処理になります。

属性キャッシュは多くの場合、ディクショナリのバージョン管理に依存するため、インタプリタは名前空間が変更されたかどうかを知ることができます。

## 33.35 属性ルックアップとモジュール

モジュールのグローバルも辞書ベースです。```python id="z6frja"
import math
math.sqrt
```モジュール オブジェクトは、その辞書に属性を格納します。

多くの場合、モジュールの検索はインスタンス記述子の検索より簡単ですが、モジュールは欠落している属性の動作をカスタマイズできます。`__getattr__`

インポートはモジュール属性もバインドします。```python id="3y1uib"
import package.submodule
```インポート後、パッケージには`submodule`属性。

## 33.36 属性ルックアップとビルトイン

組み込み型では、多くの場合、特殊な内部レイアウトと記述子が使用されます。

例:```python id="pswgfz"
list.append
dict.get
str.upper
int.bit_length
```これらの属性は通常、C で実装されたメソッド記述子です。

インスタンスを介したアクセス:```python id="c2qhto"
[].append
```バインドされた組み込みメソッド オブジェクトを返します。

即時呼び出しでは、最適化されたメソッド呼び出しパスが使用される場合があります。

## 33.37 属性の検索とメモリ管理

属性ルックアップは参照を操作します。

属性が返された場合、CPython は結果が有効であることを確認する必要があります。

ルックアップによってバインドされたメソッド、プロパティの結果、または記述子の結果が作成される場合、参照の所有権は正しく処理される必要があります。

ルックアップ中に一時オブジェクトが作成される場合があります。```text id="yd6imc"
bound methods
property return values
descriptor return values
exception objects
strings or proxy objects in custom hooks
```障害パスはクリーンアップする必要があります。

属性検索では Python コードを呼び出すことができるため、参照の安全性が重要です。

## 33.38 属性の検索と再入

属性検索は Python に再入力できます。

例:```python id="cjrgl2"
class C:
    def __getattribute__(self, name):
        return compute_value(name)
```への呼び出し`compute_value`任意の Python コードを実行する可能性があります。

これは、1 回の属性検索中に、Python コードで次のことができることを意味します。```text id="pbs3p5"
mutate the object
mutate the class
change descriptors
trigger garbage collection
raise exceptions
call back into the same object
```ルックアップの実装はこれを許容する必要があります。

## 33.39 属性の検索とプロキシ

プロキシ オブジェクトは多くの場合、カスタム属性ルックアップを実装します。```python id="gmm0jz"
class Proxy:
    def __init__(self, target):
        self._target = target

    def __getattr__(self, name):
        return getattr(self._target, name)
```それから:```python id="t9wo3d"
proxy.x
```代表者:```python id="mwraoc"
target.x
```暗黙的な特殊メソッドの検索は通常のインスタンス属性の検索をバイパスすることが多いため、堅牢なプロキシは特殊メソッドを慎重に処理する必要があります。

たとえば、実装する`__getattr__`一人では作れないかもしれない`len(proxy)`に委任する`len(target)`

## 33.40 属性ルックアップと ORM

多くの ORM は記述子を使用します。

形状の例:```python id="9lk1n2"
class Field:
    def __get__(self, obj, cls):
        return obj._data[self.name]

    def __set__(self, obj, value):
        obj._data[self.name] = value

class User:
    name = Field()
```それから:```python id="gygh09"
user.name
```プレーン属性は読み取れません。それは呼びます`Field.__get__`

これにより、ライブラリは以下を実装できるようになります。```text id="m7h3nv"
validation
lazy loading
database column mapping
change tracking
computed fields
relationship loading
```記述子は、属性構文をプログラム可能なアクセスに変換します。

## 33.41 属性検索とデータクラス

データクラスは属性の検索を基本的に変更しません。```python id="itowya"
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int
```スロットが有効になっていない限り、インスタンスは通常、フィールドをインスタンス ディクショナリに保存します。```python id="2es6x4"
p = Point(1, 2)
print(p.x)
```これは通常のインスタンス属性の検索です。

スロット付き:```python id="zkk0iv"
@dataclass(slots=True)
class Point:
    x: int
    y: int
```フィールド ストレージは、通常のインスタンス ディクショナリではなくスロットを使用します。

## 33.42 属性ルックアップと型オブジェクト

型はオブジェクトであり、属性もあります。```python id="veiddt"
int.bit_length
str.upper
dict.items
```これらは型オブジェクトの属性です。

インスタンスの属性を検索する場合、インスタンスのタイプによって検索が制御されます。

クラスの属性を検索する場合、メタクラスは検索を制御します。

この再帰的オブジェクト モデルは Python の中心です。```text id="7jbz3r"
object has type
type is also an object
type has metaclass
metaclass controls class attribute lookup
```## 33.43 属性の検索とパフォーマンス

属性アクセスには、ローカル変数アクセスよりもオーバーヘッドが大きくなります。

比較する:```python id="2vbgf5"
x
```関数内で、ここで`x`ローカルです:```text id="quibql"
LOAD_FAST
```と:```python id="h0qo06"
obj.x
```それには以下が必要です:```text id="p6kgjq"
LOAD_FAST obj
LOAD_ATTR x

LOAD_ATTR記述子ロジック、辞書検索、キャッシュ チェック、型バージョン チェック、およびエラー処理が含まれる場合があります。

ホット ループがローカル バインディングから恩恵を受ける場合があるのはこのためです。```python id="3tqoir" append = xs.append for item in items: append(item)


## 33.44 ホットパスの属性検索とエラー

属性検索の失敗は例外を引き起こすため、比較的負荷が高くなります。

例:```python id="uacsdx"
try:
    value = obj.missing
except AttributeError:
    value = default
```ミスが頻繁にある場合は、辞書または明示的なセンチネルを使用する方が高速な場合があります。

しかし、最良の設計はセマンティクスに依存します。欠席が例外的な場合、または動的 API と対話する場合、属性検索の失敗は正しく、慣用的です。

## 33.45 属性検索の検査

使用する`vars`インスタンス ディクショナリを検査するには:```python id="pqoskd"
class C:
    pass

obj = C()
obj.x = 1

print(vars(obj))
```クラス辞書を使用します。```python id="zw539h"
print(C.__dict__)
```MRO を使用します。```python id="oqmr19"
print(C.__mro__)
```使用`inspect.getattr_static`多くの場合、通常の動的検索をトリガーせずに属性を検査するには、次のようにします。```python id="kb22m2"
import inspect

inspect.getattr_static(obj, "x")
```これは、記述子や`__getattr__`コードを実行します。

## 33.46 最小限の属性検索モデル

簡略化された検索モデル:```python id="rjhnk7"
def lookup(obj, name):
    cls = type(obj)

    class_attr = find_in_mro(cls, name)

    if is_data_descriptor(class_attr):
        return class_attr.__get__(obj, cls)

    if hasattr(obj, "__dict__") and name in obj.__dict__:
        return obj.__dict__[name]

    if has_get(class_attr):
        return class_attr.__get__(obj, cls)

    if class_attr is not missing:
        return class_attr

    getattr_hook = find_in_mro(cls, "__getattr__")
    if getattr_hook is not missing:
        return getattr_hook(obj, name)

    raise AttributeError(name)
```これでは重要な実際の詳細が省略されています。```text id="vn6j5o"
custom __getattribute__
metaclasses
slots internals
C-level fast paths
reference counts
inline caches
error handling
module lookup
special method lookup
```ただし、通常の記述子の優先順位がキャプチャされます。

## 33.47 よくある誤解

|誤解 |正しいモデル |
|---|---|
|`obj.x`いつも読む`obj.__dict__["x"]`|データ記述子、スロット、クラス属性、フックが介入する可能性があります。
|メソッドは各インスタンスに保存されます。通常のメソッドはクラスに保存され、検索中にバインドされます。
|プロパティはフィールドです |プロパティは関数を呼び出す記述子です。
|`__getattr__`すべての検索を処理します。通常の検索が失敗した後にのみ実行されます。
|`__getattribute__`欠落している属性のみを処理します。すべての通常の属性アクセスを処理します。
|インスタンス属性は常にクラス属性よりも優れています。データ記述子はインスタンス属性に勝ります。
|特別なメソッドは常に通常の検索を使用します。多くはタイプ スロットを通じて検索されます。
|属性アクセスには副作用はありません |任意の Python コードを実行できます。

## 33.48 読書戦略

属性の検索を検討するには、次の順序で例を構築します。```python id="xp7o07"
class C:
    x = 1
```次に、以下を追加します。```python id="jdu7zp"
obj.x = 2
```次にメソッドを追加します。```python id="yzo90g"
def f(self): ...
```次に、それを次のように置き換えます。```python id="nku86v"
@property
def x(self): ...
```次に、以下を追加します。```python id="6ho4rb"
__getattr__
__getattribute__
__slots__
staticmethod
classmethod
multiple inheritance
metaclass
```ステップごとに、以下を検査します。```python id="eaahv5"
vars(obj)
C.__dict__
C.__mro__
type(obj)
```そして、アクセス サイトを逆アセンブルします。```python id="xhk4lr"
import dis

def read(obj):
    return obj.x

dis.dis(read)
```これにより、構文からオブジェクト モデルの動作までの実用的なマップが構築されます。

## 33.49 章の概要

属性検索は背後にあるメカニズムです`obj.name`。これは、オブジェクト タイプ、クラス ディクショナリ、インスタンス ディクショナリ、記述子、継承、カスタム フック、スロット、モジュール、メタクラス、およびインライン キャッシュを含む動的プロトコルです。

通常のインスタンス属性のコア検索優先順位は次のとおりです。```text id="t5lrtf"
data descriptor
instance dictionary
non-data descriptor
class attribute
__getattr__
AttributeError
```この順序では、メソッド、プロパティ、シャドウイング、スロット、および多くのフレームワーク パターンについて説明します。

CPython は、特殊なバイトコード パスとインライン キャッシュを使用して属性検索を大幅に最適化しますが、Python の動的セマンティクスを保持する必要があります。クラスは変更でき、インスタンスは変更でき、記述子はコードを実行でき、カスタム フックはプロセス全体をオーバーライドできます。