變量是編程的基礎概念,Python 的變量也看似很簡單,可是若是理解不當,生搬硬套,可能會遇到一些麻煩。編程
下面用 10 個代碼示例展現 Python 的 變量 本質。bash
當 a
出如今賦值語句的左側時,它僅僅表明一個 名字:app
a = 1024
a = 'davycloud'
a = ['點贊', '關注', '收藏']
複製代碼
賦值完成後,這個名字和右側的對象就 綁定 在一塊兒了。在這個過程當中,函數
a
是否已經綁定了其它對象徹底不用考慮,也就是說,無論前面
a
綁定了什麼,或者什麼也沒有綁定,它都是個名字。
綁定了對象的名字,也就是咱們常說的 變量,當它出如今賦值語句的右側時,它表明了 對象的引用:優化
a = []
b = a
複製代碼
當把 a
賦給 b
的時候,a
表明的是列表對象的引用,也就是說:ui
a
和
b
在這以後都是這同一個列表的引用
由於列表是可變對象,因此能夠經過 a
和 b
任意一個變量改變對象,二者同時都會反映出對象的變化:spa
a.append(1) # a = [1], b = [1]
b.append(2) # a = [1, 2], b = [1, 2]
複製代碼
「可變對象下面會再次討論code
把多個賦值語句連在一塊兒時,處理的順序是從右往左:cdn
a = b = []
複製代碼
這裏 b
首先是充當名字,綁定到一個列表對象;而後又充當對象的引用,賦給另外一個名字 a
,也就是說,上面的語句等價於:對象
# 寫法1
b = []
a = b
複製代碼
它和下面的賦值方式有着大相徑庭的後果:
# 寫法2
b = []
a = []
複製代碼
在這裏,兩個變量分別綁定了兩個不一樣的列表,它們之間互相併沒有關聯。
可是這裏有個有趣的地方,若是咱們把 []
換成一個整數 1
或者字符串,那麼 寫法 1 和 寫法 2 兩種賦值方式在結果上就並沒有不一樣。
繼續上面的例子,兩個名字綁定到同一個列表,操做其中一個,另外一個就受到影響:
b = []
a = b
a.append(1)
複製代碼
這是由於列表是一個 可變對象。而若是把列表換成數字或者字符串:
b = 'davy'
a = b
a += 'cloud'
複製代碼
對 a
的自增操做並不會影響到 b
。先給出自增操做的等價形式:
a = a + 'cloud'
複製代碼
可見,這仍然是一次賦值而已。利用前面的結論:
a
表明對象的引用,即
'davy'
,它和
'cloud'
加起來生成一個新的對象
a
是一個名字,它再次和新對象,即
'davycloud'
綁定在一塊兒
也就是說,這中間總共產生了 3 個字符串對象。
咱們仔細觀察不難看出,當咱們想要 改變對象的時候,必須經過的是對象提供的接口(對於列表來講,就是 append
方法,或者下標操做,對於其它對象,能夠是改變它的屬性),而 不可能經過從新賦值改變對象 。
賦值只是名字的綁定,再次強調。
這裏咱們還能獲得另一個結論:
不可變對象被屢次引用/綁定不會產生反作用。好比說:
# a, b 綁定到同一個對象
a = b = 'davy'
# a, b 分別綁定到一個對象
a = 'davy'
b = 'davy'
複製代碼
在上面的示例中,兩種綁定的語法含義是不一樣的,可是,由於字符串是不可變的,也就是說,即便 a
和 b
綁定到同一個字符串,它們也不會互相影響。既然這樣,那麼又何須在內存中重複建立兩個如出一轍的的 davy
字符串出來呢。不如直接複用好了,能夠節省一點內存:
>>> a = 'davy'
>>> b = 'davy'
>>> a is b
True
複製代碼
再次可是,這種優化並非全局的,也就是說,並非只要是相同的字符串就必定是惟一的對象:
>>> a += 'cloud'
>>> b += 'cloud'
>>> a is b
False
>>> a
'davycloud'
>>> b
'davycloud'
>>> a == b
True
複製代碼
因此呢,你們知道有這種狀況就好,對於不可變對象都要使用 ==
去比較,而不要用 is
,由於它可能會產生時而正確時而錯誤的詭異結果。
當一個賦值語句中右側出現了多個對象,或者多個對象的引用,它們會自動打包成一個元組。
在賦值語句的左邊,須要有相同數量的名字供解包:
a = [1024]
b = 'davycloud'
a, b = b, a
複製代碼
這個例子中,綜合利用前 3 個規則:
不可貴出一個結論,這裏兩個名字交換了對象的引用,對象本體並無移動。
a
沒有賦值,直接地運行結果:
>>> print(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
複製代碼
這裏看似很好解釋,變量 a
沒有定義嘛!加一行賦值語句不就好了。
可是:
NameError
:
name 'a' is not defined,名字未定義錯誤,並非變量未定義
a
究竟是什麼呢?數字?字符串?函數?類?模塊?
接上一個例子:
a = 1
def a():
pass
class a():
pass
import sys as a
複製代碼
不只是賦值,定義函數,定義類,導入模塊或模塊中的對象,都是在綁定名字。
定義函數是把一個名字綁定到一個函數對象,定義類是把名字綁定到一個類對象,導入一個模塊就是把一個名字綁定到一個模塊對象。
Python 中一切皆對象,因此,它們都是變量。
既然說到了函數,那就繼續來看函數的傳參:
def func(x):
return x
複製代碼
這個函數毫無用處,可是正好用來解釋參數的傳入和傳出。
a = func(1024)
b = func(a)
複製代碼
函數中的參數 x
其實也是一個名字,只是它的 做用域 是限定在 func
函數的內部。
給函數傳參就如同是賦值,給這個內部名字綁定一個對象,而出參就好似出如今賦值右側的變量,就是傳出來一個對象引用:
# 僞代碼
func(1024):
x = 1024
a = x
複製代碼
「關於變量的做用域這裏點到爲止,有機會再詳細討論。
注意,上面的規則對全部的對象類型都是同樣的,不管是可變對象仍是不可變對象。
根據前面的分析,很容易得出結論:
所以,對於可變對象的傳參須要格外謹慎。特別地,函數的默認參數不要使用可變對象。
由於默認參數的綁定是在函數定義階段發生的:
def get_people(people=[]):
return people
複製代碼
在調用 get_people
時,除非給 people
指定一個參數,不然它綁定的老是在函數定義時刻產生的那個列表。而咱們的本意多是,若是默認沒有參數,就生成一個空列表。
一個常規的作法是,在函數內部新建對象:
def get_people(people=None):
if people is None:
people = []
return people
複製代碼
Python 提供了 del
關鍵字能夠用來
NameError
,就好像它歷來沒存在過同樣。
而對象呢,它們只是減小了一個引用:
a = [1, 2, 3]
b = a
del a # 徹底不會影響到 b
複製代碼
一個對象有多個變量引用到的狀況下不會被清理很好理解,其實即便當前沒有任何名字綁定到這個對象,這個對象也不會當即刪除掉:
>>> a = []
>>> id(a)
2292904305736
>>> del a
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b = []
>>> id(b)
2292904305736
複製代碼
總而言之,del
的含義就是 解綁,別期望它刪除對象。
「對象的引用計數和銷燬是 Python 內部維護的,通常狀況下咱們無需關心。有興趣的能夠查閱 Python 的垃圾回收相關內容。
終極例子:
a = [[]] * 3
a[0].append(1)
print(a) # [[1], [1], [1]]
複製代碼
這裏迷惑性比較強,由於用到了列表的 *
操做。當對一個列表乘法操做時,通常的理解是對其中的元素進行 複製(copy)。
看到 copy 很容易又會提起所謂的 淺拷貝 和 深拷貝,這裏顯然不是深拷貝,那麼想固然的很容易理解爲是淺拷貝,錯!
這裏不過又是一次隱形的賦值,讓咱們把它展開:
x = []
y = [x]
a = [x, x, x] # a = y * 3 的等價寫法
a[0].append(1)
print(a)
複製代碼
最重要的就是第 3 行代碼:
y * 3
是要把
y
中的元素複製
3
份
x
,
x
重複出現
3
次吧
[x, x, x]
,
x
是對象的引用,因此咱們只是把對象的引用複製了 3
份,對象本體徹底沒有觸及。
那麼若是要真正複製這個列表應該怎麼作呢?利用到系統提供的淺拷貝函數,或者是利用 切片:
# 列表內置的 copy 方法
a = [x.copy() for i in range(3)]
# 利用切片
a = [x[:] for i in range(3)]
# 利用 copy 標準庫
from copy import copy
a = [copy(x) for i in range(3)]
複製代碼
前面兩種方法是列表對象自帶的接口,而 copy
模塊則更加通用。
除了以上內容,關於變量還有個重要的概念就是理解它的 做用域,這部份內容將另撰文講解。
若是本文對你有幫助,請 點贊、分享、關注,謝謝!