Python描述器实现类型检查

来源:互联网 发布:知乎封号解封 编辑:程序博客网 时间:2024/06/06 06:38

(一) 使用描述器对赋值过程做类型检查


下述代码的简要说明
  • a = A(1,’yhy’) 实例化A类的时候,self.x访问的x是类变量TypeCheck(‘a’,int),首先会初始化TypeCheck类,由于是self.x = x赋值会调用set方法,在set方法里面,instance.dict[self.srcType] = value, 就已经将赋值完成了。

  • a.x 取值的时候,需要self.x,同样是调用self.TypeCheck,也会初始化,初始化的时候,在init方法里面会拿到self.srcType和self.dstType的值,拿到了self.srcType和self.dstType值在get方法里面,才会知道instance__dict__[key]的key是什么,依据对应的key值取出对于的value,最后返回。因此最后拿到的是instance__dict__[key]的值

class TypeCheck:    def __init__(self, srcType, dstType):        self.srcType = srcType        self.dstType = dstType    # instance == a , cls == A    def __get__(self, instance, cls):        if instance is None:            return self        return instance.__dict__[self.srcType]    def __set__(self, instance, value):        if isinstance(value,self.dstType):            instance.__dict__[self.srcType] = value        else:            raise TypeError('{} should be {}'.format(value,self.dstType))class A:    # 这个a,b 作为字典的key只要不一样就行    x = TypeCheck('a',int)    y = TypeCheck('b',str)    def __init__(self, x, y):        self.x = x        self.y = ya = A(1,'yhy')print(a.x,a.y)

(二) 在装饰器中调用描述器做类型检查


下述代码的简要说明
  • 通过setattr魔术方法,对Person类进行了修改,这里的name作为类属性,name = TypeCheck(name, required_type),这样就将Person类进行了改造,使得Person类有了两个类变量,一个是name = TypeCheck(‘name’, required_type), 另一个是age = TypeCheck(‘age’, required_type)

  • 因此,Person(‘yhy’,18) 初始化的时候,self.name 中的name不是实例变量而是类变量,会调用描述器TypeCheck

  • 赋值的时候,就会调用set方法,取值的时候会调用get方法

# Python write by yhyfrom functools import wrapsclass TypeCheck:    def __init__(self, srcType, dstType):        self.srcType = srcType        self.dstType = dstType    # instance == a , cls == A    def __get__(self, instance, cls):        if instance is None:            return self        return instance.__dict__[self.srcType]    def __set__(self, instance, value):        if isinstance(value,self.dstType):            instance.__dict__[self.srcType] = value        else:            raise TypeError('{} should be {}'.format(value,self.dstType))# 函数装饰器装饰的是一个Person类# 描述器描述类与描述函数是不一样的def typeassert(**kwargs):    def dec(cls):        for name, required_type in kwargs.items():            setattr(cls, name, TypeCheck(name, required_type))        return cls    return dec@typeassert(name=str, age=int)class Person:    def __init__(self, name: str, age: int):        self.name = name        self.age = agep = Person('yhy',18)print(p.name)

装饰器装饰的Person类变为:

# 装饰器修改后的Person类class Person:    # 装饰器使得Person类多了两个类变量name和age    name = TypeCheck('name',str)     age = TypeCheck('age',int)    def __init__(self, name: str, age: int):        self.name = name        self.age = age

(三) 导入inspect库,通过signature函数分析装饰器中类的参数,实现对装饰器中类的装饰


简要介绍inspect库的signature函数
# signature库可以对获取类初始化的参数的变量名和变量类型from inspect import signatureclass A:    def __init__(self, x: int, y: str):        passprint(signature(A).parameters)print(signature(A).parameters.items())for key, value in signature(A).parameters.items():    print(key)    # 取出类型    print(value.annotation)# print(key) 和 print(value.annotation) 的输出结果为x                   # 这是参数变量名<class 'int'>       # 这是变量名对应的类型y<class 'str'>
通过上述的inspect库的signature函数的简要介绍,那么就可以通过signature函数获取变量名,从而实现无需给装饰器显示的传递参数,可以直接分析需要装饰的类,获取类初始化需要传递的参数名和参数的类型,在通过参数名和参数的类型装饰需要装饰的类
from inspect import signatureclass TypeCheck:    def __init__(self, srcType, dstType):        self.srcType = srcType        self.dstType = dstType    # instance == a , cls == A    def __get__(self, instance, cls):        if instance is None:            return self        return instance.__dict__[self.srcType]    def __set__(self, instance, value):        if isinstance(value,self.dstType):            instance.__dict__[self.srcType] = value        else:            raise TypeError('{} should be {}'.format(value,self.dstType))def typeassert(cls):    #def dec(cls):        sig = signature(cls).parameters.items()        for key, value in sig:            setattr(cls, key, TypeCheck(key,value.annotation))        return cls    #return dec@typeassertclass Person:    def __init__(self, name: str, age: int):        self.name = name        self.age = agep = Person('yhy',25)print(p.name)print(p.age)