日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Python賦值邏輯的實現_python

作者:代碼輸入中... ? 更新時間: 2023-05-30 編程語言

摘要:

如果你學過 C 語言,那么當你初見 Python 時可能會覺得 Python 的賦值方式略有詭異:好像差不多,但又好像哪里有點不太對勁。

本文比較并解釋了這種賦值邏輯上的差異。回答了為什么需要這種賦值邏輯以及如何使用這種賦值邏輯的問題。

當然,即使未學過 C 語言,也可通過本文更好地了解 Python 的賦值邏輯——這種賦值邏輯影響著 Python 的方方面面,從而可以讓你更好地理解和編寫 Python 程序。

第一章 引例

先來看一組似乎矛盾的代碼:

# 代碼 1
 
>>> a = 3
>>> b = a
>>> b = 5
>>> a
3

這看上去似乎很好理解。第二步中,?a?只是把值復制給?b,然后?b?又被更新為?5a?和?b?是兩個獨立的變量,那么?a?的值當然不會受到影響。

真的是這樣嗎?

再來看一段代碼:

# 代碼 2
 
>>> a = [1, 2, 3]
>>> b = a
>>> b[0] = 1024
>>> a
[1024, 2, 3]

第二步中,a?只是復制把列表復制給?b,然后更新?b[0]?的值,最后輸出?a,可是?a?竟然也被改變了。

按照代碼 1 的邏輯(即變量之間獨立),代碼 2 的中的?a?不應該受到影響。

為什么出現了這樣的差異?

第二章 Python 的“反直覺”

先不解釋上面那個“看似矛盾”的問題。

先來看看另一組簡單的 Python 代碼在內存中是什么樣子的:

# 代碼 3
 
b = 3
b = b + 5

它在內存中的操作示意圖是這樣的:

然而,從代碼的的字面意思上看,“把 3 賦給 b,把 b 加 5 之后再賦給 b。”

也就是把代碼看成這個樣子:

b ← 3b ← b + 5

所以下面這張在內存中的操作圖可能更符合我們的直覺:

也即?b + 5?的值又寫回到?b?中。典型的 C 程序就是這樣的。為變量?b?分配一個?int?型的內存單元,然后將整數?3?存放在該內存單元中。b?就代表了該塊內存空間,不再移動,可以更新?b?的值,但?b?在內存中的地址就不再變化了。所以我們說?b = b + 5,就等于?b ← b + 5,把?b?的值加?5?之后還依然放入?b?中。 變量?b?和它所在內存空間緊緊綁定在一起,人形合一。

而再看看上面 Python 中的內存示意圖,b + 5?得到了一個新值,然后令?b?指向了這個新值。換句話說,它做的是事情是這樣的:

b → 3
b → b + 5

先令?b?指向?3,再令?b?指向?b + 5?這個新值。

C 程序更新的是內存單元中存放的值,而 Python 更新的是變量的指向。
C 程序中變量保存了一個值,而 Python 中的變量指向一個值。

如果說 C 程序是通過操縱內存地址而間接操作數據(每個變量固定對應一個內存地址,所以說操縱變量就是操縱內存地址),數據處于被動地位,那么 Python 則是直接操縱數據,數據處于主動地位,變量只是作為一種引用關系而存在,而不再擁有存儲功能。

在 Python 中,每一個數據都會占用一個內存空間,如?b + 5?這個新的數據也占用了一個全新的內存空間。

Python 的這種操作讓數據成為主體,數據與數據之間直接進行交互。

而數據在 Python 中被稱為對象 (Object)

這句話并不太嚴謹。不過在這個簡單的例子中是成立的。

一個整數?3?是一個?int?型對象,一個?'hello'?是一個字符串對象,一個?[1, 2, 3]?是一個列表對象。

Python 把一切數據都看成「對象」。它為每一個對象分配一個內存空間。 一個對象被創建后,它的 id 就不再發生變化。

id 是 identity 的縮寫。意為“身份;標識”。
在 Python 中,可以使用?id(),來獲得一個對象的 id,可以看作是該對象在內存中的地址。

