什么是描述符?
python描述符是一个"绑定行为"的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法有__get()__、__set()__、__delete()__ 。如果这些方法中的任意一种方法被定义在对象中,那么这个对象就是一个描述符。 它是一个类,定义了另一个类的访问方式,即一个类可以将属性管理托管给另一个类 。
需要注意的是描述符不能定义在类的构造函数中,只能定义为类的属性,描述符只属于类不属于实例。
描述符协议
描述符类基于3个特殊方法,这3个方法组成了描述符协议
- __ set__(self,obj,type=none)设置属性时调用,也称为setter
- __ get__(self,obj,value) 读取属性时调用,也称为getter
- __ delete__(self,obj) 对象调用del时调用这一方法
描述符特点
- 是一个类,定义了访问另外一个类属性的方式
- 把实现了__get()__、__set()__ 方法的描述符称为数据描述符
- 把只实现了__get()__方法的描述符称为非数据描述符
- 在每次查找属性时,描述符协议的方法实际上是由对象的特殊方法getattrbute(不是getattr()),每次通过点号(instance.atrribute)或者getattr(instance,'attribute')函数来执行查找时,都会隐式的调用getattribute(),对象属性查找顺序如下
- 实例属性
- 类属性
- 父类属性
- __getattr__()方法
魔法方法__ get__(), __ set__(), __ delete__()的原型为
- __ get__(self, instance, owner)
- __ set__(self, instance, value)
- __ del__(self, instance)
原型中self,instance,owner分别代表什么意思呢?还是用实例代码来演示:
class Desc(object):
"""
数据描述符
"""
def __init__(self, key, expected_type):
"""
key: 用户传进的值
expected_type:用户传进的值的类型
"""
self.key = key
self.expected_type = expected_type
"""
描述符的三个内置属性的参数如下:
---------------------------------------------------
self: 是描述符的对象,不是使用描述符类的对象,
instance: 这才是使用描述符类的对象
owner: 是instance的类
value: 是instance的值
---------------------------------------------------
"""
def __get__(self, instance, owner):
print("__get__...")
print("self : \t\t", self, id(self))
print("instance : \t", instance, id(instance))
print("owner : \t", owner)
print('='*40, "\n")
return instance.__dict__[self.key]
def __set__(self, instance, value):
print('__set__...')
print("self : \t\t", self, id(self))
print("instance : \t", instance, id(instance))
print("value : \t", value)
print('='*40, "\n")
if not isinstance(value, self.expected_type):
raise TypeError("参数%s必须为%s" % (self.key, self.expected_type))
instance.__dict__[self.key] = value
class Phone:
#使用描述符
name = Desc("name", str)
print('name-------------', id(name))
price = Desc("price", int)
print('price-------------', id(price))
#以下为测试代码
phone = Phone("HUAWEI", 5000)
print('phone----------------------', id(phone))
print(phone.__dict__)
phone.name = "XIAOMI"
print(phone.__dict__)
print(phone.name) # 此时调用get方法 相当于Phone.__dict__['name'].__get__(phone, Phone)
以下为################################################################输出内容
name------------- 35253048
price------------- 35253104
__set__...
self : <__main__.Desc object at 0x000000000219EB38> 35253048
instance : <__main__.Phone object at 0x000000000219EC18> 35253272
value : HUAWEI
========================================
__set__...
self : <__main__.Desc object at 0x000000000219EB70> 35253104
instance : <__main__.Phone object at 0x000000000219EC18> 35253272
value : 5000
========================================
phone---------------------- 35253272
{'name': 'HUAWEI', 'price': 5000}
__set__...
self : <__main__.Desc object at 0x000000000219EB38> 35253048
instance : <__main__.Phone object at 0x000000000219EC18> 35253272
value : XIAOMI
========================================
{'name': 'XIAOMI', 'price': 5000}
__get__...
self : <__main__.Desc object at 0x000000000219EB38> 35253048
instance : <__main__.Phone object at 0x000000000219EC18> 35253272
owner : <class '__main__.Phone'>
========================================
XIAOMI
可以看到实例化类Phone后,调用对象phone设置name,price会自动调用Desc类的__set()__方法
- self:Desc的实例对象,通过id可以看出也就是代码中的name,price
- instance: Phone的实例对象,通过id可以看出也就是phone
- owner :谁拥有这些东西,当然是Phone这个类,它是最高统治者
调用描述符
描述符在所有者类或者其实例中被调用,对于实例对象 object.__getattribute__() 会把 phone.name 转化为 type(Phone).__dict__['name'].__get__(phone, type(phone)) ,对于类type.__getattribute__()会把Phone.name转化为Phone.__dict__['name'].__get__(None,type(phone))
有几点需要牢记
- 描述符被__getattribute__()方法调用
- 重载
__getattribute__()可能会妨碍描述器被自动调用 -
__getattribute__()仅存在于继承自object的新式类(python3都是新式类)之中 -
object.__getattribute__()和type.__getattribute__()对__get__()的调用不一样
描述符的种类及优先级
把描述符分为数据描述符、非数据描述符,把实现了get和set方法的描述符称为数据描述符、除了set方法没有实现的称为非数据描述符。之所以要区分描述符的种类,是因为在代理类的属性时有严格的优先级限制。优先级顺序高低如下
类属性>数据描述符>实例属性>非数据描述符>找不到的属性,出发getattr()
类属性>数据描述符
class RevealAccess(object):
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print ('Retrieving', self.name)
return self.val
def __set__(self, obj, val):
print ('Updating', self.name)
self.val = val
class MyClass(object):
x = RevealAccess(10, 'var "x"')
y = 5
print(MyClass.__dict__) # 此时x属性为描述符对象
MyClass.x = 8 # 此时没有执行描述符set方法
print(MyClass.__dict__) # 此时x:8
#############################################
{'__module__': '__main__', 'x': <__main__.RevealAccess object at 0x000000000280EAC8>, 'y': 5, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None}
{'__module__': '__main__', 'x': 8, 'y': 5, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None}
通过修改MyClass类x属性值,发现并没有触发set方法的执行,说明类属性的优先级大于数据描述符,此时类属性覆盖了数据描述符,类属性相关操作都与描述符无关。
数据描述符>实例属性
class RevealAccess(object):
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print ('Retrieving', self.name)
return self.val
def __set__(self, obj, val):
print ('Updating', self.name)
self.val = val
class MyClass(object):
x = RevealAccess(10, 'var "x"')
y = 5
t = MyClass()
print(t.__dict__) # 没有x属性
print(MyClass.__dict__) # x属性为描述符对象
print(t.x) # 调用get方法
###############################################
{}
{'__module__': '__main__', 'x': <__main__.RevealAccess object at 0x00000000027FEAC8>, 'y': 5, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None}
Retrieving var "x"
10
实例属性>非数据描述符
class Descriptors:
"""
非数据描述符
"""
def __get__(self, instance, owner):
print("执行Descriptors的set")
def __delete__(self, instance):
print("执行Descriptors的delete")
class Phone:
#使用描述符
name = Descriptors()
def __init__(self, name, price):
self.name = name
self.price = price
#测试
phone = Phone("HUAWEI",6000) #报错,描述符中没有__set__()方法
如果我们的实例属性中使用了非数据描述符,就不能进行复制操作,非数据描述符应用于不需要设置属性的值或者函数(只读操作)
类属性与实例属性重名
class Descriptors:
"""
非数据描述符
"""
def __init__(self, name, price):
self.name = name
self.price = price
def __get__(self, instance, owner):
print("执行Descriptors的get")
return self.name
def __set__(self, instance, value):
print("执行Descriptors的set")
self.value = value
def __delete__(self, instance):
print("执行Descriptors的delete")
class Phone:
#使用描述符
name = Descriptors('XIAOMI', 3000)
def __init__(self, name, price):
self.name = name
self.price = price
#测试
phone = Phone("HUAWEI",6000)
print(phone.name)
#####################################
执行Descriptors的set
执行Descriptors的get
XIAOMI
从结果可以看到,类属性和实例属性重名时,描述符优先,会覆盖实例属性
下篇讲描述符的应用场景







Comments | NOTHING