属性、property和属性描述符
1、实例属性和类属性的区别
在 Python 中,类属性是定义在类级别上的变量或常量,它们是所有该类实例共享的值。而实例属性是定义在实例级别上的变量或常量,每个实例都有其自己的值。
- 值的存储位置:类属性存储在类的命名空间中,而实例属性存储在实例的命名空间中。
- 访问方式:类属性可以通过类名或实例访问,但实例属性只能通过实例访问。
- 继承:子类会继承父类的类属性,但不会继承父类的实例属性。
示例:
class MyClass: class_attribute = 'class_value' def __init__(self, instance_attribute): self.instance_attribute = instance_attribute my_instance_1 = MyClass('instance_value_1') my_instance_2 = MyClass('instance_value_2') print(MyClass.class_attribute) # 输出:'class_value' print(my_instance_1.class_attribute) # 输出:'class_value' print(my_instance_1.instance_attribute) # 输出:'instance_value_1' print(my_instance_2.instance_attribute) # 输出:'instance_value_2'
在上述代码中,class_attribute
是一个类属性,被 MyClass
的所有实例共享。而 instance_attribute
则是一个实例属性,每个实例都有一份自己的值。
2、property应用场景
-
计算属性:有时候我们需要根据对象的状态计算出某个属性的值,这时候可以使用
property
将一个方法转换成一个只读属性,以便在访问这个属性时动态计算出其值。 -
数据校验:当我们想要限制对象的某个属性的取值范围或格式时,可以使用属性的 setter 方法对输入数据进行校验,并确保它们满足要求。
-
防止意外修改:有时候我们希望某些属性只能被读取,而不能被修改,可以使用
property
将其设置为只读属性。 -
封装内部实现细节:属性还可以用来隐藏对象内部的实现细节,从而提高代码的安全性和可维护性。
3、属性描述符和proprety的区别
属性描述符和property都是Python中用于定义类属性的机制,但它们的实现方式不同。
属性描述符是一个类,该类定义了三个方法:__get__()
、__set__()
和__delete__()
。这些方法允许您控制属性的访问、修改和删除。属性描述符可以与任何类属性一起使用,并且一个属性描述符可以被多个类属性共享。当一个属性描述符被赋值给一个类属性时,它会替换该属性的默认行为。例如,在一个类中定义一个属性描述符用于限制属性的取值范围:
class Range: def __init__(self, min_value, max_value): self.min_value = min_value self.max_value = max_value def __get__(self, instance, owner): return instance.__dict__[self.name] def __set__(self, instance, value): if value < self.min_value or value > self.max_value: raise ValueError("Value out of range") instance.__dict__[self.name] = value def __set_name__(self, owner, name): self.name = name class MyClass: x = Range(0, 10) my_obj = MyClass() my_obj.x = 5 print(my_obj.x) my_obj.x = 15 # Raises ValueError: Value out of range
在这个例子中,属性描述符Range
控制了属性x
的取值范围。
另一方面,property
是一个内置函数,它提供了一种简化属性访问的方式。与属性描述符不同,property
不是一个类,而是一个包装器函数,它接受三个可选参数:fget
、fset
和fdel
。这些参数分别指定获取、设置和删除属性时所调用的方法。如果只需要定义读取属性的方法,则可以省略fset
和fdel
。例如:
class MyClass: def __init__(self, x): self._x = x @property def x(self): print("Getting x") return self._x @x.setter def x(self, value): print("Setting x") self._x = value my_obj = MyClass(5) print(my_obj.x) # Calls getter method my_obj.x = 10 # Calls setter method
在这个例子中,property
包装器定义了一个名为x
的属性,使用@property
修饰器标记getter方法,使用@x.setter
修饰器标记setter方法。这样就可以像访问普通属性一样来访问和修改x
属性。当访问x
属性时,会自动调用getter方法;当修改x
属性时,会自动调用setter方法。
总的来说,属性描述符提供了更灵活的属性控制机制,而property
则提供了更简单、更易于使用的语法糖。
4、属性描述符的触发
描述符是一种可以自定义属性访问的方式,它能够通过属性访问来拦截对一个对象属性的读取操作、赋值操作或删除操作,从而实现自定义逻辑。要想识别到属性调用,需要在描述符类中实现__get__
、__set__
和__delete__
这三个方法中的至少一个。
__get__(self, instance, owner)
方法会在获取属性值时被调用,它接收两个参数:instance
表示实例对象,owner
表示定义该属性的类,如果该属性是通过类直接访问,那么instance
为None;如果该属性是通过实例访问,那么instance
为实例本身。__set__(self, instance, value)
方法会在给属性赋值时被调用,它接收三个参数:instance
表示实例对象,value
表示要赋的值。__delete__(self, instance)
方法会在删除属性时被调用,它接收两个参数:instance
表示实例对象。当使用描述符时,只要将其作为一个类的属性,那么就能够在该属性被访问、赋值或删除时自动触发相应的方法。例如,下面是一个简单的描述符类,它能够记录属性访问的次数:
class CountAccess: def __init__(self, name): self.name = name self.count = 0 def __get__(self, instance, owner): self.count += 1 return getattr(instance, self.name) def __set__(self, instance, value): self.count += 1 setattr(instance, self.name, value) def __delete__(self, instance): self.count += 1 delattr(instance, self.name) class MyClass: x = CountAccess('x')
当通过实例访问x
属性时,就会自动触发CountAccess
中相应的方法,例如:
obj = MyClass() obj.x = 10 # 触发 CountAccess.__set__ 方法,记录访问次数并设置 obj 的 x 属性值为 10 print(obj.x) # 触发 CountAccess.__get__ 方法,记录访问次数并获取 obj 的 x 属性值 del obj.x # 触发 CountAccess.__delete__ 方法,记录访问次数并删除 obj 的 x 属性