描述器:__get__、__set__、__delete__
实现描述器协议,控制属性访问。
描述器:__get__、__set__、__delete__ 是 CoddyKit 上的免费 Python Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Python Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Python Academy 课程共包含 4 节课。
什么是描述符?
描述符是定义了 __get__、__set__ 或 __delete__,并被赋值为类属性的对象。Python 会在属性访问时调用这些方法。
class Descriptor:
def __get__(self, obj, objtype=None):
print(f"__get__ called, obj={obj}")
return 42
class MyClass:
attr = Descriptor()
print(MyClass().attr) # __get__ called ... 42数据描述符与非数据描述符
数据描述符定义了 __set__ 或 __delete__,其优先级高于实例的 __dict__。非数据描述符(只有 __get__)则让位于 __dict__。
class DataDesc:
def __get__(self, obj, t): return "data"
def __set__(self, obj, val): pass
class NonDataDesc:
def __get__(self, obj, t): return "nondata"
class C:
d = DataDesc()
n = NonDataDesc()
c = C()
c.__dict__["n"] = "instance" # shadows the non-data desc
c.__dict__["d"] = "instance" # does NOT shadow data desc
print(c.n) # instance
print(c.d) # data__get__ 签名
__get__(self, obj, objtype):obj 是实例(在类上访问时为 None);objtype 是类。
class Verbose:
def __get__(self, obj, objtype=None):
if obj is None:
return self # accessed on class
return f"value for {obj!r}"
class Widget:
label = Verbose()
print(Widget.label) # <Verbose object>
print(Widget().label) # value for <Widget object>__set__ 与验证
实现 __set__(self, obj, value),即可在存储值之前对其进行验证或转换。
class PositiveInt:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, obj, t):
return obj.__dict__.get(self.name)
def __set__(self, obj, val):
if not isinstance(val, int) or val <= 0:
raise ValueError(f"{self.name} must be a positive int")
obj.__dict__[self.name] = val
class Rect:
width = PositiveInt()
height = PositiveInt()
r = Rect()
r.width = 10
# r.width = -1 # ValueError__set_name__
当描述符被赋值给类时,Python 3.6+ 会在描述符上调用 __set_name__(owner, name),使其能够访问自身的属性名称。
class Typed:
def __set_name__(self, owner, name):
self.public = name
self.private = "_" + name
def __get__(self, obj, t):
return None if obj is None else getattr(obj, self.private, None)
def __set__(self, obj, val):
setattr(obj, self.private, val)
class User:
name = Typed()
u = User()
u.name = "Alice"
print(u.name) # Alice用于移除属性的 __delete__
定义 __delete__(self, obj),即可拦截 del obj.attr。
class Protected:
def __set_name__(self, owner, name): self.name = name
def __get__(self, obj, t):
return obj.__dict__.get(self.name)
def __set__(self, obj, v): obj.__dict__[self.name] = v
def __delete__(self, obj):
raise AttributeError(f"Cannot delete {self.name}")
class Config:
host = Protected()
c = Config(); c.host = "localhost"
# del c.host # AttributeError函数是非数据描述符
函数实现 __get__,在实例上访问时返回绑定方法。这就是 Python 实现方法绑定的方式。
def greet(self):
return f"Hello from {self}"
class C: pass
C.greet = greet
c = C()
print(c.greet()) # Hello from <C object>classmethod 和 staticmethod 是描述符
classmethod 和 staticmethod 是内置描述符类,它们包装函数并改变 __get__ 的返回值。
class MyClass:
@classmethod
def from_string(cls, s):
return cls()
# classmethod.__get__ returns a bound method with cls
# staticmethod.__get__ returns the plain function惰性属性描述符
构建一个描述符,使其只计算一次值,并按实例对值进行缓存。
class LazyAttr:
def __init__(self, func): self.func = func
def __set_name__(self, owner, name): self.name = name
def __get__(self, obj, t):
if obj is None: return self
val = self.func(obj)
obj.__dict__[self.name] = val # shadows descriptor
return val
class Report:
@LazyAttr
def summary(self):
print("computing...")
return "done"
r = Report()
print(r.summary) # computing... done
print(r.summary) # done (cached)property 是描述符
property 本身就是用 C 实现的数据描述符。理解描述符有助于您理解 @property、@classmethod 和 @staticmethod 的工作原理。
class Circle:
def __init__(self, r): self._r = r
@property
def radius(self): return self._r
@radius.setter
def radius(self, v):
if v < 0: raise ValueError
self._r = v
# property is: property.__get__ = fget, property.__set__ = fset描述符查找顺序
Python 属性查找顺序:1)类型(MRO)中的数据描述符;2)实例 __dict__;3)非数据描述符和类变量。
# Pseudocode for obj.attr:
# 1. Check type(obj).__mro__ for a data descriptor
# 2. Check obj.__dict__
# 3. Check type(obj).__mro__ for non-data descriptor or class var
# 4. Raise AttributeError快速检查
哪种类型的描述符优先于实例 __dict__?
回顾
描述符通过 __get__、__set__ 和 __delete__ 拦截属性访问。数据描述符会遮蔽实例的 __dict__;非数据描述符不会。使用 __set_name__ 可获取属性名称。property、classmethod 和 staticmethod 都是描述符。
常见问题解答
「描述器:__get__、__set__、__delete__」课时是免费的吗?
是的 — 「描述器:__get__、__set__、__delete__」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Python Academy 课程的其余内容,请升级到 CoddyKit PRO。 Python Academy 课程共包含 4 节课。
「描述器:__get__、__set__、__delete__」这节课中我会学到什么?
实现描述器协议,控制属性访问。 你通过在浏览器中直接运行的动手代码来练习 Python Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Python Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Python Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「描述器:__get__、__set__、__delete__」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Python Academy 课中编写并运行代码吗?
能。每节 Python Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- Python 类的创建方式
- 编写自定义元类
- 描述器:__get__、__set__、__delete__
- __slots__ 与内存优化