通过描述符、描述符MXIN、参数注解等方法来实现强制类型转换
类型检查
python本身是弱类型的语言,虽然使用方便,但是也会带来一些问题,比如在函数中传入了错误的类型,或者在使用某个变量时,忘记了它的类型,这些问题都会导致程序出错,而且很难发现。
有时候我们需要对传入的参数进行类型检查,以减少错误,这里介绍几种实现强类型检查的方法。
使用描述符
限制类型时首先想到的应该就是描述符。描述符本身也很简单,用的也比较多,这里就不再赘述了,下面是一个简单的描述符类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class DecInt (): def __init__ (self,key ): self.key="" def __set__ (self, instance, value ): if not isinstance (value, int ): raise TypeError("not int" ) else : instance.__dict__[self.key]=value def __get__ (self, instance, owner ): return instance.__dict__[self.key] def __delete__ (self, instance ): instance.__dict__.pop(self.key) class X : nums=DecInt("nums" ) def __init__ (self,nums ): self.nums = nums a = X(1 ) b = X("1" )
使用描述符与MXIN方法
描述符类可以玩的更花一点,就是使用继承关系,实现MXIN。不过这样理解起来有点复杂,下面是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 class Descriptor : def __init__ (self, name = None , **opts ): self.name = name for key, value in opts.items(): setattr (self, key, value) def __set__ (self, instance, value ): instance.__dict__[self.name] = value class Typed (Descriptor ): expected_type = type (None ) def __set__ (self, instance, value ): if not isinstance (value, expected_type): raise TypeError("expected " + str (self.expected_type)) super ().__set__(instance, value) class Unsigned (Descriptor ): def __set__ (self, instance, value ): if value < 0 : raise ValueError("value must > 0" ) super ().__set__(instance, value) class MaxSized (Descriptor ): def __init__ (self, name = None , **opt ): if 'size' not in opt: raise TypeError("missing size option" ) super .__init__(name, **opt) def __set__ (self, instance, value ): if len (value) >= self.size: raise ValueError("> size" ) super .__set__(instance, value) class Integer (Typed ): expected_type = int class UnsignedInteger (Integer, Unsigned): ... class String (Typed ): expected_type = str class SizedString (String, MaxSized): ... class Stock : name = SizedString('name' , size = 8 ) shares = UnsignedInteger('shares' ) def __init__ (self, *args, **kargs ): ...
下面来解释一下流程:
首先Descriptor
只是一个简单的描述符基类,且只有set
方法
Typed
, Unsigned
,
MaxSized
也是描述符,这些二级描述符继承自Descriptor
,并重写了其set
方法,分别检测赋值时类型、长度。
Integer
, UnsignedInteger
,
String
,
SizedString
是继承上述描述符类,并基于上述限制来进行组合。由于默认没有构造函数,会使用基类的构造函数。同样也是描述符。
直接使用的描述符是Integer
,
UnsignedInteger
, String
,
SizedString
同样,也可以使用装饰器的方式来完成,且速度更快!
当然,上面这个例子不使用MXIN也可以,只使用描述符也能达成相同效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class DecType (): def __init__ (self, key, type_ ): self.key="" self.type_ = type_ def __set__ (self, instance, value ): if not isinstance (value, self.type_): raise TypeError("not type {}" .format (self.type_)) else : instance.__dict__[self.key]=value def __get__ (self, instance, owner ): return instance.__dict__[self.key] def __delete__ (self, instance ): instance.__dict__.pop(self.key) class X : nums=DecType("nums" , int ) name=DecType("names" , str ) def __init__ (self, nums, name ): self.nums = nums self.name = name a = X(1 , "1" ) b = X("1" , 1 )
通过函数注解来限制函数传入参数
上面的描述符都是限制类中的数据类型,但是检查函数的类型往往也是很常见的。
在某一次更新之后,python引入了注解语法,但是这些注解并不会限制参数类型,只是用于提示而已 :
1 2 3 4 def f (x:int , y:float ): ...
但是我们可以使用一个装饰器来利用注解信息来检查参数 :
1 2 3 4 5 6 7 8 9 10 11 12 13 def check (func ): def inner (*args ): check_list = func.__annotations__ for arg, type_ in zip (args,check_list.values()): if not isinstance (arg, type_): raise TypeError(f"{arg} not {type_} " ) return inner @check def f (x:int , y:float ): ...
上述代码主要是通过__annotations__
来获取注解:
1 2 3 4 5 6 7 def f (x:int , y:float ): ... print (f.__annotations__)
值得一提的是,FastAPI也是使用类似的方式来检查参数类型。
总结
检查类中的参数类型可以使用描述符、描述符MXIN的方式
检查函数传入的参数可以通过__annotations__
方法+装饰器来实现