描述器


什么是描述器呢?近来在复习python 面向对象的笔记,正好温故而知新。

描述器

描述器就是 可以描述一个属性操作的对象。

举例说明

比如有一个类, 类属性 age , 可以在类里面进行修改, 也可以在类外面用 类名.age = 新值进行想修改。
但是这样是不安全的, 但我们也没法对其进行拦截,我们需要对这种数据进行 校验 , 不符合业务逻辑的,就不允许修改。

引出描述器

此时我们要想完成这种对数据进行拦截, 经过校验后才运行赋值的操作,要怎么实现呢?这个时候, 就可以借助描述器了。

描述器的方法

描述器里有三个方法, __set__ , __get__ 和 __delete__ 
有了描述器之后, 所有的在类的外部对类属性进行修改,删除,获取的操作都会被解析器自动转换成描述器的操作, 
这样就可以对数据进行过滤, 保护等

描述器的定义方式(一)

class People:
    def __init__(self):
        self.__age = 0

    def get_age(self):
        return self.__age

    def set_age(self, value):
        if int(value) > 0:
            self.__age = value

    def del_age(self):
        del self.__age

    age = property(get_age, set_age, del_age)


p = People()
p.age = 10
print(p.age)
del p.age
# AttributeError: 'People' object has no attribute '_People__age'
# 已经删除了 age属性了,再去获取,就会报错
# print(p.age)

通过 help 来看到类的说明文档

class People:
    def __init__(self):
        self.__age = 0

    def get_age(self):
        return self.__age

    def set_age(self, value):
        if int(value) > 0:
            self.__age = value

    def del_age(self):
        del self.__age

    age = property(get_age, set_age, del_age)


p = People()
p.age = 10
# print(p.age)
help(People)

运行效果

class People(builtins.object)
 |  Methods defined here:
 |  
 |  __init__(self)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  del_age(self)
 |  
 |  get_age(self)
 |  
 |  set_age(self, value)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  age


进程已结束,退出代码 0

可以看到age 是属于数据描述器

使用 @property 装饰器来实现描述器

class People:
    def __init__(self):
        self.__age = 0

    @property
    def age(self):
        return self.__age

    # 方法名必须一致, 通过装饰器区分是设置还是删除
    @age.setter
    def age(self, value):
        if int(value) > 0:
            self.__age = value

    @age.deleter
    def age(self):
        del self.__age


p = People()
p.age = 12
print(p.age)


这个 实现方法有个弊端, 对一个属性的操作需要用到三个方法, 如果对三个属性进行操作, 就要用到九个方法, 这样是很不方便的

描述器的定义方式(二)把对一个属性的操作都封装到一个类里

class Age:
    def __get__(self, instance, owner):
        print("get")

    def __set__(self, instance, value):
        print("set")

    def __delete__(self, instance):
        print("delete")


class People:
    age = Age()

people = People()
people.age=28
print(people.age)
del people.age

运行效果

set
get
None
delete

从这里可以看出, 只是方法调用了, 没有存储宿主类的数据。 后面会讲怎么做的,这里先不管。

注意事项:一般是 通过宿主类的实例对象进行调用

class Age:
    def __get__(self, instance, owner):
        print("get")

    def __set__(self, instance, value):
        print("set")

    def __delete__(self, instance):
        print("delete")


class People:
    age = Age()


print("look  + " + str(People.age))
# 不会起作用的
del People.age

运行效果

get
look  + None

由于描述器是宿主类的类属性,是可以通过类名进行访问, 但是由于参数是self, 所以也就只有获取可以调用,而且这个获取是只针对类的属性进行获取,设置和删除的 方法是调用不了的。
因此描述器一般都是通过宿主类的实例化对象进行使用的。

实例属性的访问顺序和描述器的拦截

一个实例属性的正常访问顺序

从对象自身的__dict__ 开始找 >> 对应类的__dict__开始找>> 父类的__dict__ 开始找 >> 如果没找到,又定义了 __getattr__方法就会调用这个方法

