Python學習筆記系列之012:傳值還是傳引用?

来源:https://www.cnblogs.com/salmond/archive/2018/04/24/8925763.html
-Advertisement-
Play Games

導讀: 1.變數和對象 2.可變對象與不可變對象 3.引用傳參 在C/C++中,傳值和傳引用是函數參數傳遞的兩種方式。由於思維定式,從C/C++轉過來的Python初學者也經常會感到疑惑:在Python中,函數參數傳遞是傳值,還是傳引用呢?看下麵兩段代碼: 看完第一段代碼,會有人說這是值傳遞,因為函 ...


導讀:

1.變數和對象

2.可變對象與不可變對象

3.引用傳參

 

在C/C++中,傳值和傳引用是函數參數傳遞的兩種方式。由於思維定式,從C/C++轉過來的Python初學者也經常會感到疑惑:在Python中,函數參數傳遞是傳值,還是傳引用呢?
看下麵兩段代碼:

def foo(arg):
arg = 5
print(arg)
x = 1
foo(x) # 輸出5
print(x) # 輸出1

def foo(arg):
arg.append(3)
x = [1, 2]
print(x) # 輸出[1, 2]
foo(x)
print(x) # 輸出[1, 2, 3]

看完第一段代碼,會有人說這是值傳遞,因為函數並沒有改變x的值;看完第二段代碼,又會有人說這是傳引用,因為函數改變了x的內容。
那麼,Python中的函數到底是傳值還是傳引用呢?

一、變數和對象

我們需要先知道Python中的“變數”與C/C++中“變數”是不同的。
在C/C++中,當你初始化一個變數時,就是聲明一塊存儲空間並寫入值。相當於把一個值放入一個盒子里:
int a = 1;

現在”a”盒子里放了一個整數1,當給變數a賦另外一個值時會替換盒子a裡面的內容:
a = 2;

當你把變數a賦給另外一個變數時,會拷貝a盒子中的值並放入一個新的“盒子”里:
int b = a;

在Python中,一個變數可以說是記憶體中的一個對象的“標簽”或“引用”:
a = 1

現在變數a指向了記憶體中的一個int型的對象(a相當於對象的標簽)。如果給a重新賦值,那麼標簽a將會移動並指向另一個對象:
a = 2

原來的值為1的int型對象仍然存在,但我們不能再通過a這個標識符去訪問它了(當一個對象沒有任何標簽或引用指向它時,它就會被自動釋放)。如果我們把變數a賦給另一個變數,我們只是給當前記憶體中對象增加一個“標簽”而已:
b = a

綜上所述,在Python中變數只是一個標簽一個標識符,它指向記憶體中的對象。故變數並沒有類型,類型是屬於對象的,這也是Python中的變數可以被任何類型賦值的原因。


在python中,值是靠引用來傳遞來的。
我們可以用id()來判斷兩個變數是否為同一個值的引用。 我們可以將id值理解為那塊記憶體的地址標示。

>>> a = 1
>>> b = a
>>> id(a) 
13033816
>>> id(b) # 註意兩個變數的id值相同
13033816
>>> a = 2
>>> id(a) # 註意a的id值已經變了
13033792
>>> id(b) # b的id值依舊
13033816

>>> a = [1, 2]
>>> b = a
>>> id(a)
139935018544808
>>> id(b)
139935018544808
>>> a.append(3)
>>> a
[1, 2, 3]
>>> id(a)
139935018544808
>>> id(b) # 註意a與b始終指向同一個地址
139935018544808
View Code

 

二、可變對象與不可變對象

在Python的基本數據類型中,我們知道numbers、strings和tuples是不可更改的對象,而list、dict、set是可以修改的對象。那麼可變與不可變有什麼區別呢?看下麵示例:

a = 1 # a指向記憶體中一個int型對象
a = 2 # 重新賦值

當將a重新賦值時,因為原來值為1的對象是不能改變的,所以a會指向一個新的int對象,其值為2。(如上面的圖示)

lst = [1, 2] # lst指向記憶體中一個list類型的對象
lst[0] = 2 # 重新賦值lst中第一個元素

因為list類型是可以改變的,所以第一個元素變更為2。更確切的說,lst的第一個元素是int型,重新賦值時一個新的int對象被指定給第一個元素,但是對於lst來說,它所指的列表型對象沒有變,只是列表的內容(其中一個元素)改變了。
好了,到這裡我們就很容易解釋開頭的兩段代碼了:

def foo(arg):
arg = 5
print(arg)
x = 1
foo(x) # 輸出5
print(x) # 輸出1

上面這段代碼把x作為參數傳遞給函數,這時x和arg都指向記憶體中值為1的對象。然後在函數中arg = 5時,因為int對象不可改變,於是創建一個新的int對象(值為5)並且令arg指向它。而x仍然指向原來的值為1的int對象,所以函數沒有改變x變數。

def foo(arg):
arg.append(3)
x = [1, 2]
print(x) # 輸出[1, 2]
foo(x)
print(x) # 輸出[1, 2, 3]

這段代碼同樣把x傳遞給函數foo,那麼x和arg都會指向同一個list類型的對象。因為list對象是可以改變的,函數中使用append在其末尾添加了一個元素,list對象的內容發生了改變,但是x和arg仍然是指向這一個list對象,所以變數x的內容發生了改變。

