變數是編程的基礎概念,Python 的變數也看似很簡單,但是如果理解不當,生搬硬套,可能會遇到一些麻煩,

下面用 10 個代碼示例展示 Python 的 變數 本質,
不過還要注意:不管你是為了Python就業還是興趣愛好,記住:專案開發經驗永遠是核心,如果你沒有2020最新python入門到高級實戰視頻教程,可以去小編的Python交流.裙 :七衣衣九七七巴而五(數字的諧音)轉換下可以找到了,里面很多新python教程專案,還可以跟老司機交流討教!
1. 只是個名字
當 a 出現在賦值陳述句的左側時,它僅僅代表一個 名字:
a = 1024
a = 'davycloud'
a = ['點贊', '關注', '收藏']
復制代碼
賦值完成后,這個名字和右側的物件就 系結 在一起了,在這個程序中,
a是否已經系結了其它物件完全不用考慮,也就是說,不管前面a系結了什么,或者什么也沒有系結,它都是個名字,- 只要名字是合法的,它可以系結到任意的物件,完全不用考慮物件的型別
2. 物件的參考
系結了物件的名字,也就是我們常說的 變數,當它出現在賦值陳述句的右側時,它代表了 物件的參考:
a = []
b = a
復制代碼
當把 a 賦給 b 的時候,a 代表的是串列物件的參考,也就是說:
- 串列物件本體不受影響
- 并不會復制出一個新的串列
a和b在這之后都是這同一個串列的參考
因為串列是可變物件,所以可以通過 a 和 b 任意一個變數改變物件,兩者同時都會反映出物件的變化:
a.append(1) # a = [1], b = [1]
b.append(2) # a = [1, 2], b = [1, 2]
復制代碼
“可變物件下面會再次討論
3. 先右 后左
把多個賦值陳述句連在一起時,處理的順序是從右往左:
a = b = []
復制代碼
這里 b 首先是充當名字,系結到一個串列物件;然后又充當物件的參考,賦給另一個名字 a,也就是說,上面的陳述句等價于:
# 寫法1
b = []
a = b
復制代碼
它和下面的賦值方式有著截然不同的后果:
# 寫法2
b = []
a = []
復制代碼
在這里,兩個變數分別系結了兩個不同的串列,它們之間互相并無關聯,
但是這里有個有趣的地方,如果我們把 [] 換成一個整數 1 或者字串,那么 寫法 1 和 寫法 2 兩種賦值方式在結果上就并無不同,
4. 變化取決于物件
繼續上面的例子,兩個名字系結到同一個串列,操作其中一個,另一個就受到影響:
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,因為它可能會產生時而正確時而錯誤的詭異結果,
5. 瞬間交換的秘密
當一個賦值陳述句中右側出現了多個物件,或者多個物件的參考,它們會自動打包成一個元組,
在賦值陳述句的左邊,需要有相同數量的名字供解包:
a = [1024]
b = 'davycloud'
a, b = b, a
復制代碼
這個例子中,綜合利用前 3 個規則:
- 先右后左
- 變數是物件的參考
- 左側是名字
不難得出一個結論,這里兩個名字交換了物件的參考,物件本體并沒有移動,
6. 都是名字的錯
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到底是什么呢?數字?字串?函式?類?模塊?
7. 我們都是變數
接上一個例子:
a = 1
def a():
pass
class a():
pass
import sys as a
復制代碼
不僅是賦值,定義函式,定義類,匯入模塊或模塊中的物件,都是在系結名字,
定義函式是把一個名字系結到一個函式物件,定義類是把名字系結到一個類物件,匯入一個模塊就是把一個名字系結到一個模塊物件,
Python 中一切皆物件,所以,它們都是變數,
8. 傳參也不過是命名
既然說到了函式,那就繼續來看函式的傳參:
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
復制代碼
9. 刪不掉的物件
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 的垃圾回收相關內容,
10. 淺拷貝?零拷貝!
終極例子:
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 模塊則更加通用,
小結
- 變數指的是名字系結了物件
- 系結時,變數就是名字
- 使用時,變數代表物件的參考
- 變數改變的只有系結關系
- 想要改變/復制物件,需要看物件有沒有提供方法
最后注意:不管你是為了Python就業還是興趣愛好,記住:專案開發經驗永遠是核心,如果你沒有2020最新python入門到高級實戰視頻教程,可以去小編的Python交流.裙 :七衣衣九七七巴而五(數字的諧音)轉換下可以找到了,里面很多新python教程專案,還可以跟老司機交流討教!
本文的文字及圖片來源于網路加上自己的想法,僅供學習、交流使用,不具有任何商業用途,著作權歸原作者所有,如有問題請及時聯系我們以作處理,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/156848.html
標籤:Python
下一篇:Python函式的使用-引數
