描述符
描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,也被叫做描述符协议。
__get__():调用一个属性时候,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时候,触发
python3和python2的区别
# 在python3中Foo是新式类,它实现了__get__(),__set__(),__delete__()中的一个三种方法的一个,这个类就被称作一个描述符 class Foo: def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __delete__(self, instance): pass
描述符的作用
重点
描述符是干什么的:描述符的作用是用来代理另外一个类的属性的,必须把描述符定义成这个类的类属性,不能定义到构造函数。
例子
class Int: def __get__(self, instance, owner): print('Int的get') def __set__(self, instance, value): print('Int的set') def __delete__(self, instance): print('Int的delete') class People: def __init__(self, name, age): self.name = name self.age = age name = Str() age = Int() p1 = People('alex',12) p1.name p1.name = 'jc' del p1.name
Str的set
Int的set
Str的get
Str的set
Str的delete
到底为什么呢
# 省略以山步骤 class People: def __init__(self, name, age): self.name = name self.age = age # name 是Str的一个实例 name = Str() # age 是 Int的一个实例 age = Int() p1 = People('alex',12) # p1.name # p1.name = 'jc' # del p1.name print(p1.__dict__)
{}
print(People.__dict__)
{'__module__': '__main__', '__init__': <function People.__init__ at 0x000001FE39192378>, 'name': <__main__.Str object at 0x000001FE391AE048>, 'age': <__main__.Int object at 0x000001FE391AE080>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
两种描述符
数据描述符(至少实现了__get__()和__set__())
class Foo: def __set__(self, instance, value): print('set') def __get__(self, instance, owner): print('get')
非数据描述符(没有实现__set__)
class Foo: def __get__(self, instance, owner): print('get')
描述符注意事项
描述符本身应该定义成新式类,被代理的类也是新式类
必须把描述符定义成这个类的类属性,才是描述符,不能够定义到构造函数里面去
注意
优先级由高到低分别是 1、类属性 类里面的属性,name,age,gender之类的
2、数据描述符 实现了__get__() 和 __set__()
3、实例属性 p1.name
4、非数据描述符 没有实现__set__()方法
5、找不到的属性触发__getattr__()
使用描述符
总所周知、python是弱类型语言,即参数的赋值没有类型限制。
例子1
class Str: def __init__(self, name): self.name = name def __get__(self, instance, owner): print('get--->', instance, owner) return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->', instance, value) print(instance.__dict__) instance.__dict__[self.name] = value print(instance.__dict__) def __delete__(self, instance): print('delete--->', instance) instance.__dict__.pop(self.name) class People: name = Str('name') def __init__(self, name, age, salary): self.name = name # 触发__set__ self.age = age self.salary = salary p1 = People('JAC', 18, 3231.3)
#实例的属性 __dict__
print(p1.__dict__)
{'name': 'randy', 'age': 18, 'salary': 3231.3}
# 赋值
p1.name = 'jackson'
# 删除
print(p1.__dict__)
del p1.name
print(p1.__dict__)
{'name': 'jackson', 'age': 18, 'salary': 3231.3}
delete---> <__main__.People object at 0x000002EC6CCFE240>
{'age': 18, 'salary': 3231.3}
例子2
class Str: def __init__(self, name): self.name = name def __get__(self, instance, owner): print('get--->', instance, owner) return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->', instance, value) instance.__dict__[self.name] = value def __delete__(self, instance): print('delete--->', instance) instance.__dict__.pop(self.name) class People: name = Str('name') def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary # 疑问:如果我用类名去操作属性呢 try: People.name # 报错,错误的根源在于类去操作属性时,会把None传给instance except Exception as e: print(e)
'NoneType' object has no attribute '__dict__'
修改例2
class Str: def __init__(self, name): self.name = name def __get__(self, instance, owner): print('get--->', instance, owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->', instance, value) instance.__dict__[self.name] = value def __delete__(self, instance): print('delete--->', instance) instance.__dict__.pop(self.name) class People: name = Str('name') def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary print(People.name) # 完美,解决
例三
class Str: def __init__(self, name, expected_type): self.name = name self.expected_type = expected_type def __get__(self, instance, owner): print('get--->', instance, owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->', instance, value) if not isinstance(value, self.expected_type): # 如果不是期望的类型,则抛出异常 raise TypeError('Expected %s' % str(self.expected_type)) instance.__dict__[self.name] = value def __delete__(self, instance): print('delete--->', instance) instance.__dict__.pop(self.name) class People: name = Str('name', str) # 新增类型限制str 牛逼的描述符 def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary try: p1 = People(123, 18, 3333.3) # 传入的name因不是字符串类型而抛出异常 except Exception as e: print(e)
set---> <__main__.People object at 0x00000167DC2FE208> 123
Expected <class 'str'>
例四
基本我们已经实现全部功能了,但是问题是,类有很多属性怎么办呢,一堆一堆去实现是不是显得很low
class Type: def __init__(self, name, expected_type): self.name = name self.expected_type = expected_type def __get__(self, instance, owner): print('get--->', instance, owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->', instance, value) if not isinstance(value, self.expected_type): # 如果不是期望的类型,则抛出异常 raise TypeError('Expected %s' % str(self.expected_type)) instance.__dict__[self.name] = value def __delete__(self, instance): print('delete--->', instance) instance.__dict__.pop(self.name) class People: name = Type('name', str) # 新增类型限制str age = Type('name', int) # 新增类型限制str salary = Type('name', float) # 新增类型限制str def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary try: p1 = People('jac', 12, 3333.0) # 传入的name因不是字符串类型而抛出异常 except Exception as e: print(e)
例5
使用一个类的装饰器(无参数)
def decorate(cls): print('类的装饰器开始运行啦------>') return cls @decorate # 无参:People = decorate(People) class People: def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary p1 = People('randy', 18, 3333.3) # 类的装饰器开始运行啦------>
使用一个类的装饰器(有参数)
def typeassert(**kwargs): def decorate(cls): print('类的装饰器开始运行啦------>', kwargs) return cls return decorate # # 有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) @typeassert( name=str, age=int, salary=float ) class People: def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary p1 = People('randy', 18, 3333.3) # 类的装饰器开始运行啦------> {'name': <class 'str'>, 'age': <class 'int'>, 'salary': <class 'float'>} print(p1.__dict__)
# {'name': 'randy', 'age': 18, 'salary': 3333.3}
例6
class Typed: def __init__(self, name, expected_type): self.name = name self.expected_type = expected_type def __get__(self, instance, owner): print('get--->', instance, owner) if instance is None: return self return instance.__dict__[self.name] def __set__(self, instance, value): print('set--->', instance, value) if not isinstance(value, self.expected_type): raise TypeError('Expected %s' % str(self.expected_type)) instance.__dict__[self.name] = value def __delete__(self, instance): print('delete--->', instance) instance.__dict__.pop(self.name) def typeassert(**kwargs): def decorate(cls): print('类的装饰器开始运行啦------>', kwargs) for name, expected_type in kwargs.items(): setattr(cls, name, Typed(name, expected_type)) return cls return decorate @typeassert( name=str, age=int, salary=float ) # 有参:1.运行typeassert(...)返回结果是decorate,此时参数都传给kwargs 2.People=decorate(People) class People: def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary print(People.__dict__) p1 = People('Jac', 18, 3333.3)
描述符总结
描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod、@staticmethod、@property甚至是__slots__属性
描述符是很多高级库和框架里面的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件
自定义Property()
class Lazyproperty: def __init__(self, func): self.func = func def __get__(self, instance, owner): print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()') if instance is None: return self return self.func(instance) # 此时你应该明白,到底是谁在为你做自动传递self的事情 class Room: def __init__(self, name, width, length): self.name = name self.width = width self.length = length # area=Lazyproperty(area) 相当于定义了一个类属性,即描述符 @Lazyproperty def area(self): return self.width * self.length r1 = Room('alex', 1, 1) print(r1.area) # 触发了__get__ # 这是我们自己定制的静态属性,r1.area实际是要执行r1.area() # 1