Warning:本篇为基础学习,由于笔者也在学习中,所以文章过于啰嗦,想直接了解type 函数的用法,推荐“IT技术随笔”的另一篇比较简洁直观的文章:https://www.cnblogs.com/wushuaishuai/p/7739728.html
当我们拿到一个变量(对象的引用)时,如何知道这个对象是什么类型,有哪些方法呢?
基本上入门级最开始使用的就是type 函数,针对于刚学习的小小白,我们在根据上文举几个例子,如:
1 num1 = 123 2 str1 = 'hello' 3 noneobj = None 4 print(type(num1)) 5 print(type(str1)) 6 print(type(noneobj)) 7 8 <class 'int'> 9 <class 'str'> 10 <class 'NoneType'>
当然,我们不使用变量,直接对数据对象本身进行type ,如type(123),type('hello'),type(None),结果是一样的。
对于一些内置变量,比如abs 函数,max 函数, len 函数,也都可以使用type 去获取他们的对象类型
1 print(type(abs)) 2 print(type(max)) 3 print(type(len)) 4 5 <class 'builtin_function_or_method'> # 这一类说明是Python 内置的函数 6 <class 'builtin_function_or_method'> 7 <class 'builtin_function_or_method'>
再来看看另外这几种情况,现有另外一个模块demo01.py,里面含有一个自定义函数print01 ,如下使用type 函数:
1 import time 2 from copy import deepcopy 3 import demo01 4 def fn(): 5 pass 6 print(type(deepcopy)) 7 print(type(fn)) 8 print(type(time)) 9 print(type(demo01)) 10 print(type(demo01.print01)) 11 print(type(lambda x: x)) 12 13 14 <class 'function'> 15 <class 'function'> 16 <class 'module'> 17 <class 'module'> 18 <class 'function'> 19 <class 'function'>
由上面测试结果,我们可以分析得知,无论是我们自定义的模块demo01,还是Python 内置的模块time,使用type 函数,得到的结果,都"属于" module 这一类,
而函数方面,无论是我们在demo01.py 里面自定义的print01,或者自定义的匿名函数,还是Python 内置的函数deepcopy ,使用type 函数,得到的结果,都是"属于" function 这一类。
另外一种是我们自己定义的类(在demo02.py 中自定义一个Student 类),如下:
1 class Teacher(object): 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age 5 6 if __name__ == '__main__': 7 t1 = Teacher('Tom', 22) 8 print(type(t1)) 9 from demo02 import Student 10 s1 = Student('Jam', 11) 11 print(type(s1)) 12 13 14 <class '__main__.Teacher'> 15 <class 'demo02.Student'>
这个就没什么说的了,只是介绍一种情况,分别是两个实例对象所属的类,一个是当前模块"__main__"的Teacher 类,另一个是引用模块"demo02" 的Student 类。
最后还有一种特殊的情况,比如int,list,set 等这些Python 的强转类型函数,或者是自定义的类,又或者是object 这个Python3 所有类的基类,又或者是type 这个函数本身呢,比如int,list,set 这些内置的数据类型类,或者是自定义的一个类,又或者是object 这个Python3 所有类的基类,又或者是type 这个类(注意,这个type 类和type 函数不是同一个"东西"),他们的对象类型是什么呢?
1 class A(object): 2 pass 3 4 print(type(A)) 5 print(type(int)) 6 print(type(list)) 7 print(type(set)) 8 print(type(object)) 9 print(type(type)) 10 11 <class 'type'> 12 <class 'type'> 13 <class 'type'> 14 <class 'type'> 15 <class 'type'> 16 <class 'type'>
由此可知,这些都"属于" type 这一类,那么type 这一类是什么呢?请看扩展2
type 函数返回值
根据参数传入的不同,返回不同的结果,上文中我们运用到的是传入一个参数,返回值是这个参数的对象类型。
至于原理,网上很多的资料,都说都是调用了这个对象的__class__方法,我目前只知道他们的结果确实是一样的,源码中也并没有说明,也可能是我没看懂,后续如果学到了,或者有知道的朋友可以在下方评论留言,谢谢。
1 ... 2 type(object_or_name, bases, dict) 3 type(object) -> the object's type 4 type(name, bases, dict) -> a new type 5 ...
乌龙事件:说好的万物皆是对象呢?整数"123"没有__class__魔法方法???
测试中产生一个疑问,对于Python 来说,万物皆是对象,但是当我们去调用一个整数数值的__class__方法时,会被报语法错误,难道整数数值就不是对象了?个人认为可能是解释器认为"."后面的应该是小数,那该怎么解决?(经过大佬的提醒,已解决,这么偏门也会想到,毕竟这门语言的年龄跟我一般大,暴露年龄)
一度怀疑这是个我新发现的BUG, 因为测试别的类型都没问题
1 a = 123 2 print(a.__class__) 3 # print(123.__class__) 4 print(123.11.__class__) 5 print((1,).__class__) 6 print(None.__class__) 7 8 9 10 <class 'int'> 11 <class 'float'> 12 <class 'tuple'> 13 <class 'NoneType'>
一度兴奋到要给龟叔发邮件什么的,但是经过大佬一提醒:加个括号!想起来元祖中只有一个元素的时候,要加“,” 的原因
1 print((123).__class__) 2 3 4 5 <class 'int'>
好吧,龟叔牛X(破音),大佬牛X(破音),咳咳,跑题了,不过经过上面的测试也明白,这个type 函数,可能就是调用对象的"__class__" 方法(懂了后就把"可能"去掉)
言归正传,计算机的所做,大致就是对于一件事情,进行判断,然后根据给的条件,执行相对应的操作,所以平常我们码代码的时候,if 判断用的肯定是比较多的,那么怎么对type 函数的返回值进行if 判断呢?
从最简单的,比如说一个变量,引用的是一个整数,但是我们不确定,那么该做什么判断呢?
1 a = 123 2 if type(a) == int: 3 print('a is int') 4 else: 5 print('a is not int') 6 7 8 9 a is int
小数,字符串,列表等等 都可以直接去进行类似的判断,那么另外一种情况呢,假如有一个变量名fn, 我们不确定他是否是一个函数名,该怎么判断呢?
Python 内置的types 模块提供了三个常量量,供我们去判断一个对象是不是函数对象,分别是:FunctionType,BuiltinFunctionType,LambdaType
FunctionType
对于一些我们自己定义函数,或者是在别的模块中导入的函数(无论是我们自己的"demo02" 模块,还是Python 的"copy" 模块),他们判断时所对应的常量都是FunctionType
1 import types 2 import demo02 3 from copy import deepcopy 4 5 def func(): 6 pass 7 8 print(type(func) == types.FunctionType) 9 print(type(demo02.fn) == types.FunctionType) 10 print(type(deepcopy) == types.FunctionType) 11 12 True 13 True 14 True
BuiltinFunctionType
而对于一些像"abs", "max", "min" 的函数,他们对应的常量 都是BuiltinFunctionType,事实上还有一个BuiltinMethodType 常量名,只不过是BuiltinFunctionType 另一个别名罢了,不知道是用做什么,但我们最好还是使用BuiltinFunctionType,这样可以达到见字知意。
print(type(abs) == types.BuiltinFunctionType) print(type(max) == types.BuiltinMethodType) print(type(min) == types.BuiltinFunctionType) print(type(min) == types.BuiltinMethodType) True True True True
LambdaType
最后再来看看LambdaType ,从字面上看就知道这是个关于匿名函数的常量名,看下面的小例子:
1 f_la = lambda x: x 2 3 print(type(f_la) == types.FunctionType) 4 print(type(f_la) == types.LambdaType) 5 6 True 7 True
由上面的结果可以得出,匿名函数既是FunctionType 类型的,又是 LambdaType 类型的,这个本来就没问题,匿名函数本来就是函数的一个分支嘛~
说完了一个参数的,我们再来说说另外一个至少是我比较少用的用法,传入三个参数
光标在type 函数后面的括号里面按住"ctrl + P"
需要传入一个"str" 类型的name,类 的名称,
需要传入一个"tuple" 类型的bases,前面传入name 实参的基类的元祖,考虑多继承
需要传入一个"dict" 类型的dict,为一个字典,包含类定义内的命名空间变量,看下面的例子
1 class People(object): 2 country = 'China' 3 4 t1 = type('Teacher', (People,), {'name': 'xiaoming', 'age': 30}) 5 print(t1.name) 6 print(t1.age) 7 print(t1.country) 8 9 10 xiaoming 11 30 12 China
扩展
1.说完这些后,我们再看看IDE 给我们报的警告,以最后一个LambdaType 的例子为例
大致意思就是不建议使用"==" 对类型进行比较,推荐使用isinstance 函数,那么使用isinstance 函数,有什么好处呢?或者说对比type 函数,进行判断的时候,有什么不同呢?
我先直接说出答案,然后再具体举例子说明:
type 函数不认为子类是父类类型的一种,即不考虑继承关系
isinstance 函数,认为字类是父类类型的一种,即考虑继承关系
所以如果判断两个对象类型是否相等,PEP8 规范推荐使用isinstance 函数,具体代码如下:
class People(object): pass class Teacher(People): pass if __name__ == '__main__': p1 = People() t1 = Teacher() print(isinstance(t1, People)) print(type(t1) == People) print(isinstance(t1, Teacher)) print(type(t1) == Teacher) True False True True
2.相信很多初学者都跟我一样很好奇,type、object、class 的关系(注意:这里所说的type ,并不是上文介绍的type 函数,而是一个类),先看下面这个图
如果抛开type 这个类,我们很多初学者都明白其中的关系:
①.object 是所有类的基类,比如list,str,dict,tuple 等等,都是继承object 类
②.字符串"abc" 是str 类的一个实例对象
那么让我们来想想Python 的口号:在Python 中,万物都是对象!那么也就是说,list,str,dict,tuple,甚至是object 这些类,也都是实例对象了,那么问题来了,谁是他们的类呢?绕口点说就是谁是Python 中所有类的类呢?没错,就是type 了,这个神奇的类,是所有Python 中类的类,哈哈,被绕晕了吗?请看下面我写的一个伪代码,注意,是伪代码,仅做学习了解用:
1 class type(object): 2 pass 3 4 int = type() # int 为Python 中的整型类 5 list = type() # list 为Python 中的列表类 6 str = type() # str 为Python 中的字符串类 7 ... 8 # 当你自定义了一个类,比如Teacher ,那么肯定会有下面这一步伪代码的 9 Teacher = type() 10
这样虽然不怎么合适,但我感觉还算比较好理解他们的关系,如果有更好的解释方法,欢迎在下面留言~
也就是说,当你创建一个字符串对象,其实在Python 中的步骤是先由type 这个类,实例化一个对象 str 类,然后再由str 类去实例化一个字符串对象,如:"hello world",其他类型也是同样的步骤。
那么它又是谁的实例对象呢?看图应该可以看出来,它是它自己的实例对象。是不是感觉很不可思议?这就是Python 的神奇之处了,可能别的语言也又类似的设计,不甚了解。
type = type()
在这里再做一个小扩展,之前刚接触object 的时候,在了解到它是所有类的基类的时候,就曾经好奇过,它的父类是谁?学习完这个type 类,见识了Python 中的这种操作后,我想,object 一定还是继承他自己吧,毕竟type 类都能实例化它自己,如果你想的和我一样的话,那么只能说很遗憾,我们都错了。。
这里介绍两个魔法方法,__class__ 和__bases__
在上面的乌龙事件(说好的万物皆对象呢?整数"123"没有__class__魔法方法??)中已经说了,Python 万物皆是对象,那么所有的对象都是经过类实例化出来的,那么所有的对象都会由__class__这个方法,调用该方法,能查看到该对象是由谁实例化出来的,例子往上看吧
这里重点说以下__bases__ 魔法方法,也就是验证object 类的父类是自身还是别的类的一种魔法方法,从字面意思上看,是查看一个对象的基类,事实上也就是如此,所以这个是只有类对象,才能调用的魔法方法,如果你不信邪的去使用非类对象调用:
1 print('hello'.__bases__) 2 3 4 5 6 Traceback (most recent call last): 7 ... 8 AttributeError: 'str' object has no attribute '__bases__'
抱歉,只能报错提醒你了,换成别的非类对象,如 "123",也只是把报错信息从‘str’ object has no attribute ‘__bases__’ 换成 ‘int’ object has no attribute ‘__bases__’ 而已
当然,使用类去调用这个魔法方法,完全没有任何问题
1 class People(object): 2 pass 3 4 print(int.__bases__) 5 print(People.__bases__) 6 print(type.__bases__) 7 print(object.__bases__) 8 9 10 (<class 'object'>,) 11 (<class 'object'>,) 12 (<class 'object'>,) 13 ()
没错,你没有看错,object 的基类是个空,而无论是Python 内置的int 类,还是我们自己定义的People 类,又或者是type 这个特殊的类,他们的基类都是object ,这就验证了那句话,object 是所有类的基类!!!