[Python]類型與對象

1. 術語

程序中所存儲的所有數據都是對象。每個對象都有一個身份、一個類型和一個值。對象的身份可以看作是指向它在內存中所處位置的指針,變量名就是引用這個具體位置的名稱。
對象的類型也稱作類別,用於描述對象的內部表示及它支持的方法與操作。創建特定類型的對象時,有時也將該對象稱爲該類型的實例。實例被創建之後,它的身份和類型就不可改變。如果對象的值是可以修改的,稱爲可變對象,反之稱爲不變對象。如果某個對象包含對其他對象的引用,則將其稱爲容器或集合。
大多數對象擁有大量特有的數據屬性和方法。屬性就是與對象相關的值。方法就是被調用時將在對象上執行某些操作的函數。使用點"."運算符可以訪問屬性和方法。
 

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__ 完全限定包名,只在模塊對象引用包時定義
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章