本文主要是介绍描述器Descriptors,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1.1 描述器的表现
用到三个魔术方法:get()、set()、delete()
方法的签名如下:
object.get(self, instance, owner)
object.set(self, instance, owner)
object.delete(self, instance)
self指代当前实例,调用者
instance是owner的实例
owner是属性所属的类
class A: # 数据描述器的访问优先级要高于实例__dict__的访问;非数据描述器的访问,优先级要低于实例字典的访问def __init__(self):self.a1 = 'a1'print('A init')def __get__(self, instance, owner): # instance为owner的实例print('get~~~~~~~~', self, instance, owner)# print(self.__dict__)return selfdef __set__(self, instance, value): # 可以禁止修改实例的属性;主要看方法里的内容print('set~~~~~~~~~~~', value, instance)# self.data = value # 保存在A的实例里了# setattr(instance, 'x', value) # 无限递归# instance.data = value# instance.__dict__['x'] = valueif instance:raise Exception('不许改')def __set_name__(self, owner, name): # python3.6新增的print(owner, name)self.name = nameclass B: # 属主x = A() # 类属性可以,描述器和属主类的类属性有关;解释器执行到这一句时,会调用__set_name__方法z = 5def __init__(self):# self.y = A() # 实例属性不会,描述器与属主类的实例属性无关self.x = 'b.x' # 动态增加属性print('B init')print('~~~~~~~~~~~~~~~~~')
print(B.x) # 会调用A实例的__get__方法;instance为None,B.x或B().x才会调用描述器
print(B.x.a1) # instance为None;注意是B.x调用了描述器
print('++++++++++++++++++')
b = B()
print(b.x) # instance为<__main__.B object at 0x0000000001E89668>
print(b.x.a1)
print('~~~~~~~~~~~~~~~')
b = B()
print(b.x) # <__main__.A object at 0x0000000000789630>
print(b.__dict__) # {'x': 'b.x'}
print('~~~~~~~~~~~~~~~~~~~~~~')
b.x = 500 # 会调用描述器的__set__方法
print(b.x) # <__main__.A object at 0x00000000027C9668>
print(b.__dict__) # {'x': 500}
print("++++++++++++++++")
B.x = 600 # 如果类的类属性x是描述器,那么不要使用这样的赋值语句
print(b.x) # 500
print(B.x) # 600print('~~~~~~~~~~~~~~~')
b = B()
b.x = 100
print(b.x)
因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类A实例的访问,就会调用__get__方法。
注意描述器与属主类的类属性有关,与实例的属性无关。数据描述器的访问优先级要高于实例字典的访问,非数据描述器的访问优先级要低于实例字典的访问。
1.2 描述器的定义
python中,一个类实现了__get__、set、__delete__三个方法中的任意一个,就是描述器。实现这三个中的某些方法,就支持了描述器协议。
- 仅实现了__get__,就是非数据描述器 non-data descriptor
- 实现了__get__、__set__就是数据描述器 data descriptor
1.3python中的描述器
python的方法(包括staticmethod()和classmethod())都实现为非数据描述器,因此实例可以重新定义和覆盖方法。property()函数实现为一个数据描述器。因此,实例不能覆盖属性的行为。
class A:@classmethoddef foo(cls): # 非数据描述符——> foo = classmethod(foo)pass@staticmethoddef bar(): # 非数据描述符pass@propertydef z(self): # 数据描述符return 5def get_foo(self): # 非数据描述符return self.foodef __init__(self): # 非数据描述符self.foo = 100self.bar = 200# self.z = 300 # 不能覆盖,会报错,因为z为数据描述符a = A()
print(a.__dict__) # {'foo': 100, 'bar': 200}
print(A.__dict__)
注意:类的非数据描述符在实例中可以被重新定义和覆盖,类的数据描述符在实例中不能被重新定义和覆盖。
练习
1. 实现StaticMethod装饰器
2. 实现ClassMethod装饰器
from functools import partialclass StaticMethod:def __init__(self, fn):self.fn = fndef __get__(self, instance, owner):return self.fnclass ClassMethod:def __init__(self, fn):self.fn = fndef __get__(self, instance, owner):return partial(self.fn, owner) # 固定owner即属主类class D:@StaticMethod # stmd = StaticMethod(stmd) 非数据描述器def stmd(x, y):print('static method', x, y)@ClassMethod # foo = ClassMethod(foo)def foo(cls, x, y):print(cls.__name__, x, y)d = D()
d.stmd(4, 5) # static method 4 5d2 = D()
d2.foo(5, 6) # D 5 6
1.4 对实例的数据进行效验
思路:
- 写函数,在__init__中先检查,如果不合格,直接抛异常
- 装饰器,使用inspect模块完成
- 描述器
第一种思路的实现代码:
class Person:def __init__(self, name: str, age: int):params = ((name, str), (age, int))if not self.check_data(params):raise TypeError('类型错误')self.name = nameself.age = agedef check_data(params):for p, typ in params:if not isinstance(p, typ):return Falsereturn True# p1 = Person('tom', '20') # TypeError: 类型错误
第二种思路的代码实现:
from functools import wraps
import inspectdef type_check(cls):@wraps(cls)def wrapper(*args, **kwargs):sig = inspect.signature(cls)params = sig.parameters # OrderedDict# print(params)# values = list(params.values())# keys = list(params.keys())# for i, p in enumerate(args):# if values[i].annotation != inspect._empty and not isinstance(p, values[i].annotation):# raise TypeError('Wrong param={} {}'.format(keys[i], p))for p, (k, v) in zip(args, params.items()):if v.annotation is not v.empty and not isinstance(p, v.annotation):raise TypeError('Wrong param= {} {}'.format(k, p))for k, v in kwargs.items():if params[k].annotation is not v.empty: # inspect._emptyif not isinstance(v, params[k].annotation):raise TypeError('Wrong param={} {}'.format(k, v))return cls(*args, **kwargs)return wrapper@type_check
class Person:def __init__(self, name: str, age: int):self.name = nameself.age = agep1 = Person('tony', 20)
p2 = Person('jacky', '18') # 直接报错
第三种思路的代码实现:
class TypeCheck: # 描述器def __init__(self, typ):self.type = typdef __get__(self, instance, owner):# print('get~~~~~~~~~~~~~~')if instance:return instance.__dict__[self.name]else:raise Exception # 或者return self,总之不正常def __set__(self, instance, value):# print('set~~~~~~~~~~~~~')if instance:if not isinstance(value, self.type):raise TypeError(self.name, '+++++++++++')else:instance.__dict__[self.name] = value # 存回到属主类的实例字典中,默默的检查,不出错的话,像什么都没有发生一样def __set_name__(self, owner, name): # python3.6新增的方法print(name)self.name = nameclass Person:name = TypeCheck(str) # 硬编码,不优雅age = TypeCheck(int)def __init__(self, name: str, age: int):self.name = name # 会调用TypeCheck实例的__set__方法(描述器)self.age = agep3 = Person('curry', 31)
p4 = Person('durant', 29) # 直接抛出异常
print(p3.__dict__) # {'name': 'curry', 'age': 31}
print(p4.__dict__) # {'name': 'durant', 'age': 29}
第三种思路代码实现的过程中,存在硬编码,不优雅,可以使用装饰器动态的给类增加方法。改进后的代码为:
class TypeCheck: # 描述器def __init__(self, name, typ):self.name = nameself.type = typdef __get__(self, instance, owner):# print('get~~~~~~~~~~~~~~')if instance:return instance.__dict__[self.name]else:raise Exception # 或者return self,总之不正常def __set__(self, instance, value):# print('set~~~~~~~~~~~~~')if instance:if not isinstance(value, self.type):raise TypeError(self.name, '+++++++++++')else:instance.__dict__[self.name] = value # 存回到属主类的实例字典中,默默的检查,不出错的话,像什么都没有发生一样# def __set_name__(self, owner, name): # python3.6新增的方法# print(name)# self.name = namedef type_check(cls):sig = inspect.signature(cls)params = sig.parameters# print(params) # 有序字典for name, param in params.items():if param.annotation is not param.empty:setattr(cls, name, TypeCheck(name, param.annotation))return cls# 注意动态给类增加属性(方法)时,__set_name__方法并没有调用,即此方法无效
@type_check # Person = type_check(Person)
class Person:# name = TypeCheck(str) # 硬编码,不优雅# age = TypeCheck(int)def __init__(self, name: str, age: int):self.name = name # 会调用TypeCheck实例的__set__方法(描述器)self.age = agep3 = Person('curry', 31)
p4 = Person('durant', 29) # 直接抛出异常
print(p3.__dict__) # {'name': 'curry', 'age': 31}
print(p4.__dict__) # {'name': 'durant', 'age': 29}
print(Person.__dict__)
既然可以使用函数装饰器给类动态增加属性,那么可否将函数装饰器改为类装饰器呢?答案是肯定的,请见详细代码:
class TypeCheck: # 描述器def __init__(self, name, typ):self.name = nameself.type = typdef __get__(self, instance, owner):# print('get~~~~~~~~~~~~~~')if instance:return instance.__dict__[self.name]else:raise Exception # 或者return self,总之不正常def __set__(self, instance, value):# print('set~~~~~~~~~~~~~')if instance:if not isinstance(value, self.type):raise TypeError(self.name, '+++++++++++')else:instance.__dict__[self.name] = value # 存回到属主类的实例字典中,默默的检查,不出错的话,像什么都没有发生一样# def __set_name__(self, owner, name): # python3.6新增的方法# print(name)# self.name = nameclass TypeInject:def __init__(self, cls):self.cls = clsdef __call__(self, *args, **kwarg):sig = inspect.signature(self.cls)params = sig.parameters# print(params) # OrderedDict([('name', <Parameter "name:str">), ('age', <Parameter "age:int">)])for name, param in params.items():# print(name, param.annotation)if param.annotation != param.empty: # inspect._emptysetattr(self.cls, name, TypeCheck(name, param.annotation))return self.cls(*args, **kwarg)@TypeInject # Person = TypeInject(Person)
class Person:def __init__(self, name: str, age: int):self.name = nameself.age = agep5 = Person('Green', 28)
python中所有方法都是描述器
这篇关于描述器Descriptors的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!