43. Mô tả
43. 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 trên một đối tượng khác. Bộ mô tả là một trong những cơ chế chính đằng sau mô hình đối tượng của Python. Chúng giải thích cách các phương thức liên kết với các thể hiện, cáchpropertyhoạt động như thế nàostaticmethodVàclassmethodhoạt động, cách các khe hoạt động và số lượng thao tác loại cấp CPython kết nối với cú pháp cấp Python.
Ở cấp độ ngôn ngữ, bộ mô tả là bất kỳ đối tượng nào xác định một hoặc nhiều phương thức sau:python id="x7hcrg" __get__(self, obj, objtype=None) __set__(self, obj, value) __delete__(self, obj) Một vật thể có__get__là một mô tả.
Một vật thể có__set__hoặc__delete__là một bộ mô tả dữ liệu.
Sự khác biệt giữa bộ mô tả không phải dữ liệu và bộ mô tả dữ liệu kiểm soát mức độ ưu tiên tra cứu.
43.1 Tại sao bộ mô tả tồn tại
Truy cập thuộc tính Python trông đơn giản:python id="96l8i5" obj.name Nhưng biểu thức này không có nghĩa là “đọc một trường có tênnametừ trí nhớ.”
Nó có nghĩa là:```text id="drqc0j" ask the object's type how attribute lookup works search descriptors and dictionaries in a defined order possibly call descriptor methods return the resulting object
Ví dụ:```python id="tkau0z"
obj.method
obj.property_name
Class.class_method
Class.static_method
obj.slot_name
```Tất cả những điều này liên quan đến hành vi mô tả.
## 43.2 Giao thức mô tả cơ bản
Một bộ mô tả xác định`__get__`.
```python id="ct8qxy"
class Descriptor:
def __get__(self, obj, objtype=None):
return "computed value"
```Sử dụng nó như một thuộc tính lớp:```python id="d0fypg"
class Example:
value = Descriptor()
e = Example()
print(e.value)
```Đầu ra:```text id="qz34l2"
computed value
```Đối tượng mô tả được lưu trữ trên lớp:```python id="csuohy"
print(Example.__dict__["value"])
```Nhưng truy cập thuộc tính thông qua các cuộc gọi cá thể`Descriptor.__get__`.
## 43.3 Đối số mô tả
Phương thức mô tả nhận được:
| Lập luận | Ý nghĩa |
|---|---|
|`self`| Đối tượng mô tả |
|`obj`| Phiên bản đang được truy cập, hoặc`None`để truy cập lớp học |
|`objtype`| Lớp chủ sở hữu |
Ví dụ:```python id="v4lvjg"
class Descriptor:
def __get__(self, obj, objtype=None):
print("obj:", obj)
print("objtype:", objtype)
return 42
class Example:
value = Descriptor()
e = Example()
print(e.value)
print(Example.value)
```Để truy cập ví dụ:```text id="by7jq8"
obj: <__main__.Example object at ...>
objtype: <class '__main__.Example'>
```Để truy cập lớp:```text id="k4ktgr"
obj: None
objtype: <class '__main__.Example'>
```Một bộ mô tả có thể sử dụng sự khác biệt này để trả về các giá trị khác nhau cho việc truy cập phiên bản và lớp.
## 43.4 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__`.
```python id="hb1iji"
class NonDataDescriptor:
def __get__(self, obj, objtype=None):
return "descriptor value"
```Ví dụ:```python id="bvccxu"
class Example:
value = NonDataDescriptor()
e = Example()
print(e.value)
```Đầu ra:```text id="j61t3h"
descriptor value
```Nhưng vì nó là một bộ mô tả phi dữ liệu nên một mục từ điển mẫu có thể ghi đè lên nó:```python id="vu3t3a"
e.__dict__["value"] = "instance value"
print(e.value)
```Đầu ra:```text id="6xj4jn"
instance value
```Quyền ưu tiên này là có chủ ý. Đó là cách các phương thức thông thường có thể bị che khuất bởi các thuộc tính cá thể.
## 43.5 Bộ mô tả dữ liệu
Bộ mô tả dữ liệu xác định`__set__`hoặc`__delete__`.
```python id="rk5sgr"
class DataDescriptor:
def __get__(self, obj, objtype=None):
return "descriptor value"
def __set__(self, obj, value):
print("setting", value)
```Ví dụ:```python id="c9rzsd"
class Example:
value = DataDescriptor()
e = Example()
e.__dict__["value"] = "instance value"
print(e.value)
```Đầu ra:```text id="m34bnr"
descriptor value
```Bộ mô tả dữ liệu chiến thắng từ điển cá thể.
Quy tắc này rất quan trọng đối với`property`.
## 43.6 Độ ưu tiên tra cứu
Để truy cập thuộc tính cá thể thông thường:```python id="ynllax"
obj.name
```Việc tra cứu đối tượng của CPython đại khái tuân theo thứ tự sau:```text id="rpb90z"
1. Look for name on the type or base types.
2. If found and it is a data descriptor, call its __get__.
3. Look in the instance dictionary.
4. If found on the type and it is a non-data descriptor, call its __get__.
5. If found on the type as a normal attribute, return it.
6. If not found, call __getattr__ if defined.
7. Otherwise raise AttributeError.
```Thứ tự này giải thích tại sao một số thuộc tính lớp có thể bị che khuất bởi các thuộc tính thể hiện còn những thuộc tính khác thì không.
## 43.7 Ví dụ về mức độ ưu tiên của bộ mô tả```python id="n4y91b"
class NonData:
def __get__(self, obj, objtype=None):
return "non-data descriptor"
class Data:
def __get__(self, obj, objtype=None):
return "data descriptor"
def __set__(self, obj, value):
raise AttributeError("read-only")
class Example:
a = NonData()
b = Data()
e = Example()
e.__dict__["a"] = "instance a"
e.__dict__["b"] = "instance b"
print(e.a)
print(e.b)
```Đầu ra:```text id="0dzdun"
instance a
data descriptor
abị che khuất vì nó là một bộ mô tả phi dữ liệu.bkhông bị che khuất vì nó là bộ mô tả dữ liệu.
43.8 Hàm là 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="4040yq" class Example: def method(self): return 42
e = Example()
print(Example.dict["method"]) print(e.method)
Đối tượng được trả về bởi quyền truy cập cá thể là một phương thức bị ràng buộc.```text id="j0sg2u"
function object stored on class
↓ __get__(instance, class)
bound method object
```Đây là lý do tại sao:```python id="r1yoxy"
e.method()
```vượt qua`e`như đối số đầu tiên.
## 43.9 Liên kết phương thức
Hành vi mô tả của hàm có thể được mô hình hóa như thế này:```python id="rrmwg4"
class FunctionLike:
def __get__(self, obj, objtype=None):
if obj is None:
return self
return BoundMethod(self, obj)
```Phương thức ràng buộc lưu trữ:```text id="9ac6b1"
function
instance
```Gọi phương thức ràng buộc:```python id="zs0r2l"
e.method(1, 2)
```gần tương đương với:```python id="0jmqjg"
Example.method(e, 1, 2)
```Đây không phải là cú pháp đặc biệt trong trình phân tích cú pháp. Đó là tra cứu thuộc tính thông thường cộng với ràng buộc mô tả cộng với lệnh gọi.
## 43.10 Lớp truy cập vào các phương thức
Khi được truy cập thông qua lớp, bộ mô tả hàm sẽ nhận được`obj=None`.
```python id="c1o0cb"
class Example:
def method(self):
return 42
print(Example.method)
```Kết quả là đối tượng giống hàm ban đầu, không phải là một phương thức bị ràng buộc với một thể hiện.
Vì vậy, điều này hoạt động:```python id="ce7spz"
e = Example()
print(Example.method(e))
```Ví dụ được thông qua một cách rõ ràng.
## 43.11 Đối tượng phương thức ràng buộc
Một đối tượng phương thức bị ràng buộc hiển thị các thuộc tính hữu ích:```python id="0hd310"
class Example:
def method(self):
return 42
e = Example()
m = e.method
print(m.__self__)
print(m.__func__)
__self__là trường hợp bị ràng buộc.__func__là chức năng cơ bản.
Về mặt khái niệm:text id="9rhbma" bound_method.__self__ -> e bound_method.__func__ -> Example.__dict__["method"] Gọi:python id="r7sscl" m() cuộc gọi:python id="sqt0vq" m.__func__(m.__self__) ## 43,12property
propertylà một bộ mô tả dữ liệu.```python id="o83sqw"
class User:
def init(self, name):
self._name = name
@property
def name(self):
return self._name
Truy cập:python id="q15uwm"
u = User("Ada")
print(u.name)
Một mô hình thô:```python id="e1wqf0"
class property:
def __get__(self, obj, objtype=None):
if obj is None:
return self
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
```Bởi vì`property`định nghĩa`__set__`, nó là một bộ mô tả dữ liệu. Nó chiến thắng từ điển cá thể.
## 43.13 Thuộc tính chỉ đọc
Thuộc tính chỉ đọc vẫn hoạt động như một bộ mô tả dữ liệu.```python id="bjmyqo"
class Example:
@property
def value(self):
return 42
e = Example()
e.__dict__["value"] = 100
print(e.value)
```Đầu ra:```text id="8qw90w"
42
```Mặc dù thuộc tính không có setter,`property`loại có`__set__`hành vi gây ra lỗi. Điều đó làm cho nó trở thành một bộ mô tả dữ liệu.
## 43.14 Thuộc tính có thể ghi
Thuộc tính có thể ghi thêm một setter.```python id="7362gp"
class User:
def __init__(self):
self._name = ""
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not value:
raise ValueError("empty name")
self._name = value
```Cách sử dụng:```python id="ly2z3r"
u = User()
u.name = "Ada"
print(u.name)
```Phân công:```python id="pzyvzj"
u.name = "Ada"
```gọi bộ mô tả`__set__`.
Nó không chỉ đơn giản là viết`u.__dict__["name"]`.
## 43,15`staticmethod`
`staticmethod`là một bộ mô tả ngăn chặn ràng buộc phương thức.```python id="7h9gwf"
class Math:
@staticmethod
def add(a, b):
return a + b
print(Math.add(2, 3))
print(Math().add(2, 3))
```Trong cả hai trường hợp, không có phiên bản nào được chèn làm đối số đầu tiên.
Một mô hình thô:```python id="k4n2sv"
class staticmethod:
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype=None):
return self.func
staticmethodlưu trữ một chức năng và trả về nó không thay đổi.
43,16classmethod
classmethodlà một bộ mô tả liên kết lớp thay vì thể hiện.```python id="m0y4q8"
class User:
@classmethod
def create(cls):
return cls()
u = User.create()
Một mô hình thô:python id="vn2wvc"
class classmethod:
def init(self, func):
self.func = func
def __get__(self, obj, objtype=None):
if objtype is None:
objtype = type(obj)
return BoundMethod(self.func, objtype)
Gọi:python id="cn0iaw"
User.create()
gần tương đương với:python id="mzdvxl"
User.dict["create"].get(None, User)()
cuộc gọi nào:python id="mgiurn"
original_function(User)
```## 43,17__slots__và mô tả__slots__sử dụng các bộ mô tả để quản lý các thuộc tính thể hiện bố cục cố định.
Ví dụ:```python id="tv0e9k" class Point: slots = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
Từ điển lớp chứa các bộ mô tả vị trí:python id="ziiwfx"
print(Point.dict["x"])
print(Point.dict["y"])
Với các khe cắm, các phiên bản bình thường có thể không có`__dict__`trừ khi được yêu cầu rõ ràng.```python id="mv07bu"
p = Point(1, 2)
print(hasattr(p, "__dict__"))
```Đầu ra:```text id="drcrll"
False
```## 43.18 Bộ mô tả thành viên và Bộ mô tả Getset
CPython hiển thị một số bộ mô tả cấp độ C dưới dạng đối tượng.
Các ví dụ phổ biến:```text id="vtr7cq"
member_descriptor
getset_descriptor
wrapper_descriptor
method_descriptor
```Bạn có thể thấy chúng trong từ điển lớp học:```python id="7ln1br"
class Point:
__slots__ = ("x",)
print(type(Point.__dict__["x"]))
```Đối với các loại tích hợp:```python id="inwepf"
print(type(int.__dict__["real"]))
print(type(str.__dict__["upper"]))
print(type(object.__dict__["__str__"]))
```Đây là các đối tượng mô tả cấp CPython bao bọc hành vi cấp C.
## 43.19 Bộ mô tả và gán thuộc tính
Đối với nhiệm vụ:```python id="4ja4xk"
obj.name = value
```CPython không phải lúc nào cũng ghi vào`obj.__dict__`.
Nếu loại có bộ mô tả dữ liệu cho`name`, phép gán gọi bộ mô tả`__set__`.
Ví dụ:```python id="2t9ndx"
class Descriptor:
def __set__(self, obj, value):
print("set", value)
class Example:
x = Descriptor()
e = Example()
e.x = 10
```Đầu ra:```text id="jfd4bi"
set 10
```Không cần viết từ điển phiên bản thông thường.
## 43.20 Bộ mô tả và xóa thuộc tính
Để xóa:```python id="mxpuzk"
del obj.name
```Nếu một bộ mô tả dữ liệu xác định`__delete__`, CPython gọi nó.```python id="wuc0pw"
class Descriptor:
def __delete__(self, obj):
print("delete")
class Example:
x = Descriptor()
e = Example()
del e.x
```Đầu ra:```text id="fkv43g"
delete
```Nếu không, việc xóa có thể xóa một mục khỏi từ điển mẫu hoặc nâng cao`AttributeError`.
## 43,21`__set_name__`Bộ mô tả có thể xác định`__set_name__`.
```python id="yvbjoc"
class Field:
def __set_name__(self, owner, name):
self.owner = owner
self.name = name
class User:
id = Field()
name = Field()
```Trong quá trình tạo lớp, Python gọi:```text id="zf52ho"
User.__dict__["id"].__set_name__(User, "id")
User.__dict__["name"].__set_name__(User, "name")
```Điều này cho phép bộ mô tả tìm hiểu tên thuộc tính mà chúng được gán.
Không có`__set_name__`, các bộ mô tả thường yêu cầu cấu hình dự phòng:```python id="k9fsdd"
id = Field("id")
name = Field("name")
```## 43.22 Bộ mô tả xác thực thực tế```python id="okxfod"
class PositiveInt:
def __set_name__(self, owner, name):
self.name = "_" + name
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.name)
def __set__(self, obj, value):
if not isinstance(value, int):
raise TypeError("expected int")
if value <= 0:
raise ValueError("expected positive integer")
setattr(obj, self.name, value)
```Cách sử dụng:```python id="u85219"
class User:
age = PositiveInt()
def __init__(self, age):
self.age = age
u = User(30)
print(u.age)
```Nhiệm vụ:```python id="7h7peb"
self.age = age
```cuộc gọi`PositiveInt.__set__`.
Quyền truy cập:```python id="jvsgys"
u.age
```cuộc gọi`PositiveInt.__get__`.
## 43.23 Lựa chọn lưu trữ mô tả
Bộ mô tả thường lưu trữ các giá trị theo từng phiên bản ở một nơi khác.
Các lựa chọn phổ biến:
| Lưu trữ | Ví dụ | Đánh đổi |
|---|---|---|
| Từ điển sơ thẩm |`obj.__dict__[name]`| Đơn giản, đòi hỏi`__dict__`|
| Thuộc tính riêng |`obj._name`| Đơn giản, có thể va chạm |
| Từ điển khóa yếu |`WeakKeyDictionary[obj]`| Hoạt động mà không cần chạm vào lệnh chính tả, chi phí cao hơn |
| Khe bù đắp | CPython nội bộ | Bố cục nhanh, cố định |
| Cửa hàng bên ngoài | Bảng ORM/phiên/trạng thái | Hữu ích cho các framework |
Một đối tượng mô tả thường được chia sẻ bởi lớp, do đó việc lưu trữ dữ liệu cụ thể của cá thể trực tiếp trên bộ mô tả thường là sai.
Xấu:```python id="httjyo"
class BadField:
def __set__(self, obj, value):
self.value = value
```Tất cả các phiên bản chia sẻ một đối tượng mô tả.
Tốt hơn:```python id="qgjw4l"
class Field:
def __set_name__(self, owner, name):
self.name = "_" + name
def __set__(self, obj, value):
setattr(obj, self.name, value)
```## 43.24 Chia sẻ mô tả
Một bộ mô tả được lưu trữ trên lớp.```python id="poo2z7"
class Field:
pass
class User:
name = Field()
```Có một cái`Field`đối tượng cho`User.name`.
Tất cả`User`các trường hợp truy cập vào cùng một đối tượng mô tả.```python id="ghzk97"
print(User.__dict__["name"])
```Đây là lý do tại sao trạng thái mô tả phải được thiết kế cẩn thận.
Trạng thái cấp độ mô tả phù hợp cho:```text id="lzdzy9"
field name
validation rule
default configuration
metadata
owner class
```Trạng thái cấp độ phiên bản thuộc về phiên bản hoặc trong kho lưu trữ theo phiên bản bên ngoài.
## 43.25 Bộ mô tả và kế thừa
Các bộ mô tả được kế thừa giống như các thuộc tính lớp khác.```python id="07zg83"
class Field:
def __get__(self, obj, objtype=None):
return "value"
class Base:
x = Field()
class Child(Base):
pass
print(Child().x)
```Đầu ra:```text id="23j8kz"
value
```Tra cứu tìm kiếm thứ tự phân giải phương pháp. Nếu một bộ mô tả được tìm thấy trong lớp cơ sở, nó sẽ tham gia truy cập thuộc tính cho các thể hiện con.
Một lớp con có thể ghi đè bộ mô tả bằng cách xác định cùng tên.```python id="jgv5bi"
class Child(Base):
x = 100
```Hiện nay:```python id="a343w1"
print(Child().x)
```trả về:```text id="sbrd9y"
100
```trừ khi áp dụng các quy tắc tra cứu khác.
## 43.26 Bộ mô tả và`super`
`super()`cũng sử dụng liên kết mô tả.
Ví dụ:```python id="klco94"
class Base:
def method(self):
return "base"
class Child(Base):
def method(self):
return super().method()
```Biểu thức:```python id="vz6j6v"
super().method
```tìm thấy`method`trên lớp tiếp theo trong MRO và liên kết nó với phiên bản gốc.
Ràng buộc đó vẫn sử dụng logic mô tả.
## 43.27 Bộ mô tả và`__getattribute__`Tất cả quyền truy cập thuộc tính bình thường đều phải trải qua`__getattribute__`.
```python id="0h0zfk"
obj.name
```gọi đại khái là:```python id="wrm9c8"
type(obj).__getattribute__(obj, "name")
```Việc thực hiện mặc định,`object.__getattribute__`, thực hiện tra cứu mô tả.
Nếu một lớp ghi đè`__getattribute__`, nó có thể thay đổi hoặc bỏ qua hành vi của bộ mô tả.
Ví dụ:```python id="6hlojz"
class Example:
@property
def x(self):
return 42
def __getattribute__(self, name):
return "intercepted"
e = Example()
print(e.x)
```Đầu ra:```text id="i8f18x"
intercepted
```Tài sản không bao giờ đạt được bởi vì`__getattribute__`chặn mọi thứ.
## 43.28 Bộ mô tả và`__getattr__`
`__getattr__`chỉ được gọi sau khi tra cứu thông thường không thành công.```python id="1tg4g8"
class Example:
def __getattr__(self, name):
return "missing"
e = Example()
print(e.anything)
```Đầu ra:```text id="cafj83"
missing
```Nếu một bộ mô tả tồn tại và trả về một giá trị,`__getattr__`không được gọi.
Thứ tự tra cứu:```text id="hpfu2m"
__getattribute__ runs first
descriptor rules happen inside normal __getattribute__
__getattr__ handles missing names only
```## 43.29 Bộ mô tả và siêu lớp
Quyền truy cập thuộc tính lớp cũng sử dụng logic mô tả, nhưng đối tượng đang được tìm kiếm là đối tượng lớp.```python id="6g2hvo"
class Meta(type):
@property
def label(cls):
return cls.__name__.lower()
class User(metaclass=Meta):
pass
print(User.label)
```Đây,`label`là một mô tả trên siêu dữ liệu. Truy cập`User.label`gọi tra cứu mô tả trên`type(User)`.
Đây là lý do tại sao siêu dữ liệu có thể định nghĩa các thuộc tính lớp được tính toán.
## 43.30 Tra cứu mô tả trên các lớp
Dành cho:```python id="bugwo3"
Class.attr
```tra cứu được xử lý bởi`type.__getattribute__`.
Việc tìm kiếm diễn ra trên đối tượng lớp và siêu dữ liệu của nó.
Nếu một thuộc tính trong siêu dữ liệu là một bộ mô tả, nó có thể liên kết với lớp làm đối tượng.
Đây là cách`classmethod`, thuộc tính siêu dữ liệu và nhiều thao tác kiểu tích hợp hoạt động.
Tra cứu lớp và tra cứu cá thể có liên quan nhưng không giống nhau. Tra cứu phiên bản tìm kiếm MRO của loại phiên bản. Tìm kiếm tra cứu lớp thông qua máy móc siêu dữ liệu.
## 43.31 Bộ mô tả trong ORM
Bộ mô tả là phổ biến trong ORM.
Hình dạng ví dụ:```python id="gs116j"
class Column:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj._row[self.name]
def __set__(self, obj, value):
obj._row[self.name] = value
```Cách sử dụng:```python id="3gj06d"
class User:
id = Column()
name = Column()
def __init__(self, row):
self._row = row
```Sau đó:```python id="e6nz6o"
u = User({"id": 1, "name": "Ada"})
print(u.name)
u.name = "Grace"
```Bộ mô tả ánh xạ quyền truy cập thuộc tính vào bộ lưu trữ hàng.
## 43.32 Bộ mô tả trong Thư viện xác thực
Các khung xác thực sử dụng các bộ mô tả để thực thi các ràng buộc.```python id="kcyh88"
class String:
def __set_name__(self, owner, name):
self.storage = "_" + name
def __set__(self, obj, value):
if not isinstance(value, str):
raise TypeError("expected str")
setattr(obj, self.storage, value)
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.storage)
```Cách sử dụng:```python id="4vl57c"
class User:
name = String()
def __init__(self, name):
self.name = name
```Định nghĩa lớp khai báo trường. Bộ mô tả thực thi quy tắc.
## 43.33 Bộ mô tả trong tính toán được lưu trong bộ nhớ đệm
Thuộc tính được lưu trong bộ nhớ đệm là một bộ mô tả phi dữ liệu.```python id="yh220g"
class cached_property:
def __init__(self, func):
self.func = func
self.name = func.__name__
def __get__(self, obj, objtype=None):
if obj is None:
return self
value = self.func(obj)
obj.__dict__[self.name] = value
return value
```Cách sử dụng:```python id="kxogrn"
class Data:
@cached_property
def total(self):
print("computing")
return 100
d = Data()
print(d.total)
print(d.total)
```Quyền truy cập đầu tiên gọi bộ mô tả và lưu trữ giá trị trong từ điển mẫu.
Quyền truy cập thứ hai trả về giá trị từ điển mẫu vì bộ mô tả không phải là dữ liệu.
Điều này phụ thuộc trực tiếp vào quyền ưu tiên của bộ mô tả.
## 43.34 Quy tắc thiết kế dữ liệu và phi dữ liệu
Sử dụng bộ mô tả dữ liệu khi bộ mô tả phải kiểm soát việc ghi hoặc không được ghi đè bởi các thuộc tính phiên bản.
Sử dụng bộ mô tả không phải dữ liệu khi muốn lưu vào bộ nhớ đệm hoặc theo dõi cấp phiên bản.
| Loại mô tả | Định nghĩa | Instance dict có thể ghi đè? | Ví dụ phổ biến |
|---|---|---:|---|
| Bộ mô tả phi dữ liệu |`__get__`chỉ | Có | chức năng, thuộc tính được lưu trữ |
| Bộ mô tả dữ liệu |`__set__`hoặc`__delete__`| Không | thuộc tính, vị trí, trường xác thực |
Sự khác biệt này giải thích nhiều hành vi truy cập thuộc tính.
## 43,35 Khe nội bộ CPython dành cho bộ mô tả
Ở cấp độ C, hành vi của bộ mô tả được thể hiện thông qua các khe kiểu.
Các hoạt động liên quan tương ứng với:```text id="3v2e6f"
tp_descr_get
tp_descr_set
```Một loại thực hiện`tp_descr_get`có thể hành động như thể nó có`__get__`.
Một loại thực hiện`tp_descr_set`có thể hành động như thể nó có`__set__`hoặc`__delete__`.
Các bộ mô tả tích hợp thường là các đối tượng C có loại cung cấp các vị trí này.
Đây là cách các phương thức tích hợp, bộ mô tả vị trí, bộ mô tả getset và bộ mô tả trình bao bọc tích hợp với tra cứu Python thông thường.
## 43.36 Tra cứu thuộc tính trong thuật ngữ CPython
Tra cứu cấp độ CPython được đơn giản hóa cho`obj.name`:
```text id="m04p6t"
1. type = Py_TYPE(obj)
2. descr = lookup name in type MRO
3. if descr has tp_descr_get and tp_descr_set:
return descr.__get__(obj, type)
4. if obj has dict and name in dict:
return dict[name]
5. if descr has tp_descr_get:
return descr.__get__(obj, type)
6. if descr exists:
return descr
7. call fallback or raise AttributeError
```Đây là trung tâm hoạt động của bộ mô tả.
## 43.37 Lỗi mô tả
Mô tả nên nâng cao`AttributeError`đối với các thuộc tính bị thiếu khi họ muốn hành vi dự phòng thuộc tính thông thường.
Ví dụ:```python id="xeje8w"
class Maybe:
def __get__(self, obj, objtype=None):
raise AttributeError("not available")
```Một mô tả làm tăng`AttributeError`có thể tương tác với`getattr`, `hasattr`và cơ chế dự phòng.
Đưa ra các ngoại lệ chính xác. Đừng giấu những lỗi không liên quan vì`AttributeError`trừ khi thuộc tính đó thực sự không có sẵn.
## 43.38 Mô tả Nội tâm
Để kiểm tra một bộ mô tả, hãy truy cập nó thông qua từ điển lớp.```python id="047r6b"
class Example:
@property
def value(self):
return 42
print(Example.__dict__["value"])
```Truy cập thông qua lớp có thể gọi`__get__`:
```python id="2rtf24"
print(Example.value)
```Vì`property`, quyền truy cập lớp trả về đối tượng thuộc tính. Nhưng các bộ mô tả khác có thể trả về các giá trị được tính toán.
Cách an toàn nhất để truy xuất bộ mô tả thô thường là:```python id="gvvuc9"
vars(Example)["value"]
```hoặc:```python id="h6m1qm"
Example.__dict__["value"]
```## 43.39 Các lỗi mô tả phổ biến
| Lỗi | Nguyên nhân | Sửa chữa |
|---|---|---|
| Tất cả các phiên bản đều có chung một giá trị | Trạng thái phiên bản được lưu trữ trên bộ mô tả | Lưu trữ trạng thái trên phiên bản hoặc bản đồ theo phiên bản bên ngoài |
| Thuộc tính bị lớp tùy chỉnh bỏ qua | ghi đè`__getattribute__`sai | Ủy quyền cho`object.__getattribute__`|
| Thuộc tính được lưu trong bộ nhớ đệm không được lưu vào bộ đệm | Bộ mô tả là bộ mô tả dữ liệu | Làm cho nó không phải là dữ liệu hoặc viết logic ưu tiên tùy chỉnh |
| Thuộc tính sơ thẩm không thể ghi đè trường | Bộ mô tả xác định`__set__`| Sử dụng bộ mô tả không phải dữ liệu nếu muốn ghi đè |
| Bộ mô tả thiếu tên trường | Không thực hiện`__set_name__`| Thêm vào`__set_name__`hoặc truyền tên một cách rõ ràng |
| Nghỉ truy cập lớp học |`__get__`không xử lý`obj is None`| Bộ mô tả trả về hoặc đối tượng cấp lớp |
## 43,40 Điểm chính
Bộ mô tả là các đối tượng xác định`__get__`, `__set__`, hoặc`__delete__`.
Một bộ mô tả phi dữ liệu xác định`__get__`chỉ một.
Bộ mô tả dữ liệu xác định`__set__`hoặc`__delete__`.
Bộ mô tả dữ liệu được ưu tiên hơn các từ điển mẫu.
Các bộ mô tả phi dữ liệu có thể bị che khuất bởi các từ điển mẫu.
Hàm là bộ mô tả, đó là lý do tại sao các phương thức liên kết với các thể hiện.`property`, `staticmethod`, `classmethod`, vị trí, phương thức tích hợp và nhiều phần bên trong CPython sử dụng bộ mô tả.
Bộ mô tả được triển khai trong CPython thông qua các khe mô tả trên các đối tượng kiểu.
Cần phải hiểu các bộ mô tả để hiểu các phương thức, thuộc tính, vị trí, siêu dữ liệu và tra cứu thuộc tính.