描述符作为独立的类创建,分配给类属性,可以通过子类和实例继承。
python的描述符是包括__get__()、__set__()、__delete__()其中一个或多个方法的类。允许在访问、设置、删除属性时执行特定的操作。
一个描述符管理一个单个的、特定的属性。
本文约定,如果一个类的属性值等于描述符实例,那么这个类称为客户类。
描述符编写为独立的类,当以相应的方式访问分配给描述符类实例的属性时,描述符类中的获取(get)、设置(set)、删除(delete)等方法自动运行。
用法
class Descriptor:
'文档说明'
def __get__(self,instance,owner):pass
def __set__(self,instance,value):pass
def __delete__(self,instance):pass
描述
描述符是一个类,并且定义了__get__()、__set__()、__delete__()中的一个或多个方法,并且描述符实例分配给另一个类的属性,当访问属性时,会自动调用描述符定义的方法。
示例
>>> class MyDSP:
'文档说明'
def __get__(*args):print('获取属性值')
def __set__(*args):print('设置属性值')
def __delete__(*args):print('删除属性')
>>> class MyC:
md=MyDSP()
>>> mc=MyC()
>>> mc.md
获取属性值
>>> MyDSP.__doc__
'文档说明'
>>> del mc.md
删除属性
>>> mc.md='梯阅线条'
设置属性值
>>> mc.md
获取属性值
描述符的三个方法都传递了描述符类实例(self)以及拥有描述符实例的实例对象(instance)。
__get__访问方法还额外地接收一个owner参数,指定了描述符实例要附加到的类。
instance通过instance.attr访问,返回属性所属的实例,通过class.attr访问,返回None。
描述
描述符方法__get__()、__set__()、__delete__()的入参参数:
self:表示描述符类实例,三个方法都有此入参;
instance(客户类实例):表示类属性赋值为描述符类实例的实例对象,三个方法都有此入参;
owner(客户类):表示类属性赋值为描述符实例的类,只有get方法有此入参;
示例
>>> class Descriptor:
def __get__(self,instance,owner):
print('描述符方法入参')
print(self,instance,owner,sep='\n')
>>> class MyClass:
attr=Descriptor()
>>> mc=MyClass()
>>> mc.attr
描述符方法入参
<__main__.Descriptor object at 0x00FD58B0>
<__main__.MyClass object at 0x00FD5930>
<class '__main__.MyClass'>
>>> MyClass.attr
描述符方法入参
<__main__.Descriptor object at 0x00FD58B0>
None
<class '__main__.MyClass'>
描述符的set方法抛出AttributeError异常,则为只读描述符。
通过只读描述符定义的属性,不能修改属性值。
用法
>>> class READONLYDESC:
def __get__(*args):pass
# 实现描述符只读属性:赋值时抛出 AttributeError 错误
def __set__(*args):raise AttributeError('禁止修改')
描述
定义描述符的__set__()方法,并且抛出AttributeError异常,即可定义只读描述符。
示例
>>> class CANSet:
# 没有定于__set__则描述符属性可被修改
def __get__(*args):print('获取')
>>> class C:
cs=CANSet()
>>> c1=C()
>>> c1.cs
获取
>>> C.cs
获取
>>> c1.cs=9555
>>> c1.cs
9555
>>> list(c1.__dict__.keys())
['cs']
>>> c2=C()
>>> c2.cs
获取
>>> C.cs
获取
>>> class READOL:
def __get__(*args):
print('获取')
for arg in args:print(arg)
# 实现描述符只读属性:赋值时抛出 AttributeError 错误
def __set__(*args):raise AttributeError('禁止修改')
>>> class R:
ro=READOL()
>>> r1=R()
>>> r1.ro
获取
<__main__.READOL object at 0x00923390>
<__main__.R object at 0x009236F0>
<class '__main__.R'>
>>> r1.ro=9555
Traceback (most recent call last):
File "" , line 1, in <module>
r1.ro=9555
File "" , line 3, in __set__
def __set__(*args):raise AttributeError('禁止修改')
AttributeError: 禁止修改
# 非描述符属性可以赋值修改
>>> r1.a='a'
>>> r1.a
'a'
描述符三个方法都有instance入参,这个instance表示类属性值=描述符实例的类实例,即拥有描述符实例的类实例,即客户类实例。可以把数据存储在客户类属性中,然后通过描述符的方法操作instance的属性。
描述
(1) python的一个描述符管理一个单个、特定的属性;
(2) __get__()方法需要返回一个值;
(3) 描述符的三个方法使用instance操作拥有描述符实例的类实例的属性;比如instance._name,对应__init__构造函数内的实例属性self._name,属性名相同;
(4) 类实例私有属性约定为前单下划线,比如,self._name;
示例
# 一个描述符管理一个单个、特定的属性
>>> class Name:
'描述符管理属性Name'
def __get__(self,instance,owner):
print('获取属性值')
return instance._name# 操作客户类实例的属性
def __set__(self,instance,value):
print('设置属性值')
instance._name=value
def __delete__(self,instance):
print('删除属性')
del instance._name
>>> class Person:
def __init__(self,name):
self._name=name# 数据存储在客户类实例中
# 将描述符类实例赋值给类属性
name=Name()
>>> p1=Person('梯阅线条')
>>> p1.name
获取属性值
'梯阅线条'
>>> del p1.name
删除属性
>>> p1.name='梯阅'
设置属性值
>>> p1.name
获取属性值
'梯阅'
>>> Name.__doc__
'描述符管理属性Name'
>>> class Name:
'描述符管理属性Name'
def __get__(self,instance,owner):
print('获取属性值')
# instance._age与self._name不一致,获取属性name时报错
return instance._age
>>> class Person:
def __init__(self,name):
self._name=name
# 将描述符类实例赋值给类属性
name=Name()
>>> p1=Person('梯阅线条')
# instance._age与self._name不一致,获取属性name时报错
>>> p1.name
获取属性值
Traceback (most recent call last):
File "" , line 1, in <module>
p1.name
File "" , line 5, in __get__
return instance._age
AttributeError: 'Person' object has no attribute '_age'
描述
描述符三个方法都有self 入参,这个self 表示描述符类实例。可以把数据存储在描述符类实例属性中,然后通过描述符的方法操作self的属性。
示例
>>> class DescPerimt:
'计算圆周长'
def __init__(self,r):
self.r=r# 数据存储在描述符实例属性
def __get__(self,instance,owner):
print('获取圆周长')
return 2*3.14*self.r
def __set__(self,instance,value):
print('设置圆半径')
self.r=value
>>> class Circle1:
cp=DescPerimt(2)
>>> class Circle2:
cp=DescPerimt(3)
>>> c1=Circle1()
>>> c2=Circle2()
>>> c1.cp
获取圆周长
12.56
>>> c2.cp
获取圆周长
18.84
>>> c1.cp=1
设置圆半径
>>> c1.cp
获取圆周长
6.28
>>> c2.cp
获取圆周长
18.84
>>> DescPerimt.__doc__
'计算圆周长'
name属性的例子使用存储在客户实例中数据,圆周长的例子使用附加到描述符对象本身的数据。所以描述符可以使用客户类实例状态和描述符状态,或者二者的组合:
(1) 描述符状态用来管理内部用于描述符工作的数据;
(2) 实例状态记录了和客户类相关的信息,以及可能由客户类创建的信息;
描述符方法也可以使用,但是描述符状态常常使得不必要使用特定的命名惯例,以避免存储在一个实例上的描述符数据的名称冲突。例如,如下的描述符把信息附加到自己的实例,因此,它不会与客户类的实例上的信息冲突。
只管理描述符的属性,不管理非描述符的属性。
描述
描述符实例和客户类实例有相同的属性名,不会产生冲突。
只管理描述符的属性,不管理费描述符的属性。
使用描述符的状态信息,把数据存储在描述符实例的属性中,只操作描述符实例self.属性。
示例
>>> class CUDescState:
def __init__(self,value):
self.value=value
def __get__(self,instance,owner):
print('获取指定数{}的立方值'.format(self.value))
return self.value**3
def __set__(self,instance,value):
print('设置指定数')
self.value=value
>>> class DoCube:
x=CUDescState(2) #x为描述符属性
y=3#y和value为非描述符属性
def __init__(self,value):
self.value=value#客户实例和描述符实例属性名可以相同
>>> ds=DoCube(5)
>>> ds.x,ds.y,ds.value
获取指定数2的立方值
(8, 3, 5)
>>> ds.x=5
设置指定数
>>> ds.y=6
>>> ds.value=7
>>> ds.x,ds.y,ds.value
获取指定数5的立方值
(125, 6, 7)
描述
使用客户实例的状态信息,把数据存储在客户实例的属性中,只操作客户实例instance.属性。
示例
# 使用描述符状态信息
>>> class CUDescState:
def __init__(self,value):
self.value=value
def __get__(self,instance,owner):
print('使用描述符self状态获取指定数{}的立方值'.format(self.value))
return self.value**3
def __set__(self,instance,value):
print('使用描述符self状态设置指定数')
self.value=value
# 使用客户实例状态信息
>>> class SQInstState:
def __get__(self,instance,owner):
print('使用客户instance状态获取指定数{}的平方值'.format(instance._y))
return instance._y**2
def __set__(self,instance,value):
print('使用客户instance状态设置指定数')
instance._y=value
>>> class DoCubeSquare:
x=CUDescState(2)
y=SQInstState()
def __init__(self):
self._y=3
self.z=5
>>> dcs=DoCubeSquare()
>>> dcs.x,dcs.y,dcs.z
使用描述符self状态获取指定数2的立方值
使用客户instance状态获取指定数3的平方值
(8, 9, 5)
>>> dcs.x=5;dcs.y=6;dcs.z=8
使用描述符self状态设置指定数
使用客户instance状态设置指定数
>>> dcs.x,dcs.y,dcs.z
使用描述符self状态获取指定数5的立方值
使用客户instance状态获取指定数6的平方值
(125, 36, 8)
描述
通过模拟property内置函数,理解特性和描述符的关联关系。
Property为描述符,模拟property内置函数,特性是一种特殊的描述符。
示例
>>> class Property:
def __init__(self,fget=None,fset=None,fdel=None,doc=None):
self.fget=fget
self.fset=fset
self.fdel=fdel
self.__doc__=doc
def __get__(self,instance,instancetype=None):
if instance is None:
return self
if self.fget is None:
raise AttributeError('无法获取属性')
return self.fget(instance)
def __set__(self,instance,value):
if self.fset is None:
raise AttributeError('无法设置属性')
self.fset(instance,value)
def __delete__(self,instance):
if self.fdel is None:
raise AttributeError('无法删除属性')
self.fdel(instance)
>>> class Person:
def __init__(self,name):
self._name=name
def getName(self):
print('获取属性值')
return self._name
def setName(self,name):
print('设置属性值')
self._name=name
name=Property(getName,setName)
>>> p1=Person('梯阅线条')
>>> p1.name
获取属性值
'梯阅线条'
>>> p1.name='梯阅'
设置属性值