一個對象被創建后,它不能被直接銷毀。因此,在上個例子中,變量?b?首先指向了對象?3,然后繼續執行?b + 5b + 5?產生了一個新的對象?8,由于對象?3?不能被銷毀,則令?b?指向新的對象?8,而不是用對象?8?去覆蓋對象?3。在代碼執行完成后,內存中依然有對象?3,也有對象?8,變量?b?指向了對象?8

如果沒有變量指向對象?3(即無法引用它了),Python 會使用垃圾回收算法來決定是否回收它(這是自動的,不需要程序編寫者操心)。

一個舊的對象不能被覆蓋,因舊的對象交互而新產生的數據會放在新的對象中。也就是說每個對象是一個獨立的個體,每個對象都有自己的“主權”。因此,兩個對象的交互可以產生一個新的對象,而不會對原對象產生影響。在大型程序中,各個對象之間的交互錯綜復雜,這種獨立性則使得這些交互足夠安全。

C 程序為每個變量都分配一個了固定的內存地址,這保證了 C 變量之間的獨立性。

C 語言是變量(也即內存地址)之間的交互,Python 是對象(數據)之間的交互。這是兩種不同的交互方式。

那么,Python 這種數據之間直接進行交互的好處體現在哪里?

很遺憾,這并不是本文所要討論的內容,該部分屬于面向對象設計的核心內容。本文只是對 Python 的這種交互方式與 C 語言的交互方式做了一些比較,以區分兩者在邏輯與物理上的差異所在。

相信這種邏輯會幫助你更好地編寫 Python 程序,并且幫助你在日后更加深入地理解面向對象的程序設計。

本章補充:
Python 的賦值更改的是變量的指向關系,因此,對于 Python,從前向后閱讀一個賦值表達式會更加容易理解。

// C 語言
b ← b + 5	// 把 b+5 的值賦給 b
# Python
b → b + 5	# 令 b 指向 b + 5

第三章 回答第一章的問題

先看代碼 1:

# 代碼 1
 
>>> a = 3
>>> b = a
>>> b = 5
>>> a
3

Python 中所有的數據都是對象,數字類型也不例外。3?是一個?int?類型的對象,5?也是一個?int?型的對象。
第一行,a?指向對象?3
第二行,令?b?也指向?a?所指向的對象?3
第三行,因為對象不可被覆蓋(銷毀),令?b?指向新對象?5,則只剩下?a?指向對象?3
第四行,輸出?a,得到?3

在內存中的操作示意圖 (Python):

這與第一章中的解釋完全不同,第一章中的解釋是用 C 語言解釋的:

這是兩種完全不一樣的機制。

Python 中?b?首先指向了對象?3,然而因為對象之間的獨立性,一個對象不能去覆蓋另一個對象,則令?b?指向對象?5,而不是將對象?3?在內存中替換為對象?5

再來看代碼 2:

# 代碼 2
 
>>> a = [1, 2, 3]
>>> b = a
>>> b[0] = 1024
>>> a
[1024, 2, 3]

第一行,令?a?指向一個列表?[1, 2, 3]
第二行,令?b?也指向?a?所指向的列表;
第三行,令?b[0] = 10241024?雖然是一個對象,但它并沒有試圖覆蓋b所指向的對象,而是對該對象的第一個元素進行修改。修改,而不是覆蓋,所以它可以原對象進行操作,而不是令?b?指向修改后的對象。
所在第四行輸出的?a?所指向的列表也發生了變化。

在內存中的操作示意圖 (Python):

這種對象的值可以修改的對象被稱為可變對象 (immutable object)。常見的列表、字典為可變對象。

因為它的值可以被修改,因此如果有多個變量指向該列表:

a = [1, 2, 3]
b = a
c = a
d = a
...

那么使用?b, c, d, ...?的任何一個變量都能訪問該對象并修改其中的內容。這種特性常常被我們用于函數的參數傳遞,如果函數的參數是可變對象,那么函數可以對“實參”中的內容進行修改:

>>> a = [1, 2, 3]
>>> def change(t):
		t[0] = 1024
 
