python函數的參數細節

来源:https://www.cnblogs.com/f-ck-need-u/archive/2018/11/10/9937828.html
-Advertisement-
Play Games

按"指針"傳遞 python中 變數賦值、參數傳遞都是通過"指針"拷貝的方式進行的 。除了按"指針"拷貝,還有一種按值拷貝的方式,關於按值、按指針拷貝的細節,參見 "按值傳遞 vs. 按指針傳遞" 。 所以在python中,變數賦值、參數傳遞,都只是拷貝了源數據的一個地址,而不會拷貝記憶體中完整的數據 ...


按"指針"傳遞

python中變數賦值、參數傳遞都是通過"指針"拷貝的方式進行的。除了按"指針"拷貝,還有一種按值拷貝的方式,關於按值、按指針拷貝的細節,參見按值傳遞 vs. 按指針傳遞

所以在python中,變數賦值、參數傳遞,都只是拷貝了源數據的一個地址,而不會拷貝記憶體中完整的數據對象副本。所以,如果在函數內部修改變數指向的數據對象,會影響函數外部的數據。

例如:

def f(x):
    print(x+3)
a=4
f(a)

在將a賦值給本地變數x的時候,只是拷貝了a目前保存的地址給x,使得x也保存了記憶體中數據對象4的地址。

如果傳遞的數據對象是可變數據對象(例如列表),那麼在函數內部修改它,會影響函數外部的原始數據對象:

L1=[11,22,33,44]
def f(x):
    x[0] += 1

f(L1)
print(L1)   # 輸出:[12, 22, 33, 44]

顯然,在函數內部修改x[0]的值,函數外部原始的L1也發生了改變。因為L1賦值給x的時候,只是拷貝了一份L1所指向列表的地址給x,使得x也指向這個列表。

為了避免這種情況,可以新創建一份列表的副本,然後傳遞給函數參數。

L1=[11,22,33,44]
def f(x):
    x[0] += 1

f(L1[:])
print(L1)   # 輸出:[11, 22, 33, 44]

上面傳遞給函數參數x的是L1[:],它會在記憶體中創建一個新的列表副本,所以x指向的是這個新的副本列表,修改它不會影響原始的列表L1。

函數參數

Python的函數對參數和返回值方面非常寬鬆,參數變數可以是任意數據類型,返回值也一樣,只需使用變數名代替它們即可。

例如,下麵的參數x可以是任意類型的結構,可以是數值、字元串、列表、字典等等類型。返回值語句return同理。

def f(x):
    print(x)
    return x

f(2)
f("haha")

實際上,上面調用函數時是按照參數位置進行傳參對本地變數x進行賦值的。除此之外,還可以指定為key=value的方式進行傳參。例如:

f(x=2)
f(x="haha")

按位置傳參

如果是多個參數,則按從左到右的順序進行參數變數的賦值:

def f(x,y,z):
    print(x)
    print(y)
    print(z)

f(2,3,4)

調用f(2,3,4)的時候,會按照從左向右的位置方式對本地變數x、y、z賦值:x=2,y=3,z=4

按關鍵字key/value方式傳值

python還支持key=value的方式設置函數調用時的參數,使用key=value的方式賦值時,順序不重要。這種函數調用時的傳值方式稱為"關鍵字傳值"。

例如:

def f(x,y,z):
    print(x)
    print(y)
    print(z)
    
f(x=3,y="haha",z=4)

也可以打亂順序:

f(x=3,z=4,y="haha")

還可以將key=value和位置傳參的方式進行混合:

f(3,"haha",z=4)

但混合按位置傳參方式的時候,位置參數必須在其它傳參方式的前面,不僅此處結合key=value時如此,後文中位置參數結合其它方式傳參也都如此:位置參數必須在最前面

例如,下麵的傳參方式是錯的:

f(z=4,3,"haha")

參數預設值

在def或lambda聲明函數的時候,可以通過var=default的方式指定參數的預設值。

例如:

def f(x=3):
    print(x)

f(4)
f("haha")
f()

