33. Tra cứu thuộc tính
#33. Tra cứu thuộc tính
Tra cứu thuộc tính là quá trình chạy được sử dụng để đánh giá các biểu thức như:```python id="5ob0m9" obj.name
Một biểu thức thuộc tính đơn giản có thể kích hoạt một lượng lớn máy móc:```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
```Tra cứu thuộc tính là động. Kết quả có thể phụ thuộc vào đối tượng thời gian chạy, lớp của nó, các lớp cơ sở, từ điển cá thể, bộ mô tả, siêu dữ liệu và các hook do người dùng định nghĩa.
## 33.1 Truy cập thuộc tính cơ bản
Biểu thức:```python id="67bubm"
obj.x
```yêu cầu CPython tra cứu thuộc tính có tên`"x"`TRÊN`obj`.
Nếu tìm thấy, việc tra cứu sẽ trả về một đối tượng Python.
Nếu thiếu, nó sẽ tăng lên`AttributeError`.
Ví dụ:```python id="l53g0p"
class C:
pass
obj = C()
obj.x = 10
print(obj.x)
```Cửa hàng nhiệm vụ`x`trong từ điển ví dụ:```text id="kz7fxs"
obj.__dict__["x"] = 10
```Việc truy cập tìm thấy nó ở đó.
## 33.2 Tra cứu thuộc tính không phải chỉ tra cứu từ điển
Đối với các đối tượng đơn giản, việc truy cập thuộc tính có thể giống như tra cứu từ điển:```python id="m2qolp"
obj.__dict__["x"]
```Nhưng việc tra cứu thuộc tính đầy đủ thì phức tạp hơn.
Biểu thức này:```python id="1v72yi"
obj.x
```có thể liên quan đến:```text id="h2b2fw"
data descriptors
instance dictionary
non-data descriptors
class attributes
base classes
__getattribute__
__getattr__
slots
properties
metaclass behavior
```Vì vậy, sự tương đương này là không đầy đủ:```python id="ue4afg"
obj.x == obj.__dict__["x"]
```Nó chỉ đúng trong những trường hợp đơn giản.
## 33.3 Từ điển sơ thẩm
Hầu hết các đối tượng Python bình thường đều có một từ điển mẫu.```python id="m08lyl"
class User:
pass
u = User()
u.name = "Ada"
u.age = 37
print(u.__dict__)
```Đầu ra:```text id="7fbd65"
{'name': 'Ada', 'age': 37}
```Thuộc tính phiên bản được lưu trữ trong từ điển này trừ khi lớp sử dụng vị trí hoặc bố cục tùy chỉnh.
Vì:```python id="v04kyu"
u.name
```CPython có thể tìm thấy`"name"`TRONG`u.__dict__`.
Nhưng từ điển mẫu không phải lúc nào cũng được kiểm tra đầu tiên. Bộ mô tả dữ liệu trên lớp được ưu tiên.
## 33.4 Thuộc tính lớp
Các thuộc tính có thể tồn tại trên lớp.```python id="f16lwf"
class User:
kind = "human"
u = User()
print(u.kind)
```Đây,`kind`không có trong`u.__dict__`. Nó ở trong`User.__dict__`.
Về mặt khái niệm:```text id="lpc7u3"
u.__dict__ does not contain "kind"
User.__dict__ contains "kind"
return "human"
```Thuộc tính lớp được chia sẻ thông qua lớp:```python id="qoumbj"
a = User()
b = User()
print(a.kind)
print(b.kind)
```Cả hai phiên bản đều nhìn thấy thuộc tính lớp giống nhau trừ khi một phiên bản che khuất nó.
## 33.5 Đánh bóng thuộc tính sơ thẩm
Một thuộc tính thể hiện có thể che khuất một thuộc tính lớp khi thuộc tính lớp không phải là bộ mô tả dữ liệu.```python id="g0f5q9"
class User:
kind = "human"
u = User()
u.kind = "admin"
print(u.kind)
print(User.kind)
```Đầu ra:```text id="la6r0l"
admin
human
```Hiện nay:```text id="hoyjwm"
u.__dict__["kind"] = "admin"
User.__dict__["kind"] = "human"
```Thuộc tính instance thắng cho`u.kind`.
Đây là lý do tại sao các thuộc tính lớp nên được sử dụng cẩn thận cho các giá trị có thể thay đổi:```python id="bfuqvu"
class Bag:
items = []
a = Bag()
b = Bag()
a.items.append("x")
print(b.items)
```Cả hai trường hợp đều thấy cùng một danh sách trừ khi`items`được ghi đè trên ví dụ.
## 33.6 Thứ tự tra cứu thuộc tính
Để truy cập thuộc tính phiên bản thông thường, thứ tự tra cứu đại khái là:```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
```Nguyên tắc ưu tiên quan trọng nhất là:```text id="rkgfxz"
data descriptor
before instance dictionary
instance dictionary
before non-data descriptor
non-data descriptor or class attribute
after instance dictionary
```Thứ tự này giải thích các thuộc tính, phương thức, vị trí và tạo bóng.
## 33.7 Bộ mô tả
Bộ mô tả là một đối tượng kiểm soát quyền truy cập thuộc tính thông qua một hoặc nhiều phương thức đặc biệt:```text id="hh6upi"
__get__
__set__
__delete__
```Mô tả được lưu trữ trên các lớp.
Ví dụ:```python id="wgub37"
class Descriptor:
def __get__(self, obj, cls):
return "computed"
class C:
x = Descriptor()
obj = C()
print(obj.x)
```Truy cập`obj.x`cuộc gọi:```python id="tbvfo8"
C.__dict__["x"].__get__(obj, C)
```Bộ mô tả cho phép các lớp tùy chỉnh ý nghĩa của việc truy cập thuộc tính.
Họ quyền lực:```text id="z7v9od"
methods
staticmethod
classmethod
property
slots
many built-in attributes
ORM fields
validation systems
lazy attributes
```## 33.8 Bộ mô tả dữ liệu
Bộ mô tả dữ liệu xác định`__set__`hoặc`__delete__`, thường với`__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)
```Đầu ra:```text id="gvfbh3"
from descriptor
```Bộ mô tả chiếm ưu thế hơn từ điển cá thể vì nó là bộ mô tả dữ liệu.
Đây là cách`property`hoạt động.```python id="aqv8hu"
class C:
@property
def x(self):
return 10
obj = C()
print(obj.x)
```Đối tượng thuộc tính là một bộ mô tả dữ liệu.
## 33.9 Bộ mô tả phi dữ liệu
Một bộ mô tả phi dữ liệu xác định`__get__`nhưng không`__set__`hoặc`__delete__`.
Các hàm Python thông thường được lưu trữ trên các lớp là các bộ mô tả phi dữ liệu.```python id="sz0xpl"
class C:
def f(self):
return "method"
obj = C()
print(obj.f())
```Bộ mô tả chức năng liên kết`obj`BẰNG`self`.
Nhưng vì nó không phải là dữ liệu nên một thuộc tính thể hiện có thể che khuất nó:```python id="o5g3nx"
obj.f = lambda: "instance function"
print(obj.f())
```Bây giờ từ điển cá thể sẽ thắng.
Đây là lý do tại sao các phương thức có thể được thay thế cho mỗi phiên bản.
## 33.10 Thuộc tính
Thuộc tính là một bộ mô tả gọi các hàm trong quá trình truy cập thuộc tính.```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)
```Biểu thức:```python id="vxxcx9"
c.area
```gọi trình thu thập tài sản.
Không có rõ ràng`()`trong nguồn vì lệnh gọi bị ẩn bên trong quyền truy cập của bộ mô tả.
Trình thiết lập thêm hành vi gán:```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()
```Phân công:```python id="acg7af"
u.name = " Ada "
```gọi trình thiết lập thuộc tính.
## 33.11 Các phương thức làm bộ mô tả
Các hàm được lưu trữ trên các lớp là các bộ mô tả.```python id="3l3xgm"
class C:
def f(self):
return 1
obj = C()
m = obj.f
```Việc tra cứu:```python id="fp1ymr"
obj.f
```gọi logic mô tả của đối tượng hàm và tạo ra một phương thức bị ràng buộc.
Về mặt khái niệm:```text id="1e8hzy"
C.__dict__["f"].__get__(obj, C)
-> bound method
```Phương thức ràng buộc lưu trữ:```text id="257g0u"
function = C.f
self = obj
```Sau đó:```python id="jh0x78"
obj.f()
```gọi hàm với`self`tự động chèn vào.
## 33,12`staticmethod`Và`classmethod`
`staticmethod`Và`classmethod`là các trình bao bọc mô tả.
Một phương thức tĩnh vô hiệu hóa ràng buộc:```python id="hmwv5c"
class Math:
@staticmethod
def add(a, b):
return a + b
Math.add(1, 2)
Math().add(1, 2)
```KHÔNG`self`hoặc`cls`được chèn vào.
Một phương thức lớp liên kết lớp:```python id="0p2qg1"
class User:
@classmethod
def make(cls):
return cls()
```Gọi:```python id="jpqkoq"
User.make()
```vượt qua`User`như đối số đầu tiên.
Việc gọi thông qua một thể hiện cũng vượt qua lớp:```python id="x77e2w"
User().make()
```## 33,13 Máy đánh bạc`__slots__`thay đổi lưu trữ thuộc tính cá thể.```python id="q0q88q"
class Point:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
```Trường hợp của`Point`thường không có bình thường`__dict__`trừ khi được yêu cầu rõ ràng.```python id="jmosbt"
p = Point(1, 2)
print(p.x)
```Các thuộc tính vị trí được triển khai bằng các bộ mô tả được lưu trữ trên lớp.
Về mặt khái niệm:```text id="cq58r6"
Point.__dict__["x"] = slot descriptor
Point.__dict__["y"] = slot descriptor
```Truy cập`p.x`gọi bộ mô tả vị trí, đọc từ một vị trí cố định trong bố cục đối tượng.
Các khe có thể giảm mức sử dụng bộ nhớ và tăng tốc một số kiểu truy cập thuộc tính, nhưng chúng cũng hạn chế các thuộc tính phiên bản động.
## 33.14 Thiếu thuộc tính
Nếu tra cứu thông thường không thành công, Python có thể gọi`__getattr__`.
```python id="r0ezoa"
class Dynamic:
def __getattr__(self, name):
if name == "answer":
return 42
raise AttributeError(name)
d = Dynamic()
print(d.answer)
__getattr__chỉ được gọi sau khi đường dẫn tra cứu thông thường không thành công.
Điều này hữu ích cho:text id="16oz5d" lazy loading proxy objects RPC clients compatibility layers dynamic APIs mock objects Nếu như__getattr__cũng thất bại, nó sẽ tăngAttributeError.
33,15__getattribute__
__getattribute__chặn tất cả quyền truy cập thuộc tính thông thường.```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()
```Mọi truy cập đều đi qua__getattribute__, bao gồm cả tra cứu phương thức.
Việc triển khai tùy chỉnh phải tránh đệ quy vô hạn:python id="6fn49h" class Bad: def __getattribute__(self, name): return self.__dict__[name] Truy cậpself.__dict__cuộc gọi__getattribute__lại.
Sử dụngobject.__getattribute__hoặcsuper().__getattribute__:
class Good:
def __getattribute__(self, name):
return object.__getattribute__(self, name)
```## 33.16 Gán thuộc tính
Bài tập:```python id="in3koz"
obj.x = value
```sử dụng logic thiết lập thuộc tính.
Thứ tự đại khái là:```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
```một phong tục`__setattr__`có thể chặn bài tập:```python id="x89t2s"
class C:
def __setattr__(self, name, value):
print("set", name, value)
super().__setattr__(name, value)
obj = C()
obj.x = 10
```Như với`__getattribute__`, mã bất cẩn có thể tái diễn.
## 33.17 Xóa thuộc tính
Xóa:```python id="annac7"
del obj.x
```sử dụng logic xóa.
Nó có thể gọi:```text id="wvs69o"
type(obj).__delattr__
descriptor.__delete__
remove from instance dictionary
clear slot value
```Mô tả ví dụ:```python id="05vatk"
class D:
def __delete__(self, obj):
print("delete")
class C:
x = D()
obj = C()
del obj.x
```Việc xóa là một phần của cùng một họ giao thức mô tả và thuộc tính.
## 33.18 Tra cứu thuộc tính lớp
Các lớp cũng là đối tượng.```python id="n3g515"
class C:
x = 10
print(C.x)
```Nhìn lên`C.x`là quyền truy cập thuộc tính trên một đối tượng lớp.
Loại`C`thường là`type`, do đó việc tra cứu thuộc tính lớp được kiểm soát bởi hành vi siêu dữ liệu.
Về mặt khái niệm:```text id="qro2hh"
object = C
type(object) = type
lookup attribute "x"
```Đây là lý do tại sao siêu dữ liệu có thể tùy chỉnh quyền truy cập thuộc tính cấp lớp.
## 33.19 Tra cứu thuộc tính siêu lớp
Siêu dữ liệu có thể xác định các thuộc tính hiển thị trên các lớp.```python id="2pzc6p"
class Meta(type):
def meta_method(cls):
return "meta"
class C(metaclass=Meta):
pass
print(C.meta_method())
```Phương thức này được tìm thấy trên siêu dữ liệu và được liên kết với đối tượng lớp.
Tra cứu thuộc tính lớp bao gồm:```text id="3rcosr"
attributes on the class itself
descriptors in the metaclass
metaclass MRO
```Tra cứu siêu dữ liệu tách biệt với tra cứu cá thể, nhưng nó sử dụng cùng một mô hình đối tượng chung.
## 33.20 Tra cứu thuộc tính mô-đun
Các mô-đun có từ điển thuộc tính.```python id="16mk8q"
import math
print(math.pi)
```Đại khái là thế này:```text id="iw6moh"
math.__dict__["pi"]
```Các mô-đun cũng có thể định nghĩa`__getattr__`đối với các thuộc tính bị thiếu.```python id="a4cpwz"
# module.py
def __getattr__(name):
if name == "lazy":
return load_lazy()
raise AttributeError(name)
```Sau đó:```python id="pme0mb"
module.lazy
```có thể được tính toán một cách linh hoạt.
Tính năng này thường được sử dụng để nhập từng phần và các miếng chêm tương thích.
## 33.21 Tra cứu và kế thừa thuộc tính
Đối với các trường hợp, việc tra cứu lớp tuân theo thứ tự phân giải phương thức.```python id="nri7kz"
class A:
x = "A"
class B(A):
pass
obj = B()
print(obj.x)
```Tra cứu tìm kiếm:```text id="3gw1ky"
B
A
object
```Lệnh được lưu trữ trong:```python id="il7bpl"
print(B.__mro__)
```Đa kế thừa sử dụng tuyến tính hóa C3 để tính toán MRO nhất quán.```python id="iob9c0"
class A: pass
class B: pass
class C(A, B): pass
print(C.__mro__)
```Tra cứu thuộc tính dựa vào thứ tự này.
## 33.22 Tra cứu thuộc tính và`super`
`super()`thay đổi nơi bắt đầu tra cứu.```python id="plcgfy"
class Base:
def f(self):
return "base"
class Child(Base):
def f(self):
return super().f()
```Bên trong`Child.f`, `super().f`tìm kiếm sau`Child`trong MRO, sau đó liên kết phương thức tìm thấy với phiên bản gốc.
Về mặt khái niệm:```text id="zj3hcg"
MRO: Child, Base, object
super from Child
search Base, then object
bind result to self
super()không phải là truy cập lớp cha đơn giản. Đó là tra cứu mô tả tương đối MRO.
33.23 Tra cứu phương pháp đặc biệt
Các phương thức đặc biệt thường được tra cứu theo kiểu chứ không phải tra cứu thuộc tính thể hiện thông thường.
Ví dụ:python id="2r7t8j" len(obj) sử dụng khe độ dài của loại. Nó không chỉ đơn giản thực hiện:python id="5oiizr" obj.__len__() Sự khác biệt này quan trọng:```python id="76o4mf"
class C:
def len(self):
return 10
obj = C() obj.len = lambda: 20
print(obj.len())
print(len(obj))
```Cuộc gọi rõ ràng có thể tìm thấy thuộc tính instance. cáclen()hoạt động sử dụng phương pháp tra cứu đặc biệt thông qua kiểu.
Tra cứu phương pháp đặc biệt được thiết kế cho tốc độ và tính nhất quán của giao thức đối tượng.
33.24 Tra cứu thuộc tính trong Bytecode
Quyền truy cập thuộc tính biên dịch theo hướng dẫn mã byte, chẳng hạn như:text id="wifw5q" LOAD_ATTR STORE_ATTR DELETE_ATTR LOAD_METHOD Vì:python id="it1gq1" value = obj.x mã byte khái niệm:text id="gp7253" LOAD_FAST obj LOAD_ATTR x STORE_FAST value Vì:python id="6xdbsd" obj.x = value mã byte khái niệm:text id="id6s81" LOAD_FAST value LOAD_FAST obj STORE_ATTR x Đối với các cuộc gọi phương thức:```python id="qou4id"
obj.f(arg)
## 33.25 Tra cứu thuộc tính có thể thực thi mã
Tra cứu thuộc tính không phải lúc nào cũng thụ động.
Biểu thức này:```python id="i49h4b"
obj.x
```có thể gọi mã người dùng thông qua:```text id="tqt8ka"
__getattribute__
descriptor __get__
property getter
__getattr__
metaclass hooks
module __getattr__
```Do đó, quyền truy cập thuộc tính có thể:```text id="gq9mxu"
raise exceptions
mutate state
perform I/O
allocate objects
return different values each time
call arbitrary Python code
```Ví dụ:```python id="lqpgto"
class C:
@property
def x(self):
print("computed")
return 10
obj = C()
obj.x
obj.x
```Getter chạy mỗi lần.
## 33.26 Tra cứu thuộc tính và ngoại lệ
Nếu thiếu một thuộc tính, Python sẽ tăng`AttributeError`.
```python id="u5hs78"
obj.missing
```Nhưng việc tra cứu thuộc tính cũng có thể đưa ra các ngoại lệ khác.
Ví dụ:```python id="yxnfz8"
class C:
@property
def x(self):
raise RuntimeError("failed")
obj = C()
obj.x
```Điều này làm tăng`RuntimeError`, không`AttributeError`.
Chỉ những thuộc tính còn thiếu mới nên nâng cao`AttributeError`. Các công cụ như`hasattr`phụ thuộc vào quy ước này.```python id="glsscx"
hasattr(obj, "x")
```hoạt động bằng cách cố gắng tra cứu và bắt`AttributeError`.
## 33.27 Tra cứu thuộc tính và`hasattr`
`hasattr(obj, name)`tra cứu thuộc tính cuộc gọi.```python id="sf1vip"
hasattr(obj, "x")
```đại khái là:```python id="d7n5ls"
try:
getattr(obj, "x")
except AttributeError:
return False
else:
return True
```Điều này có nghĩa`hasattr`có thể thực thi mã người dùng.
Nếu một người nhận tài sản tăng`RuntimeError`, `hasattr`không coi đó là thuộc tính còn thiếu.```python id="6ci9p0"
class C:
@property
def x(self):
raise RuntimeError("boom")
hasattr(C(), "x")
```các`RuntimeError`truyền bá.
## 33.28 Tra cứu thuộc tính và`getattr`
`getattr`thực hiện tra cứu thuộc tính động.```python id="uk5dyw"
getattr(obj, "name")
```tương đương với:```python id="qatskj"
obj.name
```khi tên thuộc tính được biết đến một cách tĩnh.
Nó cũng hỗ trợ mặc định:```python id="l96rs7"
getattr(obj, "missing", default)
```Điều này trả về`default`chỉ khi tra cứu tăng lên`AttributeError`.
Nó không loại bỏ các ngoại lệ tùy ý từ các bộ mô tả hoặc móc tra cứu tùy chỉnh.
## 33.29 Tra cứu thuộc tính và`setattr`
`setattr`thực hiện gán thuộc tính động.```python id="j47b3p"
setattr(obj, "x", 10)
```tương đương với:```python id="1q0s4t"
obj.x = 10
```cho một tên tĩnh.
Nó vẫn tôn trọng:```text id="amsxsl"
__setattr__
data descriptors
slots
read-only attributes
```Vì thế`setattr`không phải là một từ điển thô viết.
## 33.30 Tra cứu thuộc tính và`delattr`
`delattr`thực hiện xóa thuộc tính động.```python id="xe7qva"
delattr(obj, "x")
```tương đương với:```python id="mbgk3j"
del obj.x
```Nó vẫn tôn trọng:```text id="gnvxp9"
__delattr__
descriptor __delete__
slot deletion
instance dictionary deletion
```## 33.31 Tra cứu thuộc tính và bộ đệm nội tuyến
Việc tra cứu thuộc tính diễn ra thường xuyên nên CPython tối ưu hóa nó.
Truy cập lặp lại:```python id="xtr2dq"
for obj in objects:
total += obj.value
```có thể nhấn cùng một bố cục thuộc tính nhiều lần.
CPython có thể đính kèm dữ liệu bộ nhớ đệm nội tuyến vào lệnh mã byte. Bộ đệm có thể lưu trữ các sự kiện như:```text id="ooumb4"
expected receiver type
type version tag
dictionary version
descriptor result
slot offset
instance dictionary offset
```Trong các lần thực thi sau này:```text id="ylpgjg"
if guards still hold:
use fast path
else:
fall back to generic lookup
```Điều này bảo tồn ngữ nghĩa động trong khi tăng tốc các trường hợp ổn định.
## 33.32 Vô hiệu hóa bộ đệm
Python cho phép đột biến lớp.```python id="uqb4tq"
class C:
x = 1
obj = C()
print(obj.x)
C.x = 2
print(obj.x)
```Tra cứu thứ hai phải xem`2`.
Do đó, bộ đệm thuộc tính không thể sử dụng lại các kết quả cũ một cách mù quáng. Nó phải đề phòng những thay đổi.
Những người bảo vệ điển hình có thể bao gồm:```text id="1kbbj7"
type identity
type version
dictionary version
descriptor kind
instance layout
```Khi giả định không thành công, CPython sẽ quay lại tra cứu chung và có thể cập nhật bộ đệm.
## 33.33 Độ ổn định của bố cục sơ thẩm
Bộ đệm thuộc tính hoạt động tốt nhất khi bố cục đối tượng ổn định.
Ví dụ:```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
```Mỗi`p`có cùng loại và có thể bố trí thuộc tính tương tự. Đây là một trường hợp tốt cho chuyên môn hóa.
Những thay đổi động có thể làm giảm hiệu quả của bộ đệm:```python id="nb80hw"
p.z = 10
del p.x
Point.x = property(...)
```Người phiên dịch phải bảo toàn tính chính xác trước tiên.
## 33.34 Tra cứu thuộc tính và từ điển
Từ điển phiên bản được tối ưu hóa cao.
Từ điển CPython là trung tâm của không gian tên đối tượng:```text id="hr03df"
module namespace
class namespace
instance namespace
globals
builtins
```Đối với nhiều đối tượng, việc tra cứu thuộc tính cuối cùng trở thành việc tra cứu từ điển cộng với việc xử lý bộ mô tả.
Bộ nhớ đệm thuộc tính thường phụ thuộc vào phiên bản từ điển để trình thông dịch có thể biết liệu vùng tên có thay đổi hay không.
## 33.35 Tra cứu thuộc tính và mô-đun
Mô-đun toàn cầu cũng dựa trên từ điển.```python id="z6frja"
import math
math.sqrt
```Đối tượng mô-đun lưu trữ các thuộc tính trong từ điển của nó.
Việc tra cứu mô-đun đơn giản hơn việc tra cứu bộ mô tả cá thể trong nhiều trường hợp, nhưng các mô-đun có thể tùy chỉnh hành vi thuộc tính bị thiếu thông qua`__getattr__`.
Việc nhập cũng liên kết các thuộc tính mô-đun:```python id="3y1uib"
import package.submodule
```Sau khi nhập, gói có thể có`submodule`thuộc tính.
## 33.36 Tra cứu thuộc tính và nội dung
Các loại tích hợp thường sử dụng các mô tả và bố cục nội bộ chuyên biệt.
Ví dụ:```python id="pswgfz"
list.append
dict.get
str.upper
int.bit_length
```Các thuộc tính này thường là các bộ mô tả phương thức được triển khai trong C.
Truy cập thông qua một thể hiện:```python id="c2qhto"
[].append
```trả về một đối tượng phương thức tích hợp sẵn.
Các cuộc gọi ngay lập tức có thể sử dụng đường dẫn cuộc gọi phương thức được tối ưu hóa.
## 33.37 Tra cứu thuộc tính và quản lý bộ nhớ
Tra cứu thuộc tính thao tác tham chiếu.
Khi một thuộc tính được trả về, CPython phải đảm bảo kết quả vẫn tồn tại.
Nếu việc tra cứu tạo ra một phương thức liên kết, kết quả thuộc tính hoặc kết quả mô tả thì quyền sở hữu tham chiếu phải được xử lý chính xác.
Các đối tượng tạm thời có thể được tạo trong quá trình tra cứu:```text id="yd6imc"
bound methods
property return values
descriptor return values
exception objects
strings or proxy objects in custom hooks
```Đường dẫn thất bại phải làm sạch chúng.
Vì tra cứu thuộc tính có thể gọi mã Python nên độ an toàn tham chiếu là rất quan trọng.
## 33.38 Tra cứu thuộc tính và truy cập lại
Tra cứu thuộc tính có thể nhập lại Python.
Ví dụ:```python id="cjrgl2"
class C:
def __getattribute__(self, name):
return compute_value(name)
```Cuộc gọi đến`compute_value`có thể thực thi mã Python tùy ý.
Điều này có nghĩa là trong một lần tra cứu thuộc tính, mã Python có thể:```text id="pbs3p5"
mutate the object
mutate the class
change descriptors
trigger garbage collection
raise exceptions
call back into the same object
```Việc thực hiện tra cứu phải chấp nhận điều này.
## 33.39 Tra cứu thuộc tính và proxy
Các đối tượng proxy thường triển khai tra cứu thuộc tính tùy chỉnh.```python id="gmm0jz"
class Proxy:
def __init__(self, target):
self._target = target
def __getattr__(self, name):
return getattr(self._target, name)
```Sau đó:```python id="t9wo3d"
proxy.x
```đại biểu tới:```python id="mwraoc"
target.x
```Một proxy mạnh mẽ phải xử lý các phương thức đặc biệt một cách cẩn thận vì việc tra cứu phương thức đặc biệt tiềm ẩn thường bỏ qua việc tra cứu thuộc tính phiên bản thông thường.
Ví dụ, việc triển khai`__getattr__`một mình có thể không làm được`len(proxy)`ủy quyền cho`len(target)`.
## 33.40 Tra cứu thuộc tính và ORM
Nhiều ORM sử dụng mô tả.
Hình dạng ví dụ:```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()
```Sau đó:```python id="gygh09"
user.name
```không đọc một thuộc tính đơn giản. Nó gọi`Field.__get__`.
Điều này cho phép các thư viện triển khai:```text id="m7h3nv"
validation
lazy loading
database column mapping
change tracking
computed fields
relationship loading
```Bộ mô tả biến cú pháp thuộc tính thành quyền truy cập có thể lập trình.
## 33.41 Tra cứu thuộc tính và các lớp dữ liệu
Các lớp dữ liệu về cơ bản không thay đổi việc tra cứu thuộc tính.```python id="itowya"
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
```Các phiên bản thường lưu trữ các trường trong từ điển phiên bản trừ khi các vị trí được bật.```python id="2es6x4"
p = Point(1, 2)
print(p.x)
```Đây là tra cứu thuộc tính cá thể thông thường.
Với các khe:```python id="zkk0iv"
@dataclass(slots=True)
class Point:
x: int
y: int
```lưu trữ trường sử dụng các vị trí thay vì từ điển phiên bản thông thường.
## 33.42 Tra cứu thuộc tính và loại đối tượng
Các loại là các đối tượng và chúng cũng có các thuộc tính.```python id="veiddt"
int.bit_length
str.upper
dict.items
```Đây là các thuộc tính trên các đối tượng loại.
Khi tra cứu các thuộc tính trên các phiên bản, kiểu tra cứu của phiên bản sẽ điều khiển.
Khi tra cứu các thuộc tính trên các lớp, siêu dữ liệu sẽ điều khiển việc tra cứu.
Mô hình đối tượng đệ quy này là trung tâm của Python:```text id="7jbz3r"
object has type
type is also an object
type has metaclass
metaclass controls class attribute lookup
```## 33.43 Tra cứu thuộc tính và hiệu suất
Quyền truy cập thuộc tính có nhiều chi phí hơn quyền truy cập biến cục bộ.
So sánh:```python id="2vbgf5"
x
```bên trong một hàm, ở đâu`x`là địa phương:```text id="quibql"
LOAD_FAST
```với:```python id="h0qo06"
obj.x
```đòi hỏi:```text id="p6kgjq"
LOAD_FAST obj
LOAD_ATTR x
LOAD_ATTRcó thể liên quan đến logic mô tả, tra cứu từ điển, kiểm tra bộ đệm, kiểm tra phiên bản loại và xử lý lỗi.
Đây là lý do tại sao các vòng lặp nóng đôi khi được hưởng lợi từ liên kết cục bộ:```python id="3tqoir" append = xs.append for item in items: append(item)
## 33.44 Tra cứu thuộc tính và lỗi trong đường dẫn nóng
Lỗi tra cứu thuộc tính tương đối tốn kém vì nó tạo ra một ngoại lệ.
Ví dụ:```python id="uacsdx"
try:
value = obj.missing
except AttributeError:
value = default
```Đối với những lần nhớ thường xuyên, sử dụng từ điển hoặc công cụ canh gác rõ ràng có thể nhanh hơn.
Nhưng thiết kế tốt nhất phụ thuộc vào ngữ nghĩa. Lỗi tra cứu thuộc tính là chính xác và thành ngữ khi sự vắng mặt là đặc biệt hoặc khi tương tác với các API động.
## 33.45 Kiểm tra tra cứu thuộc tính
sử dụng`vars`để kiểm tra các từ điển mẫu:```python id="pqoskd"
class C:
pass
obj = C()
obj.x = 1
print(vars(obj))
```Sử dụng từ điển lớp:```python id="zw539h"
print(C.__dict__)
```Sử dụng MRO:```python id="oqmr19"
print(C.__mro__)
```Sử dụng`inspect.getattr_static`để kiểm tra các thuộc tính mà không kích hoạt tra cứu động thông thường trong nhiều trường hợp:```python id="kb22m2"
import inspect
inspect.getattr_static(obj, "x")
```Điều này rất hữu ích khi mô tả hoặc`__getattr__`sẽ thực thi mã.
## 33.46 Mô hình tra cứu thuộc tính tối thiểu
Một mô hình tra cứu đơn giản:```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)
```Điều này bỏ qua các chi tiết thực tế quan trọng:```text id="vn6j5o"
custom __getattribute__
metaclasses
slots internals
C-level fast paths
reference counts
inline caches
error handling
module lookup
special method lookup
```Nhưng nó nắm bắt được thứ tự ưu tiên của bộ mô tả thông thường.
## 33.47 Những hiểu lầm phổ biến
| Hiểu lầm | Đúng mẫu |
|---|---|
|`obj.x`luôn đọc`obj.__dict__["x"]`| Bộ mô tả dữ liệu, vị trí, thuộc tính lớp và móc có thể can thiệp |
| Các phương thức được lưu trữ trên mỗi phiên bản | Các phương thức thông thường được lưu trữ trên lớp và bị ràng buộc trong quá trình tra cứu |
| Thuộc tính là các trường | Thuộc tính là các bộ mô tả gọi hàm |
|`__getattr__`xử lý mọi tra cứu | Nó chỉ chạy sau khi tra cứu thông thường không thành công |
|`__getattribute__`chỉ xử lý các thuộc tính bị thiếu | Nó xử lý tất cả các truy cập thuộc tính thông thường |
| Thuộc tính phiên bản luôn đánh bại thuộc tính lớp | Bộ mô tả dữ liệu đánh bại các thuộc tính cá thể |
| Các phương pháp đặc biệt luôn sử dụng tra cứu thông thường | Nhiều người được tra cứu thông qua các khe loại |
| Quyền truy cập thuộc tính không có tác dụng phụ | Nó có thể thực thi mã Python tùy ý |
## 33,48 Chiến lược đọc
Để nghiên cứu tra cứu thuộc tính, hãy xây dựng các ví dụ theo thứ tự sau:```python id="xp7o07"
class C:
x = 1
```Sau đó thêm:```python id="jdu7zp"
obj.x = 2
```Sau đó thêm một phương thức:```python id="yzo90g"
def f(self): ...
```Sau đó thay thế nó bằng:```python id="nku86v"
@property
def x(self): ...
```Sau đó thêm:```python id="6ho4rb"
__getattr__
__getattribute__
__slots__
staticmethod
classmethod
multiple inheritance
metaclass
```Đối với mỗi bước, hãy kiểm tra:```python id="eaahv5"
vars(obj)
C.__dict__
C.__mro__
type(obj)
```Và tháo rời các trang web truy cập:```python id="xhk4lr"
import dis
def read(obj):
return obj.x
dis.dis(read)
```Điều này xây dựng một bản đồ thực tế từ cú pháp đến hành vi của mô hình đối tượng.
## 33.49 Tóm tắt chương
Tra cứu thuộc tính là cơ chế đằng sau`obj.name`. Nó là một giao thức động liên quan đến loại đối tượng, từ điển lớp, từ điển cá thể, bộ mô tả, tính kế thừa, móc tùy chỉnh, vị trí, mô-đun, siêu dữ liệu và bộ đệm nội tuyến.
Ưu tiên tra cứu cốt lõi cho các thuộc tính phiên bản thông thường là:```text id="t5lrtf"
data descriptor
instance dictionary
non-data descriptor
class attribute
__getattr__
AttributeError
```Thứ tự này giải thích các phương thức, thuộc tính, tạo bóng, vị trí và nhiều mẫu khung.
CPython tối ưu hóa rất nhiều việc tra cứu thuộc tính bằng các đường dẫn mã byte chuyên dụng và bộ nhớ đệm nội tuyến, nhưng nó phải bảo toàn ngữ nghĩa động của Python. Các lớp có thể thay đổi, các thể hiện có thể thay đổi, bộ mô tả có thể chạy mã và các hook tùy chỉnh có thể ghi đè toàn bộ quá trình.