>>> change(a)
>>> a
[1024, 2, 3]
>>>  

調用函數?change?時,令?t?也指向了?a?所指向的列表,然后使用?t?更改了列表中的第一個元素,更改,而不是覆蓋,因此對?t?所指向的對象的更改也改變了“實參”?a?所指向的對象。而 C 語言則因為實參到形參是值傳遞,則無法改變實參的內容(雖然借助指針可以實現,但這里只說一般情況下)。

但在函數以外的區域,我們要盡量避免這樣使用,這很容易導致出錯(當然,有時候會很有用,這取決于你的程序)。比如,在多人協作編程時,如果甲不小心修改了某可變對象,那么乙、丙、丁等用到該對象的人都會受到影響。

而對于不可變對象 (immutable object),即其值無法更改的對象,傳入函數時則不會影響“實參”的值:

>>> a = 5
>>> def add(n):
		n = n + 2
 
>>> add(a)
>>> a
5

調用函數?add?時,令?n?也指向了?a?所指向的對象?5, 再執行?n = n + 2n?所指向的對象?5?與對象?2?相加得到了一個新的對象?7由于一個對象不能覆蓋另一個對象,則?n?指向新的對象?7,而沒有改變原對象。因此?a?的值未發生變化。雖然與 C 程序的結果一致,但與 C 程序的機制完全不同,C 程序之所以沒改變?a,是因為調用函數時只發生了值傳遞,即只把?a?的值復制給了?n

不要混淆這兩種賦值邏輯,它們有著完全不同的物理實現方式。

不同的思維邏輯會導致不同的編寫邏輯。盡管這兩種邏輯在很多情況下的結果是一致的,但并不能就簡單地認為它們是一致的。否則在一些小的細節方面出了錯誤,就會難以理解。只能死記硬背,把一些東西當作 Python 的特例來記,雖然「唯手熟爾」也可以讓你走得很遠,但思維正確時,不僅可以走得更遠,也會走得更加輕松。

比如,當你的思維清晰時,以下問題的答案自然也就水落石出了:

  • 為什么列表的方法的返回值大多是?None
  • 為什么字符串的方法的返回值大多是一個新的對象?
  • 為什么 Python 中沒有自增/自減運算符?
  • 為什么有的可變對象傳入函數之后,卻不能被函數修改“實參”的值?
    (比如將上面的?change?函數的主體改成?t = t[1:]。調用函數之后,a?所指向的對象并沒有發生改變。)
  • ……

這些內容與本文主題不大相關,所以不再列出答案。

有趣的補充:

1. 數字是一個天然的不可變對象(immutable object)。
對于?n = n + 2,有人可能會說,為什么不能把它看成像列表那樣的修改,修改后?n?依然指向的是原對象,這樣的話執行?add(a)?之后,a?就會變成?7?了,可為什么不是這樣?
因為每一個數字都是一個單個的對象,而對象不能覆蓋對象。所以該句實際上是:?a?指向的對象加上對象?2,產生了一個新的對象,然后令?a?指向了新對象?a + 2
因此,數字類型并不存在修改這一說,它是一個天然的不可變對象。

2. 為什么 Python 中沒有自增(++)、自減(--)運算符?
自增或自減運算符,在 C 語言中很常用,簡潔實用。但在 Python 中卻一定不會有。上節說到,數字是天然的不可變對象,所謂自增就是自身增加,所以它無法自增。它只能從一個對象指向下一個對象。可以這樣寫?a += 1
3. 既然 Python 更改的只是引用關系,那么如何復制一個列表?

# 答案:
## 1. 使用 list 的 copy 方法
b = a.copy()
## 2. 使用 slice 操作
b = a[:]	# slice 操作返回一個新的對象
# 答案:
## 1. 使用 list 的 copy 方法
b = a.copy()
## 2. 使用 slice 操作
b = a[:]	# slice 操作返回一個新的對象

原文鏈接:https://blog.csdn.net/m0_59485658/article/details/129132127

  • 上一篇:沒有了
  • 下一篇:沒有了
欄目分類
最近更新