上面的f(4)f("haha")都對函數f()的本地變數x進行了賦值。但是最後一個調用語句f()未賦值,而是使用參數的預設值3。

設置參數預設值時,如果函數有多個參數,則帶預設值參數後面必須放在最後面。例如:

# 正確
def f(x,y,z=4)
def f(x,y=1,z=4)

# 錯誤
def f(x,y=4,z)

只要為參數設置了預設值,那麼調用函數的時候,這個參數就是可選的,可有可無的,如果沒有,則採用預設值。

def f(x,y=2,z=4):
    print(x)
    print(y)
    print(z)

# 不採用任何預設值
f(2,3,4)

# 採用z的預設值
f(2,3)

# 採用y的預設值
# 此時z必須按key=value的方式傳值
f(2,z=5)

# y、z都採用預設值
f(2)

變長參數:*

對於任意長度的參數,可以在def聲明的函數中使用*將各位置參數收集到一個元組中。例如:

def f(*args):
    print(args)

f(1,2,3,4)

上面調用f(1,2,3,4)的時候,將所有參數都收集到了一個名為args的元組中。所以上面的函數將輸出:

(1, 2, 3, 4)

既然是元組,就可以對參數進行迭代遍歷:

def f(*args):
    for arg in args:
        print(arg)

f(1,2,3,4)

必須註意,*是按位置收集參數的。

def f(x,y,*args):
    print(x)
    print(y)
    for arg in args:
        print(arg)

f(1,2,3,4)

按照從左向右的傳參規則,首先將1賦值給x,將2賦值給y,然後將剩餘所有的位置參數收集到args元組中,所以args=(3,4)

如果*後面還有參數,則調用函數的時候,後面的參數必須使用key=value的方式傳遞,否則會收集到元組中,從而導致參數缺少的問題:

def f(x,*args,y):
    print(x)
    print(y)
    for arg in args:
        print(arg)

# 正確
f(1,3,4,y=2)

# 錯誤
f(1,2,3,4)

上面調用f(1,3,4,y=2)的時候,會按照位置參數對x賦值為1,然後將所有位置參數收集到元組args中,因為y=2是非位置參數傳值方式,所以args=(3,4)

如果為上面的y設置預設值:

def f(x,*args,y=2)

那麼f(1,2,3,4)會將(2,3,4)都收集到元組args中,然後y採用預設值2。

變長參數:**

除了可以使用*將位置參數收集到元組中,還可以使用**key=value格式的參數收集到字典中。

例如:

def f(x,**args):
    print(x)
    print(args)

f(1,a=11,b=22,c=33,d=44)

上面首先按位置傳參的方式賦值x=1,然後將剩餘的所有key=value參數收集到名為args的字典中。所以,args字典的內容為:

{'a': 11, 'b': 22, 'c': 33, 'd': 44}

既然是將參數收集到字典中,就可以使用字典類的工具操作這個字典。例如,遍歷字典。

**後面不能出現任何其它類型的參數。例如,下麵的都是錯誤的def定義方式:

def f(x,**args,y)
def f(x,**args,y=3)
def f(x,**args,*t)

只能將位置參數或者*的收集放在**的前面。

def f(x,y,**args)
def f(x,*args1,**args2)

函數調用時的*和**

除了在def定義函數時,參數中可以使用***收集參數,在函數調用的時候也可以使用***分別解包元組(列表或其它對象)、字典。一定要註意區分函數定義和函數調用時的***,它們的用法是不通用的。

例如,解包元組:

def f(a,b,c,d):
    print(a)
    print(b)
    print(c)
    print(d)

T=(1,2,3,4)
f(*T)

*除了可以解包元組,還可以解包其它可迭代對象,例如列表。甚至是字典也能解包,只不過*解包的字典得到的是key組成的參數列表,和value無關:

D=dict(a=11,b=22,c=33,d=44)
f(*D)

# 輸出:
a
b
c
d

**解包的字典則是key=value組成的參數列表。以下是函數調用時使用**進行解包,字典D中的key名稱必須和def中定義的參數名稱相同:

