python-get、set、delete内置属性(描述符)

可参考 https://www.cnblogs.com/Jimmy1988/p/6808237.html

一、描述符是什么?

  描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法有 __get__(), __set__(), 和__delete__()。如果这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符。

  通俗说法就是一个新式类中的类属性的值是另一个新式类,且这个新式类实现了__get__(), __set__(), 和__delete__()

  那这些方法何时、何地会被触发呢?

  __get__():另一个新式类中调用属性时会触发

   __set__():另一个新式类中属性赋值时触发

  __delete__():另一个新式类中采用del删除属性时触发

class Foo:

    def __get__(self, instance, owner):
        print("get执行了")
        print("self:%s" %self)
        print("instance:%s" %instance)
        print("owner:%s" %owner)

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

    def __delete__(self, instance):
        print("delete执行了")


f = Foo()
f.name = "gg"   # 没有触发get方法
print(f.__dict__)
del f.name  # 没有触发del方法
print(f.__dict__)

  运行结果

{'name': 'gg'}
{}

 

  没有触发,到底是怎么回事呢?

  

class Foo:

    def __get__(self, instance, owner):
        print("get执行了")
        print("self:%s" %self)
        print("instance:%s" %instance)
        print("owner:%s" %owner)

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

    def __delete__(self, instance):
        print("delete执行了")




class Bar:
    x = Foo() # Bar属性被Foo代理


 # 我们再获取属性看是否会触发

b = Bar()
b.x     

  运行结果

get执行了
self:<__main__.Foo object at 0x0000000002117630>  #其实就是Bar的属性x,因为x被Foo代理了,所以这里是Foo这个实例
instance:<__main__.Bar object at 0x0000000002117828>  # 自己本身的实例对象
owner:<class '__main__.Bar'>  # 实例的拥有者

  

那么set和del是怎么触发呢?

class Foo:
    cls_val = "en"
    def __init__(self,name):
        self.name = name

    def __get__(self, instance, owner):
        print("get执行了")
        # print("self:%s" %self)
        # print("instance:%s" %instance)
        # print("owner:%s" %owner)
        print("name:%s" %self.name)

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

    def __delete__(self, instance):
        print("delete执行了")

class Bar:
    x = Foo("x") # Bar属性被Foo代理

    def __init__(self):
        self.y = Foo("y")



b = Bar()
print("#"*40)
b.x = 100
print("#"*40)
del b.x

 

 

   运行结果

########################################
set执行了
########################################
delete执行了

 

 

 

 

二、描述符用来干什么?

  描述符的作用是用来代理另一个类的属性(必须把描述符定义为这个类的类属性,不能定义到构造函数中)

 

三、描述符分类

  数据描述符:至少实现了__set__()和__get__()方法

  非数据描述符:没有实现__set__()方法

四、注意事项

  1、描述符本身定义为新式类,被代理的类也应该是新式类

  2、必须把描述符定义为被代理类的类属性,不能定义到构造函数值

  3、 严格遵循该优先级,优先级由高到低如下

    类属性 >数据描述符>实例属性>非数据描述符>找不到(__getattr__())

  

  类属性 >数据描述符

class Foo:

    def __get__(self, instance, owner):
        print("get执行了")


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

    def __delete__(self, instance):
        print("delete执行了")




class Bar:
    x = Foo() # Bar属性被Foo代理

print(Bar.x) # 查找x属性,然后发现是个描述符,于是触发了get

Bar.x = 1 # 设置类属性,没有触发描述符,说明类属性>数据描述符
print(Bar.__dict__["x"])    # 类属性已经设置的值为1 

 

 运行结果

 

get执行了
None
1

 

 

数据描述符>实例属性

class Foo:

    def __get__(self, instance, owner):
        print("get执行了")


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

    def __delete__(self, instance):
        print("delete执行了")




class Bar:
    x = Foo() # Bar属性被Foo代理



b = Bar()
b.x # 找实例b的属性,但是x属于已经被代理,是数据描述符,所以触发get
b.x = 100 # 赋值实例b的属性,但是x属于已经被代理,是数据描述符,所以触发set
del b.x #删除实例b的属性,但是x属于已经被代理,是数据描述符,所以触发del

 

   运行结果

get执行了
set执行了
delete执行了

 

 实例属性>非数据描述符

 

class Foo:

    def __get__(self, instance, owner):
        print("get执行了")






class Bar:
    x = Foo() # Bar属性被Foo代理

b2 = Bar()
b2.x    #找到属性x是描述符,触发get
print("#"*40)
b = Bar()
b.x = 900       # 找到属性x是描述符,但是没有set方法,所以实例x属性的值改为900。实例属性>非数据描述符

  运行结果

get执行了
########################################

 

  

 五、描述符的应用

  限制传入参数的数据类型

  name为字符串类型

  age为int类型

class Type:

    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 = Type("name")

    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary


p1 = People("老顽童", 99, 1000)  # 实例化,将"老顽童"传给形参name,name又赋值给实例属性name,而name被Tpye代理了,触发set方法
print(p1.__dict__)  # 没有name属性,因为name被代理了
print(p1.name)  # 触发get方法

 

  运行结果

运行set
{'name': '老顽童', 'age': 99, 'salary': 1000}
运行get
老顽童

 

 

 被代理的属性,在设置、获取、删除时,会触发描述符(可以理解为一个代理,一个中介的角色),通过get、set、del方法然后回传给实例

那么我们在get、set、del加上一些判断逻辑,就可以实现实例添加属性时对数据类型进行限制的功能了

 

 

class Type:

    def __init__(self,key,data_type):
        self.key = key
        self.data_type = data_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.data_type):
            raise TypeError ("传入的%s不是%s类型" % (value,self.data_type))
        instance.__dict__[self.key] = value # 将实例的属性赋值

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


class People:
    name = Type("name",str)
    age = Type("age",int)

    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary


# p1 = People("老顽童", 99, 1000)  # 实例化,将"老顽童"传给形参name,name有赋值给属性name,而name被Tpye代理了,触发set方法
# print(p1.__dict__)  # 没有name属性,因为name被代理了
# print(p1.name)  # 触发get方法

p1 = People("东方不败", "99", 1000)

  运行结果

Traceback (most recent call last):
运行set
  File "D:/pythonproject/work/venv/myplan/day28/描述符应用.py", line 37, in <module>
运行set
    p1 = People("东方不败", "99", 1000)
  File "D:/pythonproject/work/venv/myplan/day28/描述符应用.py", line 29, in __init__
    self.age = age
  File "D:/pythonproject/work/venv/myplan/day28/描述符应用.py", line 15, in __set__
    raise TypeError ("传入的%s不是%s类型" % (value,self.data_type))
TypeError: 传入的99不是<class 'int'>类型

 

 

 Type加入了data_type属性,设置数据类型,可以灵活的限制每个被代理属性的数据类型

 

 

posted on 2019-11-20 14:59  别动我的锅  阅读(231)  评论(0)    收藏  举报

导航

levels of contents