Python3 描述符 (__get__/__set__/__delete__/__getattr__/__getattribute__/__setattr__/__delattr__)

描述符

 Python 中,通过使用描述符,可以让程序员在引用一个对象属性时自定义要完成的工作。
本质上看,描述符就是一个类,只不过它定义了另一个类中属性的访问方式。换句话说,一个类可以将属性管理全权委托给描述符类。

描述符是 Python 中复杂属性访问的基础,它在内部被用于实现 property、方法、类方法、静态方法和 super 类型。

描述符类基于以下 3 个特殊方法(魔法方法),换句话说,这 3 个方法组成了描述符协议:

  • 方法的原型为:
    1. __get__(self, instance, owner),调用一个属性时,触发
    2. __set__(self, instance, value),为一个属性赋值时,触发
    3. __delete__(self, instance),采用del删除属性时,触发

参数: 

self : 描述符实例
instance:相当于例子中的实例book
value: 就是要赋予的值
owner:是所有者的类

#描述符类
class revealAccess:
    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:
    x = revealAccess(10,'var "x"')
    y = 5
m = myClass()
# 访问m.x。会先触发__getattribute__方法
# 由于x属性的值是一个描述符,会触发它的__get__方法
print(m.x)
# 设置m.x的值。对描述符进行赋值,会触发它的__set__方法
# 在__set__方法中还会触发__setattr__方法(self.val = val)
m.x = 20
print(m.x)
print(m.y)
"""
# 查看m和MyClass的__dict__,发现这与对描述符赋值之前一样。
# 这一点与一般属性的赋值不同,可参考上述的__setattr__方法。
# 之所以前后没有发生变化,是因为变化体现在描述符对象上,
# 而不是实例对象m和类MyClass上。
>>> m.__dict__
{}
>>> MyClass.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'MyClass' objects>,
            '__doc__': None,
            '__module__': '__main__',
            '__weakref__': <attribute '__weakref__' of 'MyClass' objects>,
            'x': <__main__.RevealAccess at 0x5130080>,
            'y': 5})
"""

"""
运行结果
Retrieving var "x"
10
updating var "x"
Retrieving var "x"
20
5
"""

"""
其中RevealAccess类的实例是作为MyClass类属性x的值存在的。而且RevealAccess类定义了__get____set__方法,它是一个描述符对象。
注意,描述符对象的__get____set__方法中使用了诸如self.valself.val = val等语句,这些语句会调用__getattribute____setattr__等方法,
这也说明了__getattribute____setattr__等方法在控制访问对象属性上的一般性(一般性是指对于所有属性它们的控制行为一致),
以及__get____set__等方法在控制访问对象属性上的特殊性(特殊性是指它针对某个特定属性可以定义不同的行为)。
"""
class Desc():
    def __get__(self, instance, owner):
        print("__get__")
        print("self=",self)
        print("instance=",instance)
        print("owner=",owner)
        print("=="*50,"\n")

class People():
    x = Desc()

p1 = People()
p1.x

print(People.__dict__)
print('-'*100)
print(p1.__dict__)

"""
执行结果如下
__get__
self= <__main__.Desc object at 0x02C40130>
instance= <__main__.People object at 0x02C400F0>
owner= <class '__main__.People'>
==================================================================================================== 

{'__module__': '__main__', 'x': <__main__.Desc object at 0x02C40130>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
----------------------------------------------------------------------------------------------------
{}

"""

  

实现了 __set__ 和 __get__ 方法的描述符类被称为数据描述符;
反之,如果只实现了 __get__ 方法,则称为非数据描述符。
如果这些方法中任何一个被定义在一个对象中,这个对象就是一个描述符。

Python给出的方案是:__getattribute____getattr____setattr____delattr__等方法用来实现属性查找、设置、删除的一般逻辑,而对属性的控制行为就由描述符来解决。

实际上,在每次查找属性时,描述符协议中的方法都由类对象的特殊方法 __getattribute__() 调用(注意不要和 __getattr__() 弄混)。

也就是说,每次使用类对象.属性(或者 getattr(类对象,属性值))的调用方式时,都会隐式地调用 __getattribute__(),它会按照下列顺序查找该属性:

  • 1.类属性
  • 2.数据描述符
  • 3.实例属性
  • 4.非数据描述符
  • 5.__getattr__()方法。

