python通过描述符管理属性

1 python通过描述符管理属性

描述符作为独立的类创建,分配给类属性,可以通过子类和实例继承。

python的描述符是包括__get__()、__set__()、__delete__()其中一个或多个方法的类。允许在访问、设置、删除属性时执行特定的操作。

一个描述符管理一个单个的、特定的属性。

本文约定,如果一个类的属性值等于描述符实例,那么这个类称为客户类。

1.1 描述符是一个类

描述符编写为独立的类,当以相应的方式访问分配给描述符类实例的属性时,描述符类中的获取(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
获取属性值

1.1.1 描述符方法参数

描述符的三个方法都传递了描述符类实例(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'>

1.1.2 只读描述符

描述符的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'

1.2 数据存储在客户类属性

描述符三个方法都有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'

1.3 数据存储在描述符实例属性

描述

描述符三个方法都有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__
'计算圆周长'

1.4 在描述符中使用状态信息

name属性的例子使用存储在客户实例中数据,圆周长的例子使用附加到描述符对象本身的数据。所以描述符可以使用客户类实例状态和描述符状态,或者二者的组合:

(1) 描述符状态用来管理内部用于描述符工作的数据;

(2) 实例状态记录了和客户类相关的信息,以及可能由客户类创建的信息;

描述符方法也可以使用,但是描述符状态常常使得不必要使用特定的命名惯例,以避免存储在一个实例上的描述符数据的名称冲突。例如,如下的描述符把信息附加到自己的实例,因此,它不会与客户类的实例上的信息冲突。

只管理描述符的属性,不管理非描述符的属性。

1.4.1 使用描述符的状态信息

描述

描述符实例和客户类实例有相同的属性名,不会产生冲突。

只管理描述符的属性,不管理费描述符的属性。

使用描述符的状态信息,把数据存储在描述符实例的属性中,只操作描述符实例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)

1.4.2 使用客户实例的状态信息

描述

使用客户实例的状态信息,把数据存储在客户实例的属性中,只操作客户实例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)

1.5 模拟property内置函数

描述

通过模拟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='梯阅'
设置属性值

你可能感兴趣的:(python,python)