Python中的property是内置的描述符类,它的核心作用是把类的方法伪装成实例属性,让开发者可以用访问属性的方式来调用方法,同时还能在属性访问时插入自定义的逻辑,比如数据校验、延迟计算等。要理解property的工作机制,首先要先了解Python的描述符协议。

描述符协议基础
Python中,如果一个类实现了__get__、__set__、__delete__这三个方法中的任意一个,那么这个类的实例就可以作为描述符。描述符绑定到另一个类的属性上时,对这个属性的访问、赋值、删除操作会被描述符的对应方法拦截。
其中只实现__get__方法的描述符叫非数据描述符,同时实现__get__和__set__方法的叫数据描述符,数据描述符的优先级比实例的__dict__更高。
property类的实现逻辑
property本身就是一个实现了描述符协议的类,它的构造函数可以接收四个参数,分别是fget、fset、fdel、doc,对应属性的获取、设置、删除方法和文档说明。当我们用@property装饰器装饰一个方法时,本质上是创建了一个property实例,把被装饰的方法作为fget参数传入。
下面是一个简化版的property实现逻辑示例:
class SimpleProperty:
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, instance, owner):
# instance是属性所属的实例,owner是实例对应的类
if instance is None:
# 类访问时返回描述符本身
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
# 调用fget方法,传入实例作为第一个参数
return self.fget(instance)
def __set__(self, instance, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(instance, value)
def __delete__(self, instance):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(instance)
# setter装饰器,用于设置fset方法
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
# deleter装饰器,用于设置fdel方法
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
property的常见用法
基础属性包装
最常见的用法是把getter方法包装成只读属性,示例代码如下:
class Student:
def __init__(self, score):
self._score = score
@property
def score(self):
# 返回_score的值,同时可以加入额外逻辑
return self._score
stu = Student(90)
# 像访问属性一样调用score方法
print(stu.score) # 输出90
带 setter 的可写属性
如果需要支持属性赋值,可以用@属性名.setter装饰器定义setter方法,实现数据校验等逻辑:
class Student:
def __init__(self, score):
self._score = score
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise TypeError("score must be integer")
if value < 0 or value > 100:
raise ValueError("score must be between 0 and 100")
self._score = value
stu = Student(90)
stu.score = 95 # 正常赋值
print(stu.score) # 输出95
stu.score = 101 # 触发ValueError异常
删除属性逻辑
还可以用@属性名.deleter装饰器定义删除属性时的逻辑:
class Student:
def __init__(self, score):
self._score = score
@property
def score(self):
return self._score
@score.setter
def score(self, value):
self._score = value
@score.deleter
def score(self):
print("score attribute deleted")
del self._score
stu = Student(90)
del stu.score # 输出score attribute deleted
property的优先级规则
由于property是数据描述符,它的优先级高于实例的__dict__。如果在实例的__dict__中设置了和property同名的属性,访问时还是会触发property的__get__方法,不会直接返回实例__dict__中的值。
示例验证如下:
class Demo:
@property
def attr(self):
return "property value"
d = Demo()
# 给实例的__dict__添加同名属性
d.__dict__["attr"] = "instance dict value"
# 访问时还是走property的逻辑
print(d.attr) # 输出property value
property与类属性的关系
当通过类访问property属性时,会返回property实例本身,而不是调用__get__方法返回的结果,因为此时__get__方法的instance参数为None,会直接返回描述符实例。这个特性可以用来获取property的相关信息,比如查看它的文档字符串:
class Demo:
@property
def attr(self):
"""this is a demo property"""
return 1
# 类访问返回property实例
print(Demo.attr) # 输出<property object at 0x...>
# 查看文档字符串
print(Demo.attr.__doc__) # 输出this is a demo property
使用注意事项
- property装饰的方法第一个参数必须是self,因为它是实例方法,会被作为fget参数传入,调用时需要传入实例。
- 不要在property的getter方法里修改实例的其他属性,否则会导致逻辑混乱,比如每次访问属性都触发修改操作。
- 如果只需要只读属性,不需要定义setter方法,否则赋值时会触发AttributeError异常。
通过上述分析可以看出,property的核心是基于描述符协议实现的,它通过拦截属性的访问、赋值、删除操作,把方法调用伪装成属性访问,同时保留了方法的灵活性,这也是它能在很多场景下简化代码的原因。