上图是查找b.x这样一个属性的过程。在这里要对此图进行简单的介绍:

  1. 查找属性的第一步是搜索基类列表,即type(b).__mro__,直到找到该属性的第一个定义,并将该属性的值赋值给descr
  2. 判断descr的类型。它的类型可分为数据描述符、非数据描述符、普通属性、未找到等类型。若descr为数据描述符,则调用desc.__get__(b, type(b)),并将结果返回,结束执行。否则进行下一步;
  3. 如果descr为非数据描述符、普通属性、未找到等类型,则查找实例b的实例属性,即b.__dict__。如果找到,则将结果返回,结束执行。否则进行下一步;
  4. 如果在b.__dict__未找到相关属性,则重新回到descr值的判断上。
    • descr为非数据描述符,则调用desc.__get__(b, type(b)),并将结果返回,结束执行;
    • descr为普通属性,直接返回结果并结束执行;
    • descr为空(未找到),则最终抛出 AttributeError 异常,结束查找。

 

 

由描述符类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法

 

描述符的对象 x 其实是类People的类属性,那么可不可以把它变成实例属性呢

class Desc():
    def __init__(self,key):
        self.key = key

    def __get__(self, instance, owner):
        print("__get__")
        print("self.key==",self.key)

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

    def __delete__(self, instance):
        print("__delete__")
        instance.__dict__.pop(self.key)  #删除


class People():
     x = Desc('x')
     def __init__(self):
         self.age =Desc('age')

p1 = People()
print(p1.x)
print(p1.age)
print(p1.__dict__)
print(People.__dict__)
#结果为
__get__
self.key== x
None
<__main__.Desc object at 0x012F09B0>
{'age': <__main__.Desc object at 0x012F09B0>}
{'__module__': '__main__', 'x': <__main__.Desc object at 0x012F00F0>, '__init__': <function People.__init__ at 0x013197C8>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}

说到低还是属性查找顺序

如果是实例属性,则没有调用__get__方法。

所以解释器将该方法转化为People.__dict__[‘age’].__get__(p1, People), 但是People 并没有 age这个属性,也就是类属性中没有age。接着看实例对象__dict__中有没有,有则返回,没有按上图继续。

返回的是Desc对象。

 

扩展:类X和实例x,调用x.foo,等效于调用:

type(x).__dict__['foo'].__get__(x,type(x))

调用X.foo,等效于调用:

type(X).__dict__['foo'].__get__(None,X)

 

如果 类属性的描述符对象 和 实例属性描述符的对象 同名时(实例属性访问不到)

class Desc():
    def __init__(self,key):
        self.key = key

    def __get__(self, instance, owner):
        print("__get__")
        print("get里面的self",self)
        print("get里面的instance",instance)
        print("get里面的owner",owner)
        print("self.key==",self.key)
        print("=="*50,"\n")
        return  self.key

    def __set__(self, instance, value):
        print("__set__")
        print("set里面的self",self)
        print("set里面的instance",instance)
        print("set里面的owner",value)
       # instance.__dict__[self.key]=value


class People():
    name = Desc('name')
    #age  = Desc('age')
    def __init__(self,name,age):
        self.name = name  #self.name = Desc('name')
        #self.age = age

p1 = People('susu',200)
print(p1.name)
print(p1.__dict__)
"""
执行结果如下:
__set__
set里面的self <__main__.Desc object at 0x031909D0>
set里面的instance <__main__.People object at 0x031909F0>
set里面的owner susu
__get__
get里面的self <__main__.Desc object at 0x031909D0>
get里面的instance <__main__.People object at 0x031909F0>
get里面的owner <class '__main__.People'>
self.key== name
==================================================================================================== 

name
{}
"""

每次属性查找,这个协议的方法实际上是由对象的特殊方法getattribute()调用。每次通过点号(ins.attribute)或者getattr(ins, ‘attribute’)函数调用都会隐式的调用getattribute(),它的属性查找的顺序如下:
1. 验证属性是否为实例的类对象的数据描述符
2. 查看该属性是否能在实例对象的dict中找到
3. 查看该属性是否为实例的类对象的非数据描述符