如何实现将描述器的get 方法给嵌入到查询机制呢?

是通过 __getattribute__ 方法实现的
内部就是先找有没有描述器方法__get__ ,  如果有就调用,没有就按照上面的机制去查找(也就是描述器的检测优先)

拦截

那么既然知道了实现的原理是通过 __getattribute__ , 我们就可以通过在宿主类中手动设置 __getattribute__ 进行拦截,会把描述器的__get__方法给拦截了

拦截代码示范

class Age:
    def __get__(self, instance, owner):
        print("get")

    def __set__(self, instance, value):
        print("set")

    def __delete__(self, instance):
        print("delete")


class People:
    age = Age()

    def __getattribute__(self, item):
        print("get方法被我拦截了")

    def __setattr__(self, key, value):
        print("set 方法也拦截了")

    def __delattr__(self, item):
        print("del 方法也拦截了")

people = People()
people.age = 85
print(people.age)
del people.age

运行效果

set 方法也拦截了
get方法被我拦截了
None
del 方法也拦截了

当然既然描述器的get 可以拦截, 那么set 和 del 也是可以拦截的

描述器的操作方法是优先于python的对象属性查询机制的

从上面的案例就可以得出这个结论,描述器的操作方法是优先于python的对象属性查询机制的

描述器的优先级问题

描述器分为两种, 资料描述器和非资料描述器

什么是资料描述器, 就是实现有 get 和 set 方法的 就是资料描述器;
而非资料描述器是仅仅实现了 get 方法的.

优先级

资料描述器 >> 实例属性 >> 非资料描述器

资料描述器 >> 实例属性

class Age:
    def __get__(self, instance, owner):
        print("get")

    def __set__(self, instance, value):
        print("set")

    def __delete__(self, instance):
        print("delete")


class People:
    age = Age()


people = People()
people.age = 85
print(people.__dict__)

运行效果

set
{}

结论

print(people.__dict__) 显示的为空,说明 age 就是取的资料描述器

实例属性 >> 非资料描述器

class Age:
    def __get__(self, instance, owner):
        print("get")
    #  非资料描述器是没有set 和 delete 方法的
    # def __set__(self, instance, value):
    #     print("set")
    #
    # def __delete__(self, instance):
    #     print("delete")


class People:
    age = Age()


people = People()
# 这里反而成了给实例对象加了实例属性
people.age = 85
print(people.__dict__)

运行效果

{'age': 85}

结论

从运行结果可以看出, 这里是实例属性的优先级更高

描述器的存储问题:描述器的数据存储都是存在宿主类的实例对象里

class Age:
    def __get__(self, instance, owner):
        print("get")
    #  非资料描述器是没有set 和 delete 方法的
    def __set__(self, instance, value):
        print("set")

    def __delete__(self, instance):
        print("delete")


class People:
    age = Age()


people = People()
people.age = 85
print(people.age)

运行效果

set
get
None

当前只是实现了描述器的使用, 但是并没有真正 的将数据存储 到 内存中

class Age:
    def __init__(self):
        self.__age = 0
    def __get__(self, instance, owner):
        print("get")
    #  非资料描述器是没有set 和 delete 方法的
    def __set__(self, instance, value):
        print("self 是 %s , instance 是 % s , value 是 %s" %(self, instance, value))
        print("set")

    def __delete__(self, instance):
        print("delete")


class People:
    age = Age()


people = People()
people.age = 85
print(people.age)

运行效果

self 是 <__main__.Age object at 0x0000020BB95C5AF0> , instance 是 <__main__.People object at 0x0000020BB9609280> , value 是 85
set
get
None

这样看出, self 是 Age的实例化对象 age, instance是People实例对象 people, value是要修改的数值.
实例化一个P2 , 对描述器进行赋值 的 时候 ,也是走这个方法.