def f(a,b,c,d):
    print(a)
    print(b)
    print(c)
    print(d)

D=dict(a=11,b=22,c=33,d=44)
f(**D)

# 輸出:
11
22
33
44

在函數調用時,可以混合位置參數、關鍵字參數、*解包參數、**解包參數。用法非常的靈活:

def f(a,b,c,d):
    print(a)
    print(b)
    print(c)
    print(d)

f(*(1,2),**{'d':4,'c':3})

f(1,*(2,3),**{'d':4})

f(1,c=3,*(2,),**{'d':4})

f(1,*(2,3),d=4)

f(1,*(2,),c=3,**{'d':4})

上面調用函數時的效果都等同於f(1,2,3,4)

keyword-only參數形式

keyword-only的參數傳值方式表示def中如果使用了*,那麼在調用函數時,它後面的參數必須只能使用關鍵字傳值。其實在前面的內容中已經出現過幾次與之相關的說明。

另外註意,*才是keyword-only開關,**不是,雖然**也有自己的一些語法限制:任意類型的參數定義都必須在**之前,包括keyword-only類型的參數。這個前面已經解釋過了。

例如:

def f(a,*b,c):
    print(a,b,c)

按照keyword-only的規則,被*b收集的位置參數不包括c,這個c必須只能使用關鍵字的方式傳值,否則就被當作位置參數被收集到元組b中。

# 正確
f(1,2,3,c=4)

# 錯誤
f(1,2,3,4)

# 錯誤
f(1,c=4,2,3)

其中最後一個錯誤和如何def的定義無關,而是函數調用時的語法錯誤,前面已經解釋過:位置參數必須放在最前面。

還可以直接使用*而非*args的方式,這表示不收集任何參數,但卻要求它後面的參數必須按照關鍵字傳值的方式

def f(a,*,b,c):
    print(a,b,c)

以下是正確和錯誤的調用方式示例:

# 正確
f(1,b=2,c=3)
f(1,c=3,b=2)
f(b=2,c=3,a=1)

# 錯誤
f(1,2,3)
f(1,2,c=3)
f(1,b=2,3)

不過,keyword-only後面的參數可以使用參數預設值。

def f(a,*,b,c=3)

那麼c是可選的,但如果給定,則必須按關鍵字方式傳值。

參數定義和參數傳值的規則

對於函數定義中的參數,有3種方式:普通位置參數、*開啟的keyword-only參數、**args收集參數。它們之間的規則是:

  • **args必須在最後面
  • **args後面可以是普通參數,但是函數調用傳值時,它後面的參數必須按照關鍵字的方式指定

所以,函數定義時參數的通用形式為:其中c和d必須使用關鍵字傳值方式

def f(a,b,  *,c,d,  **dicts)
def f(a,b,  *args,c,d,  **dicts)

對於函數調用中的參數,有:普通位置參數、關鍵字參數、*解包參數、**解包參數。它們之間的規則時:

  • 普通位置參數必須在最前面
  • **解包必須在最後面
  • 關鍵字參數和*解包參數只要求在上述兩種參數形式中間,順序可以隨意

所以,函數調用時的傳參形式為:

f(a,b,c,  *(d,e,f),g=1,h=2,  **dict(j=3,k=4))
f(a,b,c,  d=1,e=2,*(f,g,h),  **dict(j=3,k=4))

例如:

def f(a,*b,c,**d):
    print(a,b,c,d)

f(1,  2,3,  c=4,  x=5,y=6)
f(1,  c=4,*(2,3),  **dict(x=5,y=6))
f(1,  *(2,3),c=4,  **dict(x=5,y=6))
f(1,  *(2,3),      **dict(c=4,x=5,y=6))
f(1,  2,3,         **dict(c=4,x=5,y=6))

函數註解(annotations)

python函數有一個名為__annotations__的屬性(可以使用dir(Func_Name)來查看)。它表示函數的註解。

函數的註解使得參數變得更規範、更通用,它有點類似於強調數據類型。但它們僅僅只是註解,只是給人看,用來起提示作用的,不會對實際的調用有任何影響