对象属性的访问顺序: 类属性>数据描述符>实例属性>非数据描述符>找不到的属性触发__getattr__()

 

上面例子中,同名要能访问到,在__set__中要加一行。

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

    def __set__(self, instance, value):
        # print('self==',self)
        print('instance==', instance)
        print('value==', value)
        instance.__dict__['_x'] = value

class People():
    _x = Desc()
    def __init__(self,x):
        self._x = x

p1 = People(10)
print(People.__dict__)
print(p1._x) #  {'_x': 10}

 

 类属性>数据描述符

class Desc():
    def __init__(self,name):
        self.name = name

    def __get__(self, instance, owner):
        print("__get__")
        # print("name==",self.name)
        # print("=="*50,"\n")
        return  self.name

    def __set__(self, instance, value):
        # print('self==',self)
        print('instance==', instance)
        print('value==', value)class People():
    x = Desc('susu')  #x 是一个数据描述符

p1 = People()
p1.x  #触发了 __get__方法

People.x = 10 #重新设置类属性
p1.x  #就没有调用了 __get__方法
print(p1.x) #覆盖了数据描述符
类属性>数据描述符

 

 数据描述符>实例属性

class Desc():
    def __init__(self,name):
        self.name = name

    def __get__(self, instance, owner):
        print("__get__")
        # print("name==",self.name)
        # print("=="*50,"\n")
        return  self.name

    def __set__(self, instance, value):
        # print('self==',self)
        print('instance==', instance)
        print('value==', value)
        instance.__dict__['_x'] = value

class People():
    x = Desc('susu')  #x 是一个数据描述符

p1 = People()
p1.x =1   #调用了__set__方法
print(p1.x) #调用__get__方法   susu
print(p1.__dict__) #{'_x': 1}

 

实例属性>非数据描述符
class Desc():
    def __init__(self,name):
        self.name = name

    def __get__(self, instance, owner):
        print("__get__")
        # print("name==",self.name)
        # print("=="*50,"\n")
        return  self.name

class People():
    x = Desc('susu')  #x 是一个数据描述符

p1 = People()
p1.x =1
print(p1.x) #返回的是 1
print(p1.__dict__)

 

非数据描述符>找不到
class Desc():
    def __init__(self,name):
        self.name = name

    def __get__(self, instance, owner):
        print("__get__")
        # print("name==",self.name)
        # print("=="*50,"\n")
        return  self.name


class People():
    x = Desc('susu')  #x 是一个数据描述符
    def __getattr__(self, item):
        print('找不到')

p1 = People()
p1.x # 会调用get方法
p1.y # 找不到才调用__getattr__方法
"""
执行结果:
__get__
找不到
"""

描述符应用

class Desc():
    def __init__(self,key):
        self.key = key

    def __get__(self, instance, owner):
        print("__get__")return  instance.__dict__[self.key]  #获取

    def __set__(self, instance, value):
        print("__set__")
        instance.__dict__[self.key]=value  #写入

    def __delete__(self, instance):
        print("__delete__")
        instance.__dict__.pop(self.key)  #删除

class People():
    name = Desc('name')
    age  = Desc('age')
    def __init__(self,name,age):
        self.name = name
        self.age = age

p1 = People('susu',200)
print(p1.name)
print(p1.age)
print(p1.__dict__)
del p1.age
print(p1.__dict__)

"""
__set__
__set__
__get__
susu
__get__
200
{'name': 'susu', 'age': 200}
__delete__
{'name': 'susu'}

"""
#name 输入的类型是str类型,如果不是str类型就报错
#age 输入的类型是int类型,如果不是int类型就报错
class Desc():
    def __init__(self,key,expected_type):
        self.key = key
        self.expected_type = expected_type

    def __get__(self, instance, owner):
        print("__get__")
        return  instance.__dict__[self.key]  #获取

    def __set__(self, instance, value):
        print("__set__")
        if not isinstance(value,self.expected_type):
            raise TypeError("%s 类型必须输入%s类型"%(self.key,self.expected_type))
        instance.__dict__[self.key]=value  #写入

    def __delete__(self, instance):
        print("__delete__")
        instance.__dict__.pop(self.key)


