> Photo by Tianyi Ma on Unsplash.
啟動任何Python解釋器時,都有70多個内置函數可用。每個Python學習者都不應不熟悉一些普通的學習者。例如,我們可以使用len()來擷取對象的長度,例如清單或字典中的項目數。再舉一個例子,我們可以使用print()列印出感興趣的對象,以進行學習和調試。
此外,幾乎所有Python程式員都應該在教程中看到内置的id()函數的使用,以用于指導特定的Python概念。但是,據我所知,這些資訊是分散的。在本文中,我想對使用id()函數了解六個關鍵Python概念進行系統的回顧。
1.一切都是Python中的對象
作為一種流行的面向對象的程式設計語言,Python在其實作中随處使用對象。例如,諸如整數,浮點數,字元串,清單和字典之類的内置資料類型都是對象。而且,函數,類甚至子產品也被用作對象。
根據定義,id()函數接受一個對象并傳回該對象的辨別,即以整數表示的記憶體位址。是以,我們可以使用此函數來證明Python中的所有對象都是真實的。
>>> import sys
>>> class Foo:
... pass
...
>>> def foo():
... pass
...
>>> a_tuple = ('Error', 404)
>>> a_dict = {'error_code': 404}
>>> a_list = [1, 2, 3]
>>> a_set = set([2, 3, 5])
>>> objects = [2, 2.2, 'hello', a_tuple, a_dict, a_list, a_set, Foo, foo, sys]
>>>
>>> for item in objects:
... print(f'{type(item)} with id: {id(item)}')
...
with id: 4479354032 with id: 4481286448 with id: 4483233904 with id: 4483061152 with id: 4483236000 with id: 4483236720 with id: 4483128688 with id: 140235151304256 with id: 4483031840 with id: 4480703856
在上面的代碼片段中,您可以看到對象清單中的每個項目都可以在id()函數中使用,該函數顯示每個對象的記憶體位址。
我認為很有趣的以下操作是,作為函數本身,id()函數也應具有其記憶體位址。
>>> print(f'{type(id)} with id: {id(id)}')
with id: 4480774224
2.變量配置設定和别名
在Python中建立變量時,通常使用以下文法:
var_name = the_object
此過程基本上将在記憶體中建立的對象綁定到特定的變量名稱。如果為變量配置設定另一個變量,例如var_name1 = var_name,會發生什麼?
考慮以下示例。在下面的代碼片段中,我們首先建立了一個名為hello的變量,并為其配置設定了字元串值。接下來,我們通過配置設定之前的變量hello建立了另一個名為world的變量。當我們列印出他們的記憶體位址時,我們發現hello和world都具有相同的記憶體位址,這表明它們是記憶體中的同一對象。
>>> hello = 'Hello World!'
>>> print(f'{hello} from: {id(hello)}')
Hello World! from: 4341735856
>>> world = hello
>>> print(f'{world} from: {id(world)}')
Hello World! from: 4341735856
>>>
>>> bored = {'a': 0, 'b': 1}
>>> print(f'{bored} from: {id(bored)}')
{'a': 0, 'b': 1} from: 4341577200
>>> more_bored = bored
>>> print(f'{more_bored} from: {id(more_bored)}')
{'a': 0, 'b': 1} from: 4341577200
>>> more_bored['c'] = 2
>>> bored
{'a': 0, 'b': 1, 'c': 2}
>>> more_bored
{'a': 0, 'b': 1, 'c': 2}
在這種情況下,變量世界通常稱為變量hello的别名,通過配置設定現有變量來建立新變量的過程可以稱為别名。在其他程式設計語言中,别名非常類似于與記憶體中基礎對象有關的指針或引用。
在上面的代碼中,我們還可以看到,當我們為字典建立别名并修改别名的資料時,該修改也将應用于原始變量,因為在背景,我們修改了記憶體中的同一字典對象。
3.比較運算符:== vs. is
在各種情況下,我們需要比較兩個對象作為決策點,以便在滿足或不滿足特定條件時應用不同的功能。就相等比較而言,我們可以使用兩個比較運算符:==和is。一些新的Python學習者可能會錯誤地認為它們是相同的,但是有細微差别。
考慮以下示例。我們建立了兩個相同項目的清單。當我們使用==運算符比較兩個清單時,比較結果為True。當我們使用is運算符比較兩個清單時,比較結果為False。他們為什麼産生不同的結果?這是因為==運算符會比較值,而is運算符會比較辨別(即記憶體位址)。
正如您所期望的,這些變量引用了記憶體中的同一對象,它們不僅具有相同的值,而且具有相同的辨別。這導緻==和is運算符的評估結果相同,如下面涉及str0和str1的示例所示:
>>> list0 = [1, 2, 3, 4]
>>> list1 = [1, 2, 3, 4]
>>> print(f'list0 == list1: {list0 == list1}')
list0 == list1: True
>>> print(f'list0 is list1: {list0 is list1}')
list0 is list1: False
>>> print(f'list0 id: {id(list0)}')
list0 id: 4341753408
>>> print(f'list1 id: {id(list1)}')
list1 id: 4341884240
>>>
>>> str0 = 'Hello'
>>> str1 = str0
>>> print(f'str0 == str1: {str0 == str1}')
str0 == str1: True
>>> print(f'str0 is str1: {str0 is str1}')
str0 is str1: True
>>> print(f'str0 id: {id(str0)}')
str0 id: 4341981808
>>> print(f'str1 id: {id(str1)}')
str1 id: 4341981808
4.整數緩存
我們在程式設計中經常使用的一組資料是整數。在Python中,解釋器通常會緩存介于-5到256之間的小整數。這意味着在啟動Python解釋器時,這些整數将被建立并可供以後在記憶體中使用。以下代碼片段顯示了此功能:
>>> number_range = range(-10, 265)
>>> id_counters = {x: 0 for x in number_range}
>>> id_records = {x: 0 for x in number_range}
>>>
>>> for _ in range(1000):
... for number in number_range:
... id_number = id(number)
... if id_records[number] != id_number:
... id_records[number] = id_number
... id_counters[number] += 1
...
>>> [x for x in id_counters.keys() if id_counters[x] > 1]
[-10, -9, -8, -7, -6, 257, 258, 259, 260, 261, 262, 263, 264]
在上面的代碼中,我建立了兩個字典,其中id_counters跟蹤每個整數的唯一辨別的計數,而id_records跟蹤整數的最新辨別。對于介于-10到265之間的整數,如果新整數的辨別與現有整數不同,則相應的計數器将遞增1。我重複了這個過程1000次。
代碼的最後一行使用清單推導技術向您顯示具有多個同一性的整數。顯然,經過1000次後,從-5到256的整數對于每個整數僅具有一個辨別,如上一段所述。要了解有關Python清單了解的更多資訊,您可以參考我以前關于此的文章:
5.淺層和深層副本
有時,我們需要制作現有對象的副本,以便我們可以更改一個副本而不更改另一個副本。内置的複制子產品為此提供了兩種方法:copy()和deepcopy(),它們分别進行淺拷貝和深拷貝。如果您不知道它們是什麼,讓我們利用id()函數來了解這兩個概念。
>>> import copy
>>> original = [[0, 1], 2, 3]
>>> print(f'{original} id: {id(original)}, embeded list id: {id(original[0])}')
[[0, 1], 2, 3] id: 4342107584, embeded list id: 4342106784
>>> copy0 = copy.copy(original)
>>> print(f'{copy0} id: {id(copy0)}, embeded list id: {id(copy0[0])}')
[[0, 1], 2, 3] id: 4341939968, embeded list id: 4342106784
>>> copy1 = copy.deepcopy(original)
>>> print(f'{copy1} id: {id(copy1)}, embeded list id: {id(copy1[0])}')
[[0, 1], 2, 3] id: 4341948160, embeded list id: 4342107664
我們首先建立了一個名為original的清單變量,它由一個嵌套清單和兩個整數組成。然後,我們分别使用copy()和deepcopy()方法制作了兩個副本(copy0和copy1)。如我們所料,原始的copy0和copy1具有相同的值(即[[0,1],2,3])。但是,它們具有不同的身份,因為與别名不同,copy()和deepcopy()方法均會在記憶體中建立新對象,進而使新副本具有不同的身份。
淺層副本和深層副本之間最本質的差別是,深層複制将為原始複合對象遞歸建立副本,而淺層複制将在适用的情況下保留對現有對象的引用。在上面顯示的示例中,變量original實際上是一個複合對象(即一個清單嵌套在另一個清單中)。
在這種情況下,使用copy()方法,變量copy0的第一個元素與原始的第一個元素具有相同的辨別(即,相同的對象)。相比之下,deepcopy()方法在記憶體中複制嵌套清單,以使copy1中的第一個元素具有與原始元素不同的辨別。
但是在深度複制中"遞歸"是什麼意思?這意味着如果存在多層嵌套(例如,嵌套在清單中的清單,又嵌套在另一個清單中),則deepcopy()方法将為每一層建立新對象。請參見以下示例以了解此功能:
>>> mul_nested = [[[0, 1], 2], 3]
>>> print(f'{mul_nested} id: {id(mul_nested)}, inner id: {id(mul_nested[0])}, innermost id: {id(mul_nested[0][0])}')
[[[0, 1], 2], 3] id: 4342107824, inner id: 4342106944, innermost id: 4342107424
>>> mul_nested_dc = copy.deepcopy(mul_nested)
>>> print(f'{mul_nested_dc} id: {id(mul_nested_dc)}, inner id: {id(mul_nested_dc[0])}, innermost id: {id(mul_nested_dc[0][0])}')
[[[0, 1], 2], 3] id: 4342107264, inner id: 4342107984, innermost id: 4342107904
6.資料可變性
Python程式設計中的一個進階主題與資料可變性有關。一般來說,不可變資料是指其值在建立後便無法更改的對象,例如整數,字元串和元組。相比之下,可變資料是指其值在建立後可以更改的那些對象,例如清單,字典和集合。
需要注意的一件事是,通過"更改值",我們的意思是是否可以更改記憶體中的基礎對象。在我的上一篇文章中可以找到關于資料可變性的詳盡讨論:
不可變與可變
為了本文讨論id()函數的目的,讓我們考慮以下示例。對于不可變資料類型(代碼片段中的整數變量千),當我們嘗試更改其值時,會在記憶體中建立一個新的整數,這由千變量的新辨別所反映。換句話說,原始的基礎整數對象無法更改。嘗試更改整數隻會在記憶體中建立一個新對象。
>>> thousand = 1000
>>> print(f'{thousand} id: {id(thousand)}')
1000 id: 4342004944
>>> thousand += 1
>>> print(f'{thousand} id: {id(thousand)}')
1001 id: 4342004912
>>> numbers = [4, 3, 2]
>>> print(f'{numbers} id: {id(numbers)}')
[4, 3, 2] id: 4342124624
>>> numbers += [1]
>>> print(f'{numbers} id: {id(numbers)}')
[4, 3, 2, 1] id: 4342124624
如果這讓您感到困惑,讓我們看看可變資料類型發生了什麼—在我們的例子中是清單變量編号。如上面的代碼所示,當我們嘗試更改數字的值時,變量号得到了更新,并且更新後的清單仍具有相同的辨別,進而确認了清單類型的對象的可變性。
總結
在本文中,我們利用内置的id()函數來了解Python中的六個關鍵概念。以下是這些概念的快速回顧:
· Python中的所有内容都是一個對象。
· 我們通過指派建立變量,别名指向記憶體中的相同對象。
· 比較運算符==比較值,而比較運算符正在比較辨別。
· Python解釋器在啟動時會建立從-5到256的整數對象。
· 淺副本和深副本均具有與其原始對象相同的值,但是淺副本僅複制原始對象的嵌套對象的引用。
· 可變對象的值可以在記憶體中更改,而不可變對象不支援值更改。
(本文翻譯自Yong Cui, Ph.D.的文章《Use id() to Understand 6 Key Concepts in Python》,參考:https://medium.com/better-programming/use-id-to-understand-6-key-concepts-in-python-73e0bbd461ec)
請識别以下二維碼關注
部分文字、圖檔來源于網絡,如涉及版權請聯系删除。