例如,下麵是沒有給註解的函數參數,也就是平時見到的參數方式:

def myfunc(a,b,c):
    return a+b+c

myfunc(1,2,3)

函數的註解分兩種:參數註解和返回值註解。

  • 參數註解:定義在各參數名之後,使用冒號分隔參數和參數的註解
  • 返回值註解:定義在參數列表之後,冒號之前,使用瘦箭頭->分隔

例如:

def myfunc(a:'string',b:[1,5],c:int)->int:
    return a+b+c

print( myfunc(1,2,3) )
print( myfunc("a","b","c") )

雖然上面的函數註解提示了參數a是一個字元串,b是一個列表,c是一個int類型的數據,以及返回值是一個int類型的值,但在函數調用的時候,這些"強調"並沒有發生影響,只不過在使用該函數的時候,如果使用IDE編寫代碼,會有代碼提示。

可以通過函數的__annotations__屬性查看函數的註解:

print(myfunc.__annotations__)

輸出:

{'a': 'string', 'b': [1, 5], 'c': <class 'int'>, 'return': <class 'int'>}

可以只對其中一個或多個參數進行註解。

如果使用了註解,還要設置參數的預設值,則預設值需要在註解的後面。例如:

def f(a:'string'=4):

函數註解只對def語句有效,對lambda無效,因為lambda已經限制了函數的定義方式。


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

-Advertisement-
Play Games
更多相關文章
  • JavaScript: 知識點回顧篇(十三):DOM -- Console 對象 ...
  • 非常值得推薦的一本書,不僅僅是學習設計模式,也是在推薦你編程能力的鍛煉。簡單易懂的例子,能讓你學到非常多的知識。需要學習的朋友可以通過網盤免費下載pdf版 (先點擊普通下載 再選擇普通用戶就能免費下載了)http://putpan.com/fs/0yiabe3ns2hu69f37/ 《Head Fi ...
  • 介紹 此Refcard提供了Apache Hadoop,這是最流行的軟體框架,可使用簡單的高級編程模型實現大型數據集的分散式存儲和處理。我們將介紹Hadoop最重要的概念,描述其架構,指導您如何開始使用它以及在Hadoop上編寫和執行各種應用程式。 簡而言之,Hadoop是Apache Softwa ...
  • 單表操作: 一、添加 (1)方式一 from mysite.models import * def add(request): book= Book(name="python",price=99,author="python作者") book.sava() return HttpResponse(" ...
  • 前面5章收穫不大,更多的是 中間的部分,如何實際寫出一種高效優美的代碼,如何封裝 類,構建子程式,如何定義好的命名。同重構有很多部分的重疊。 其中感觸最深的一節,軟體工程最首要的核心技術: 控制複雜度!!!控制複雜度!!!需要學習的朋友可以通過網盤免費下載pdf版 (先點擊普通下載 再選擇普通用戶就 ...
  • 查詢銀行賬戶的數量 1.建立一個項目導入jar包(ioc aop dao 連接池 資料庫驅動 ),拷貝容器對應的配置文件到src下 2.在配置文件中開啟組件掃描 3.寫一個DAO介面定義一個查詢方法 4.定義一個JdbcTemplate的成員變數 4.1在類上加@Repository標註 4.2註入 ...
  • 基於 SpringSecurity 實現標準用戶名密碼登錄,基於 SpringSocial 實現QQ登錄,基於 OAuth2 實現認證伺服器。在完成登錄功能的同時,一步步分析 spring security、spring social、oauth 的實現原理,源碼分析等。 ...
  • 系統:Windows10 軟體:Java SE 8 配置詳細過程 1.“此電腦”,右鍵→“屬性,選擇“高級系統設置” 1.“此電腦”,右鍵→“屬性,選擇“高級系統設置” 2.選擇環境變數,再系統環境變數 3.新建 在新建頁面,輸入變數名“JAVA_HOME”;變數值“你的JDK的路徑,然後點擊“確定 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...