三、引用傳參

可變類型與不可變類型的變數分別作為函數參數時,會有什麼不同嗎?
Python有沒有類似C語言中的指針傳參呢?

>>> def selfAdd(a):
... """自增"""
... a += a
...
>>> a_int = 1
>>> a_int
1
>>> selfAdd(a_int)
>>> a_int
1
>>> a_list = [1, 2]
>>> a_list
[1, 2]
>>> selfAdd(a_list)
>>> a_list
[1, 2, 1, 2]

Python中函數參數是引用傳遞(註意不是值傳遞)。對於不可變類型,因變數不能修改,所以運算不會影響到變數自身;而對於可變類型來說,函數體中的運算有可能會更改傳入的參數變數。

想一想為什麼

>>> def selfAdd(a):
... """自增"""
... a = a + a # 我們更改了函數體的這句話
...
>>> a_int = 1
>>> a_int
1
>>> selfAdd(a_int)
>>> a_int
1
>>> a_list = [1, 2]
>>> a_list
[1, 2]
>>> selfAdd(a_list)
>>> a_list
[1, 2] # 想一想為什麼沒有變呢?

總結:
x += x是直接對x指向的空間進行修改,而不是讓b指向一個新的。
x = x+x先把=號右邊的結果計算出來,然後讓x指向這個新的地方,不管原來b指向誰.

 四、一切皆對象

Python使用對象模型來儲存數據,任何類型的值都是一個對象。所有的python對象都有3個特征:身份id類型type值value
身份:每一個對象都有自己的唯一的標識,可以使用內建函數id()來得到它。這個值可以被認為是該對象的記憶體地址。
類型:對象的類型決定了該對象可以保存的什麼類型的值,可以進行什麼操作,以及遵循什麼樣的規則。type()函數來查看python 對象的類型。
:對象表示的數據項。

>>> a = 1
>>> id(a)
140068196051520
>>> b = 2
>>> id(b)
140068196051552
>>> c = a
>>> id(c)
140068196051520
>>> c is a
True
>>> c is not b
True

運算符 is 、 is not 就是通過id()的返回值(即身份)來判定的,也就是看它們是不是同一個對象的“標簽”。

 

本文來源於我看到的一篇文檔,具體來源不可考,我覺得對於引用講的還是比較清楚的。引用以及可變對象不可變對象,在Python中時比較重要的,因為在接下來的學習中,都會有意無意地用到。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.模板名片發送後不顯示內容?(如第一張圖) 經過查看官方文檔,是data數據格式問題,小程式端傳給後端的data數據被服務端解析出了一點問題(data裡面的字元串加入了"\")。現在後端將數據從新做了清洗。已解決。解決後的展示如第二張圖。 2.上傳圖片一直失敗。 解決答案相關鏈接:https:// ...
  • 概述 理解柯里化函數,需要有閉包的基礎,只有徹底理解閉包後才能理解柯里化,如果尚未理解閉包,建議閱讀上文js引擎的執行過程(一);如果理解了閉包再研究柯里化函數,則會大大的加深你對閉包理解,並且更清楚的認識到閉包的應用場景,那麼如果在面試時候問到閉包,你就可以侃侃而談了;並且理解柯里化函數會在很大的 ...
  • 當代碼在執行環境中執行時,會創建一個作用域鏈。作用域鏈本質是一個指向變數對象的指針列表。 如果執行環境是函數,則將其活動對象(最開始時只包含一個變數->argument對象)作為變數對象。ps:argument對象在全局環境中是不存在的. (基於2條件下)作用域鏈中的下一個變數對象來自外部環境,而再 ...
  • 概述 js是一種非常靈活的語言,理解js引擎的執行過程對我們學習javascript非常重要,但是網上講解js引擎的文章也大多是淺嘗輒止或者只局部分析,例如只分析事件迴圈(Event Loop)或者變數提升等等,並沒有全面深入的分析其中過程。所以我一直想把js執行的詳細過程整理成一個較為詳細的知識體 ...
  • Document ...
  • 百度一下WebRTC,我想也是一堆。本以為用SkyRTC-demo 就可以一馬平川的實現聊天,結果折騰了半天,文本信息都發不出去,更別說視頻了。網上的SimpWebRTCDemo,WebRTC-Experiment等對於第一次部署的人來說,都是相當的蛋疼。於是親自踩坑填坑,完美實現! ...
  • 阿裡發佈了<<阿裡巴巴Java開發手冊終極版>>,也許看過後也不能完全吸收,我在這裡分類整理,方便大家在手機端查看,一起學習阿裡對Java工程師編程的規約。 註釋規約 1. 【強制】類、類屬性、類方法的註釋必須使用 Javadoc 規範,使用/**內容*/格式,不得使用// xxx 方式。 說明:在 ...
  • 1. Ubuntu16.04上使用sudo apt-get install php7.1 安裝php的預設路徑如下: a. php可執行命令:/usr/bin/php7.1 和 /usr/bin/php b. 需要安裝sudo apt install php7.1-dev 才會有 /usr/bin/ ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...