[python]類型與對象
程序中所存儲的所有數據都是對象。每個對象都有一個身份、一個類型和一個值。對象的身份可以看作是指向它在內存中所處位置的指針,變量名就是引用這個具體位置的名稱。
對象的類型也稱作類別,用於描述對象的內部表示及它支持的方法與操作。創建特定類型的對象時,有時也將該對象稱為該類型的實例。實例被創建之後,它的身份和類型就不可改變。如果對象的值是可以修改的,稱為可變對象,反之稱為不變對象。如果某個對象包含對其他對象的引用,則將其稱為容器或集合。
大多數對象擁有大量特有的數據屬性和方法。屬性就是與對象相關的值。方法就是被調用時將在對象上執行某些操作的函數。使用點"."運算符可以訪問屬性和方法。
2. 對象的身份與類型
內置函數id()可返回一個對象的身份,返回值為整數。is運算符用於比較兩個對象的身份。內置函數type()則返回一個對象的類型。例如:
def compare(a, b):
if a is b:
# 同一個對象
if a == b:
# 具有相同的值
if type(a) is type(b):
# 具有相同類型
對象的類型本身也是一個對象,稱為對象的類。所有類型對象都有一個指定的名稱,可用於執行類型檢查,例如:
if type(s) is list: s.append(item) if type(d) is dict: d.update(t)
檢查類型的更佳方式是用內置函數isinstance(object, type),例如:
if isinstance(s, list):
s.append(item)
if isinstance(d, dict):
d.update(t)
因為isinstance()函數能夠實現繼承,因此是檢查所有Python對象類型的首選方式。
3. 引用計數與垃圾收集
所有對象都有引用計數。無論是給對象分配一個新名稱,還是將其放入一個容器,該對象的引用計數就會增加,例如:
a = 37 # 創建值為37的對象 b = a # 增加37的引用計數 c = [] c.append(b) #增加37的引用計數
這個例子創建了一個包含值37的對象,a只是引用這個新創建對象的一個名稱,將a賦值給b時,b就成了同一對象的新名稱,而且該對象的引用計數會增加。類似地,將b放到一個列表中時,該對象的引用計數將再次增加。
使用del語句或者引用超出作用域時(或者被重新賦值),對象的引用計數就會減少,例如:
del a # 減少37的引用計數
b = 42 #減少37的引用計數
c[0] = 2.0 #減少37的引用計數
使用sys.getrefcount()函數可以獲得對象的當前引用計數,例如:
a = 37
import sys
print(sys.getrefcount(a))
多數情況下,引用計數比猜測的要大得多,對於不可變數據(如數字和字符串),解釋器會主動在程序的不同部分共享對象,以便節約內存。
當一個對象的引用計數歸零時,它將被垃圾收集機制處理掉。在某些情況下,很多已不再使用的對象間可能存在循環依賴關系,例如:
a = {}
b = {}
a[‘b‘] = b
b[‘a‘] = a
del a
del b
在以上例子中,del語句將會減少a和b的引用計數,並銷毀用於引用底層對象的名稱。然而因為每個對象都包含一個對其他對象的引用,所以引用計數不會歸零,對象也不會被銷毀,從而導致內存泄露。為了解決這個問題,解釋器會定期執行一個循環檢測器,搜索不可訪問對象的循環並刪除它們。
4. 引用與復制
在程序進行像a = b這樣的賦值時,就會創建一個對b的引用。對於像數字和字符串這樣的不可變對象,這種賦值實際上創建了b的一個副本。然而,對於可變對象(如列表和字典)引用行為會完全不同,例如:
a = [1, 2, 3, 4]
b = a
print(b is a) # True
b[2] = -100
print(a[2]) #-100
因為a和b引用的同一個對象,修改其中任意一個變量都會影響到另一個。所以必須創建對象的副本而不是新的引用。對於像列表和字典這樣的容器對象,可以使用兩種復制操作: 淺復制和深復制。淺復制將創建一個新對象,但它包含的是對原始對象中包含的項的引用,例如:
a = [1, 2, [3, 4]]
b = list(a)
print(b is a) #False
b.append(100)
print(b) # [1, 2, [3, 4], 100]
print(a) # [1, 2, [3, 4]]
b[2][0] = -100
print(b) # [1, 2, [-100, 4], 100]
print(a) # [1, 2, [-100, 4]]
深復制將創建一個新對象,並且遞歸地復制它包含的所有對象。可以使用標準庫中的copy.deepcopy()函數完成該工作,例如:
import copy
a = [1, 2, [3, 4]]
b = copy.deepcopy(a)
b[2][0] = -100
print(b) # [1, 2, [-100, 4]]
print(a) # [1, 2, [3, 4]]
5. 表示數據的內置類型
大約有12種數據類型可用於表示程序中用到的大多數數據。如下表所示:
類型分類 | 類型名稱 | 描述 |
---|---|---|
None | Type(None) | null對象None |
數字 | int | 整數 |
數字 | float | 浮點數 |
數字 | complex | 復數 |
數字 | bool | 布爾值 |
序列 | str | 字符串 |
序列 | list | 列表 |
序列 | tuple | 元組 |
序列 | range | 創建的整數範圍 |
映射 | range | 創建的整數範圍 |
集合 | set | 可變集合 |
集合 | frozenset | 不可變集合 |
None類型表示一個沒有值的對象,在程序中表示為None。如果一個函數沒顯示返回值,則返回該對象。None常用於可選參數的默認值,以便讓函數檢測調用者是否為該參數實際傳遞了值。
Python使用4種數字類型:布爾型、整數、浮點數以及復數。除了布爾值,所有數字對象都是有符號的。所有數字類型都不可變。數字類型擁有大量的屬性和方法,可以簡化涉及混合算術的運算。為了與有理數兼容,整數使用了屬性x.numerator和x.denominator。為了兼容復數,整數或浮點數y擁有屬性y.real和y.imag,以及方法y.conjugate()。使用y.as_interger_ratio()可將浮點數y轉換為分數形式的一對整數。方法y.is_interger()用於測試浮點數y是否表示整數值。通過方法y.hex()和y.fromhex()可用低級二進制形式使用浮點數。
序列表示索引為非負整數的有序對象集合,包括字符串、列表和元組。所有序列支持的方法如下表:
項目 | 描述 |
---|---|
s[i] | 返回一個序列的元素i |
s[i:j] | 返回一個切片 |
s[i:j:stride] | 返回一個擴展切片 |
lens(s) | s中的元素數 |
min(s) | s中的最小值 |
max(s) | s中的最大值 |
sum(s [, initial]) | s中各項的和 |
all(s) | 檢查s中的所有項是否為True |
any(s) | 檢查s中的任意項是否為True |
適用於可變序列的方法如下表:
項目 | 描述 |
---|---|
s[i] = v | 項目賦值 |
s[i:j] = t | 切片賦值 |
s[i:j:stride] = t | 擴展切片賦值 |
del s[i] | 項目刪除 |
del s[i:j] | 切片刪除 |
del s[i:j:stride] | 擴展切片刪除 |
列表支持的方法如下表:
方法 | 描述 |
---|---|
list(s) | 將s轉換為一個列表 |
s.append(x) | 將一個新元素x追加到s末尾 |
s.extend(x) | 將一個新列表追加到s末尾 |
s.count(x) | 計算s中x的出現次數 |
s.index(x [, start [, stop]]) | 找到x首次出現的位置 |
s.insert(i, x) | 在索引i處插入x |
s.pop([i]) | 返回元素i並從列表中移除它,省略i則返回列表中最後一個元素 |
s.remove(x) | 搜索x並從s中移除它 |
s.reverse() | 顛倒s中的所有元素的順序 |
s.sort([key [, reverse]]) | 對s中的所有元素進行排序。key是一個鍵函數。reverse表明以倒序對列表進行排序 |
list(s)可將任意可叠代類型轉換為列表。如果s已經是列表,則該函數構造的新列表是s的一個淺復制。
字符串支持的方法如下表:
方法 | 描述 |
---|---|
s.captitalize() | 首字符變大寫 |
s.center(width [, pad]) | 在長度為width的字段內將字符串居中。pad是填充字符 |
s.count(sub [, start [, end]]) | 計算指定子字符串sub的出現次數 |
s.decode([encoding [, errors]]) | 解碼一個字符串並返回一個Unicode字符串 |
s.encdoe([encoding [, errors]]) | 返回字符串的編碼版本 |
s.endswith(suffix [, start [, end]]) | 檢查字符串是否以suffix結尾 |
s.expandtabs([tabsize]) | 使用空格替換制表符 |
s.find(sub [, start [, end]]) | 找到指定子字符串sub首次出現的位置,否則返回-1 |
s.format(args, *kwargs) | 格式化s |
s.index(sub [, start [, end]]) | 指到指定子字符串sub首次出現的位置,否則報錯 |
s.isalnum() | 檢查所有字符是否都為字母或數字 |
s.isalpha() | 檢查所有字符是否都為字母 |
s.isdigit() | 檢查所有字符是否都為數字 |
s.islower() | 檢查所有字符是否都為小寫 |
s.isspace() | 檢查所有字符是否都為空白 |
s.istitle() | 檢查字符串是否為標題字符串(每個單詞首字母大寫) |
s.isupper() | 檢查所有字符是否都為大寫 |
s.join(t) | 使用s作為分隔符連接序列t中的字符串 |
s.ljust(width [, fill]) | 在長度為width的字符串內左對齊s |
s.lower() | 轉換為小寫形式 |
s.lstrip([chrs]) | 刪掉chrs前面的空白或字符 |
s.partition(sep) | 使用分隔符字符串sep劃分一個字符串。返回一個元組(head, sep, tail) |
s.replace(old, new [, maxreplace]) | 替換一個子字符串 |
s.rfind(sub [, start [, end]]) | 找到一個子字符串最後一次出現的位置 |
s.rindex(sub [, start [, end]]) | 找到一個子字符串最後一次出現的位置,否則報錯 |
s.rjust(width [, fill]) | 在長度為width的字符串內右對齊s |
s.rpartition(sep) | 使用分隔符sep劃分字符串,但是從字符串的結尾處開始搜索 |
s.rsplit([sep [, maxsplit]]) | 使用sep作為分隔符對一個字符串從後往前進行劃分。maxsplit是最大劃分次數 |
s.rstrip([chrs]) | 刪掉chrs尾部的空白或字符 |
s.split([sep [, maxsplit]]) | 使用sep作為分隔符對一個字符串進行劃分。maxsplit是劃分的最大次數 |
s.splitlines([keepends]) | 將字符串分為一個行列表。如果keepends為1,則保留各行最後的換行符 |
s.startswith(prefix [, start [, end]]) | 檢查一個字符串是否以prefix開頭 |
s.strip([chrs]) | 刪掉chrs開頭和結尾的空白或字符 |
s.swapcase() | 將大寫轉換為小寫,或者相反 |
s.title() | 將字符串轉換為標題格式 |
s.translate(table [, deletechars]) | 使用一個字符轉換表table轉換字符串,刪除deletechars中的字符 |
s.upper() | 將一個字符串轉換為大寫形式 |
s.zfill(width) | 在字符串的左邊填充0,直至其寬度為width |
很多字符串方法都接受可選的start和end參數,其值為整數,用於指定s中起始和結束位置的索引。大多數情況下,這些值可以為負值,表示索引是從字符串結尾處開始計算的。
映射類型表示一個任意對象的集合,而且可以通過另一個幾乎是任意鍵值的集合進行索引。和序列不同,映射對象是無序的,可以通過數字、字符串和其他對象進行索引。映射是可變的。
字典是唯一內置的映射類型,任何不可變對象都可以用作字典鍵值,如字符串、數字、元組等。字典的方法如下表:
項目 | 描述 |
---|---|
len(m) | 返回m中的項目數 |
m[k] | 返回m中鍵k的項 |
m[k] = x | 將m[k]的值設為x |
del m[k] | 從m中刪除m[k] |
k in m | 如果k是m中的鍵,則返回True |
m.clear() | 刪除m中的所有項目 |
m.copy() | 返回m的一個副本 |
m.fromkeys(s [, value]) | 創建一個新字典並將序列s中的所有元素作為新字典的鍵,這些鍵的值均為value |
m.get(k [, v]) | 返回m[k],如果找不到m[k],則返回v |
m.items() | 返回由(key, value)對組成的一個序列 |
m.keys() | 返回鍵值組成的一個序列 |
m.pop(k [, default]) | 如果找到m[k],則返回m[k]並從m中刪除,否則返回default的值 |
m.popitem() | 從m中刪除一個隨機的(key, value)對,並把它返回為一個元組 |
m.setdefault(k [, v]) | 如果找到m[k],則返回m[k],不則返回v,並將m[k]的值設為v |
m.update(b) | 將b中的所有對象添加到m中 |
m.values() | 返回m中所有值的一個序列 |
集合是唯一的無序集。與序列不同,集合不提供索引或切片操作。它們和字典也有所區別,即對象不存在相關的鍵值。放入集合的項目必須是不可變的。集合分為兩種類型,set是可變的集合,而frozenset是不可變的集合,這兩類集合都是用一對內置函數創建的,例如:
s = set([1, 5, 10, 15])
f = frozenset([‘a‘, 37, ‘hello‘])
所有集合支持的方法如下表:
項目 | 描述 |
---|---|
len(s) | 返回s中項目數 |
s.copy() | 制作s的一份副本 |
s.difference(t) | 求差集。返回所有要s中,但不在t中的項目 |
s.intersection(t) | 求交集。返回所有同時在s和t中的項目 |
s.isdisjoint(t) | 如果s和t沒有相同項,則返回True |
s.issubset(t) | 如果s是t的一個子集,則返回True |
s.issuperset(t) | 如果s是t的一個超集,則返回True |
s.symmetric_difference(t) | 求對稱差集。返回所有在s或t中,但又不同時在這兩個集合中的項 |
s.union(t) | 求並集。返回所有在s或t中的項 |
可變集合還另外提供了一些方法,如下表:
項目 | 描述 |
---|---|
s.add(item) | 將item添加到s中。如果item已經在s中,則無任何效果 |
s.clear() | 刪除s中的所有項 |
s.difference_update(t) | 從s中刪除同時也在t中的所有項 |
s.discard(item) | 從s中刪除item,如果item不要s的成員,則無任何效果 |
s.intersection_update(t) | 計算s與t的交集,並將結果放入s |
s.pop() | 返回一個任意的集合元素,並將其從s中刪除 |
s.remove(item) | 從s中刪除item,如果item不是s的成員,引發異常 |
s.symmetric_difference_update(t) | 計算s與t的對稱差集,並將結果放入s |
s.update(t) | 將t中的所有項添加到s中 |
所有的這些操作都可以直接修改集合s。
6. 表示程序結構的內置類型
在Python中,函數、類和模塊都可以當做數據操作的對象,如下表:
類型分類 | 類型名稱 | 描述 |
---|---|---|
可調用 | types.BuiltinFunctionType | 內置函數或方法 |
可調用 | type | 內置類型和類的類型 |
可調用 | object | 所有類型和類的祖先 |
可調用 | types.FunctionType | 用戶定義的函數 |
可調用 | types.MethodType | 類方法 |
模塊 | types.ModuleType | 模塊 |
類 | object | 所有類型和類的祖先 |
類型 | type | 內置類型和類的類型 |
可調用類型表示支持函數調用操作的對象。具有這種屬性的對象有:用戶定義的函數,方法、內置函數與方法,可調用的類與實例。
用戶定義的函數是指用def語句或lambda運算符在模塊級別上創建的可調用對象,它具有以下屬性:
屬性 | 描述 |
---|---|
f._doc_ | 文檔字符串 |
f._name_ | 函數名稱 |
f._dict_ | 包含函數屬性的字典 |
f._code_ | 字節編譯的代碼 |
f._defaults_ | 包含默認參數的元組 |
f._globals_ | 定義全局命名空間的字典 |
f._closure_ | 包含與嵌套作用域相關數據的元組 |
方法是在類定義中定義的函數。有3種常見的方法:實例方法、類方法和靜態方法。實例方法是操作指定類的實例的方法,實例作為第一個參數傳遞給方法,根據約定該參數一般稱為self。類方法把類本身當作一個對象進行操作,在第一個參數中將類對象傳遞給類。靜態方法就是打包在類中的函數,它不能使用一個實例或類對象作為第一個參數。例如:
f = Foo()
meth = f.instance_method
meth(30)
在以上例子中,meth稱為綁定方法。綁定方法是可調用對象,它封裝了函數和一個相關實例。調用綁定方法時,實例就會作為第一個參數(self)傳遞給方法。方法查找也可以出現類本身上,例如:
umeth = Foo.instance_method
umeth(f, 30)
在以下例子中,umeth稱為非綁定方法。非綁定方法是封裝了方法函數的可調用對象,但需要傳遞一個正確類型的實例作為第一個參數。如果傳遞的對象類型錯誤,就會引發TypeError異常。
為方法對象定義的屬性如下表:
屬性 | 描述 |
---|---|
m._doc_ | 文檔字符串 |
m._name_ | 方法名稱 |
m._class_ | 定義該方法的類 |
m._func_ | 實現方法的函數對象 |
m._self_ | 與方法相關的實例(如果是非綁定方法則為None) |
類對象和實例也可以當作可調用對象進行操作。類對象使用class語句創建,並作為函數調用,以創建新實例。在這種情況下,將函數的參數傳遞給類的_init_()方法,以便初始化新創建的實例。如果實例定義了一個特殊方法_call_(),它就能夠模擬函數的行為。如果該方法是為某個實例x而定義,使用x(args)語句等同於調用方法x._call_(args)。
定義類時,類定義通常會生成一個type類型的對象,一個類型對象t的常用屬性如下表:
屬性 | 描述 |
---|---|
t._doc_ | 文檔字符串 |
t._name_ | 類名稱 |
t._bases_ | 基類的元組 |
t._dict_ | 保存類方法和變量的字典 |
t._module_ | 定義類的模塊名稱 |
t._abstractmethods_ | 抽象方法名稱的集合 |
創建一個對象實例時,實例的類型就是定義它的類,例如:
f = Foo()
print(type(f)) # <class ‘__main__.Foo‘>
下表顯示實例擁有的特殊屬性:
屬性 | 描述 |
---|---|
t._class_ | 實例所屬的類 |
t._dict_ | 保存實例數據的字典 |
模塊類型是一個容器,可保存使用import語句加載的對象。模塊定義了一個使用字典實現的命名空間,比如,m.x=y等價於m._dic_["x"]=y。模塊的可用屬性如下:
屬性 | 描述 |
---|---|
m._dict_ | 與模塊相關的字典 |
m._doc_ | 模塊文檔字符串 |
m._name_ | 模塊名稱 |
m._file_ | 用於加載模塊的文件 |
m._path_ | 完全限定包名,只在模塊對象引用包時定義 |
[python]類型與對象