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 是所有類的基類!!!