class People():
     name = Desc('name',str)
     age = Desc('age',int)
     def __init__(self,name,age,salary):
         self.name = name
         self.age = age
         self.salary = salary

p1 = People('susu',28,75222)
print(p1.name)
print(p1.age)
print(p1.__dict__)
print(People.__dict__)

 

 注意:

必须把描述符定义成这个类的类属性,不能为定义到构造函数中。按照顺序,定义在构造方法中,不能实现,上面有代码

 

利用描述符实现@property

 

 拓展

1. __get__ 和 __getattr__ 和 __getattribute__ 和getattr()的区别

   “object.__getattribute__(self, name)”

__getattribute__是实例对象查找属性或方法的入口。自动调用。实例对象访问属性或方法时都需要调用到__getattribute__,之后才会根据一定的规则在各个__dict__中查找相应的属性值或方法对象,若没有找到则会调用__getattr__。

  object.__getattr__(self, name)

__getattr__可以用来在当用户试图访问一个根本不存在(或者暂时不存在)的属性时,来定义类的行为。前面讲到过,当__getattribute__方法找不到属性时,最终会调用__getattr__方法。它可以用于捕捉错误的以及灵活地处理AttributeError。只有当试图访问不存在的属性时它才会被调用。返回一个值或 AttributeError 异常。(此处属性指方法和属性)

  object.__get__(self, instance, owner)

如果类中重写了它,则这个类就可以称为“描述符”,上面有详细介绍

  getattr()

用于返回一个对象属性值,getattr(instance,'c')和instance.c是一样的。看一下反射。方法和属性都可以获取。

class A:
    att = 'abc'
    def __getattribute__(self, item):
        print("触发了 __getattribut__()")
        return object.__getattribute__(self, item) + ' from getattribute'

    def __getattr__(self, item):
        print("触发了 __getattr__() 方法")
        return item + " form getatter"

    def __get__(self, instance, owner):
        print("触发了 __get__()方法", instance, owner)
        return self

class B:
    a1 = A()

if __name__ == '__main__':
    a2 = A()
    b = B()

    print('————————————————————————————————————————')
    print(a2.att)
    print('————————————————————————————————————————')
    print(getattr(a2,'att'))
    print('————————————————————————————————————————')
    print(a2.ppppppppp)
    print('————————————————————————————————————————')
    b.a1
    print('————————————————————————————————————————')
    print(b.a1.att)
————————————————————————————————————————
触发了 __getattribut__()
abc from getattribute
————————————————————————————————————————
触发了 __getattribut__()
abc from getattribute
————————————————————————————————————————
触发了 __getattribut__()
触发了 __getattr__() 方法
ppppppppp form getatter
————————————————————————————————————————
触发了 __get__()方法 <__main__.B object at 0x000001BADAB38940> <class '__main__.B'>
————————————————————————————————————————
触发了 __get__()方法 <__main__.B object at 0x000001BADAB38940> <class '__main__.B'>
触发了 __getattribut__()
abc from getattribute

Process finished with exit code 0

 每次通过实例访问属性,都会经过__getattribute__函数。而当属性不存在时,仍然需要访问__getattribute__,不过接着要访问__getattr__。这就好像是一个异常处理函数。

每次访问descriptor(即实现了__get__的类),都会先经过__get__函数。 

需要注意的是,当使用类访问不存在的变量是,不会经过__getattr__函数。而descriptor不存在此问题,只是把instance标识为none而已。

 

  • __setattr__(self, name, value)

    __setattr__方法允许你自定义某个属性的赋值行为,不管这个属性存在与否,都可以对任意属性的任何变化都定义自己的规则。关于__setattr__有两点需要说明:第一,使用它时必须小心,不能写成类似self.name = "Tom"这样的形式,因为这样的赋值语句会调用__setattr__方法,这样会让其陷入无限递归;第二,你必须区分 对象属性 和 类属性 这两个概念。后面的例子中会对此进行解释。

  • __delattr__(self, name)

    __delattr__用于处理删除属性时的行为。和__setattr__方法要注意无限递归的问题,重写该方法时不要有类似del self.name的写法。

class Animal(object):
    run = True