class Age:
    def __init__(self):
        self.__age = 0
    def __get__(self, instance, owner):
        print("get")
    #  非资料描述器是没有set 和 delete 方法的
    def __set__(self, instance, value):
        print("self 是 %s , instance 是 % s , value 是 %s" %(self, instance, value))
        print("set")

    def __delete__(self, instance):
        print("delete")


class People:
    age = Age()


people = People()
people.age = 85
print(people.age)
p2 = People()
p2.age = 100
print(p2.age)

运行效果

self 是 <__main__.Age object at 0x00000169A9185AF0> , instance 是 <__main__.People object at 0x00000169A91C9280> , value 是 85
set
get
None
self 是 <__main__.Age object at 0x00000169A9185AF0> , instance 是 <__main__.People object at 0x00000169A91C9850> , value 是 100
set
get
None

可以看到age 对象都是同一个内存地址, 但是 People实例对象的地址不一样,
因此当前 age 是作为People的类属性 的, 所有的People实例对象都共同拥有 age 属性

如果在在描述器中, 把要修改的值都绑定到self 身上(self 就是Age的实例对象本身), 会怎样?

class Age:
    def __init__(self):
        self.__age = 0
    def __get__(self, instance, owner):
        print("get")
        # 不返回, 宿主类的实例对象拿不到值
        return self.v
    #  非资料描述器是没有set 和 delete 方法的
    def __set__(self, instance, value):
        print("self 是 %s , instance 是 % s , value 是 %s" %(self, instance, value))
        print("set")
        self.v = value

    def __delete__(self, instance):
        print("delete")


class People:
    age = Age()


people = People()
people.age = 85
print(people.age)
p2 = People()
# p2.age = 100
# 此时p2.age 会是 85
print(p2.age)

这样就会导致所有的实例对象共享age对象的属性,这样如果有一个实例对象修改了, 另一个实例对象就会受到波及,数据不安全.

绑到 instance 身上(宿主类的实例对象)

class Age:
    def __init__(self):
        self.__age = 0
    def __get__(self, instance, owner):
        print("get")
        # 不返回, 宿主类的实例对象拿不到值
        return instance.v
    #  非资料描述器是没有set 和 delete 方法的
    def __set__(self, instance, value):
        print("self 是 %s , instance 是 % s , value 是 %s" %(self, instance, value))
        print("set")
        instance.v = value

    def __delete__(self, instance):
        print("delete")


class People:
    age = Age()


people = People()
people.age = 85
print(people.age)
p2 = People()
# AttributeError: 'People' object has no attribute 'v'
# p2.age = 100
# 如果注释了 p2.age = 100,就会报错。 描述器在未赋值之前, 直接使用 描述器的get方法就会报错没有这个属性
# print(p2.age)

为了运行验证,可以执行如下代码

class Age:
    def __init__(self):
        self.__age = 0
    def __get__(self, instance, owner):
        print("get")
        # 不返回, 宿主类的实例对象拿不到值
        return instance.v
    #  非资料描述器是没有set 和 delete 方法的
    def __set__(self, instance, value):
        print("self 是 %s , instance 是 % s , value 是 %s" %(self, instance, value))
        print("set")
        instance.v = value

    def __delete__(self, instance):
        print("delete")


class People:
    age = Age()


people = People()
people.age = 85
print(people.age)
p2 = People()
# AttributeError: 'People' object has no attribute 'v'
p2.age = 100
# 如果注释了 p2.age = 100,就会报错。描述器在未赋值之前, 直接使用 描述器的get方法就会报错没有这个属性
print(p2.age)

运行效果

self 是 <__main__.Age object at 0x000001ED77F15AF0> , instance 是 <__main__.People object at 0x000001ED77F594C0> , value 是 85
set
get
85
self 是 <__main__.Age object at 0x000001ED77F15AF0> , instance 是 <__main__.People object at 0x000001ED77F59850> , value 是 100
set
get
100

进程已结束,退出代码 0

文章作者: 陌上人如玉
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 陌上人如玉 !
  目录