class Dog(Animal):
    fly = False
    def __init__(self, age):
        self.age = age
    def __getattr__(self, name):
        print "calling __getattr__\n"
        if name == 'adult':
            return True if self.age >= 2 else False
        else:
            raise AttributeError
    def __setattr__(self, name, value):
        print "calling __setattr__"
        super(Dog, self).__setattr__(name, value)  #self.__dict__['name'] = value
    def __delattr__(self, name):
        print "calling __delattr__"
        super(Dog, self).__delattr__(name)

”“”
# 创建实例对象dog
>>> dog = Dog(1)
calling __setattr__
# 检查一下dog和Dog的__dict__
>>> dog.__dict__
{'age': 1}
>>> Dog.__dict__
dict_proxy({'__delattr__': <function __main__.__delattr__>,
            '__doc__': None,
            '__getattr__': <function __main__.__getattr__>,
            '__init__': <function __main__.__init__>,
            '__module__': '__main__',
            '__setattr__': <function __main__.__setattr__>,
            'fly': False})
# 获取dog的age属性
>>> dog.age
1
# 获取dog的adult属性。
# 由于__getattribute__没有找到相应的属性,所以调用__getattr__。
>>> dog.adult
calling __getattr__
False
# 调用一个不存在的属性name,__getattr__捕获AttributeError错误
>>> dog.name
calling __getattr__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 10, in __getattr__
AttributeError

# 给dog.age赋值,会调用__setattr__方法
>>> dog.age = 2
calling __setattr__
>>> dog.age
2
# 先调用dog.fly时会返回False,这时因为Dog类属性中有fly属性;
# 之后再给dog.fly赋值,触发__setattr__方法。
>>> dog.fly
False
>>> dog.fly = True
calling __setattr__
# 再次查看dog.fly的值以及dog和Dog的__dict__;
# 可以看出对dog对象进行赋值,会在dog对象的__dict__中添加了一条对象属性;
# 然而,Dog类属性没有发生变化
# 注意:dog对象和Dog类中都有fly属性,访问时会选择哪个呢?
>>> dog.fly
True
>>> dog.__dict__
{'age': 2, 'fly': True}
>>> Dog.__dict__
dict_proxy({'__delattr__': <function __main__.__delattr__>,
            '__doc__': None,
            '__getattr__': <function __main__.__getattr__>,
            '__init__': <function __main__.__init__>,
            '__module__': '__main__',
            '__setattr__': <function __main__.__setattr__>,
            'fly': False})

实例对象的__setattr__方法可以定义属性的赋值行为,不管属性是否存在。当属性存在时,它会改变其值;当属性不存在时,它会添加一个对象属性信息到对象的__dict__中,然而这并不改变类的属性。从上面的例子可以看出来。

# 由于上面的例子中我们为dog设置了fly属性,现在删除它触发__delattr__方法
>>> del dog.fly
calling __delattr__
# 再次查看dog对象的__dict__,发现和fly属性相关的信息被删除
>>> dog.__dict__
{'age': 2}
“”“

 

 这个代码不错

class Quantity:#改进版描述符类
    __counter = 0 

    def __init__(self):
        cls = self.__class__ 
        prefix = cls.__name__
        index = cls.__counter
        self.storage_name = '_{}#{}'.format(prefix, index) #每个描述符实例的属性名称都是独一无二的。
        cls.__counter += 1 

    def __get__(self, instance, owner): #此处owner参数是托管类LineItem。
        return getattr(instance, self.storage_name) #从instance中获取储存属性的值。

    def __set__(self, instance, value):
        if value > 0:
            setattr(instance, self.storage_name, value) #使用setattr把值储存在instance中。
        else:
            raise ValueError('value must be > 0')

class LineItem:#托管类
    weight = Quantity()
    price = Quantity()

    def __init__(self,description,weight,price):
        self.description=description
        self.weight=weight
        self.price=price

    def subtotal(self):
        return self.weight*self.price

 

 

http://fanchunke.me/Python/Python%E4%B8%AD%E7%9A%84%E5%B1%9E%E6%80%A7%E8%AE%BF%E9%97%AE%E4%B8%8E%E6%8F%8F%E8%BF%B0%E7%AC%A6/

 https://www.cnblogs.com/sugh/articles/11912608.html

 

posted @ 2021-01-29 22:36  云long  阅读(356)  评论(0编辑  收藏  举报