跳至內容

Python/函數

維基教科書,自由的教學讀本

函數定義

[編輯]

Python函數定義的通常格式為:

def 函数名参数列表:
    #函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明
    函数体
    #return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None

參數的傳值與傳引用

[編輯]

Python中,對象有類型;變量僅僅是對象的一個引用,變量沒有類型。 string、tuple和 numbers 是不可更改的類型(immutable type),給它們的變量賦值其實是指向了新的對象;而 list,dict 等則是可以修改的類型(mutable type)。Python函數的參數傳遞,在函數內部最初都是訪問實參那個對象而不是複製一份:

  • 不可變類型:在函數內部「修改」參數的值,只是引用到另一個對象,不影響實參本身。
  • 可變類型:在函數內部修改參數的值後,函數外部的實參也會受影響

命名實參與不定長參數

[編輯]

Python的函數實參可分為兩類:

  • 無命名的非關鍵字實參(non-keyword argument)或位置實參(positional argument)
  • 關鍵字實參(keyword argument)或譯作命名實參。關鍵字實參之間的順序可以任意。

關鍵字實參必須出現在關鍵字實參右側,否則報錯「positional argument follows keyword argument」。

函數形參可分為

  • 無預設值的形參
  • 有預設值的形參
  • *args
  • **kwargs,稱為「var-keyword argument」。
  • *(單個星號)
  • /(單個斜槓)
def foo(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
     -------------  -------------  -------------
       |             |                  |
       |        Positional or keyword   |
       |                                - Keyword only
        -- Positional only
def bar(param1, param2=default_value,*args, kwd1, **kwargs):

函數的形參表必須遵守下屬規則:

  • 出現在*args或者*之後的形參都只能對應命名實參
  • 帶預設值的形參,其右側一直到*的所有參數都必須是有預設值形參。參數的預設值在函數定義時從左至右求值。
  • 形參中沒有名字的單獨一個星號*,只是起到分隔符作用,並不對應於實參,表示實參表此處向右都必須是命名實參。這適用於當名稱有意義並且函數定義通過顯式名稱更容易理解時,只用關鍵字實參;或者您希望防止用戶依賴所傳遞參數的位置。
  • 從Python 3.8開始,形參中沒有名字的單獨一個斜槓/,只是起到分隔符作用,並不對應於實參,表示實參表此處向左都必須是位置形參。這適用於調用函數時強制實參的求值順序;或者以後修改形參名字但不影響調用者使用。「僅是位置形參」(Positional only)的名字將不再嘗試與關鍵字實參的名字匹配,這意味著同名的關鍵字實參可以被**kwargs收集,這稱為Recap。
  • 形參**kwargs必須出現在*args的右側,否則報錯「invalid syntax」
  • 形參**kwargs必須是最後一個形參,否則報錯「arguments cannot follow var-keyword argument」
  • 通常,不在一個函數同時使用*args**kwargs,以免混淆。

調用函數時,實參可分為四類:

  1. 位置實參
  2. 關鍵字實參,形如 name=value
  3. 單星號解包實參,形如*args
  4. 雙星號解包實參,形如**kwargs

實參在實參表中出現的規則:

  1. 實參表可分為左、中、右三部分,任何部分都可以為空
  2. 實參表左部分,任意數量的位置實參可以和任意數量的單星號解包實參混雜出現;
  3. 實參表中部分,任意數量的關鍵字實參可以和任意數量的單星號解包實參混雜出現;
  4. 實參表右部分,任意數量的關鍵字實參可以和任意數量的雙星號解包實參混雜出現。

函數調用時,形實參數結合的規則:

  1. 首先形參表的「僅位置形參」與位置實參從左向右結合。
  2. 其次形參表的「僅關鍵字形參」與關鍵字實參實參結合。
  3. 再次對於「可位置可關鍵字形參」:
    1. 首先與關鍵字實參匹配。
    2. 其次對於不能匹配關鍵字實參的「可位置可關鍵字形參」,再與位置實參匹配。
    3. 最後還剩下未匹配的位置實參組成一個元組收集到*args形參,還剩下未匹配的關鍵字實參組成一個字典收集到**kwargs形參。

形參實參結合時一些語法報錯情形:

  • 已經匹配的非關鍵字形參,其對應的實參的名字不能再作為關鍵字實參出現,否則報錯「duplicate value for the same argument」或「got multiple values for argument」。
    • 特例:「僅是位置形參」(Positional only)的名字將不再嘗試與關鍵字實參的名字匹配,這意味著同名的關鍵字實參可以被**kwargs收集,這稱為Recap。
  • 如果沒有**kwargs形參,關鍵字實參的名字不出現在形參表中則報錯「an unexpected keyword argument」
  • 不能被匹配的形參,將報錯「missing 1 required positional argument」或「missing 1 required keyword-only argument」
def test1(a, b, c=0, *args, **kwargs):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kwargs)
 
def test2(a, b, c=0, *args, d, **kwargs):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'args=', args, 'kw =', kwargs)
 
# 定义一个元组和字典用作参数传入
args = (1, 2, 3, 4)
kw = {'d': 99, 'x': '#'}
 
test1(*args, **kw)
# a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
test2(*args, **kw)
#a = 1 b = 2 c = 3 d = 99 args= (4,) kw = {'x': '#'}

def test3(a, *args,d, c=0):
    print('a =', a, 'c =', c, 'd =', d, 'args=', args)
args=(1,3,5)
test3(-1, d=101, *args) # 这是关键字实参与单星号解包实参混杂情形
#输出为:a = -1 c = 0 d = 101 args= (1, 3, 5)

函數返回多個值

[編輯]

返回多個值時自動作為一個tuple。函數調用者可以使用多個變量來接受多個返回值(即tuple中的各個成分)。

嵌套函數

[編輯]

嵌套函數(nested function),需要注意:

  • 內部函數可以讀寫訪問外部函數的可變(mutable)變量(如 list、dict 等)
  • 內部函數可以訪問外部函數的不可變(immutable)變量(如 int、str、tuple 等)
  • 內部函數使用nonlocal關鍵字,可以讀寫訪問任意層次的外部函數的不可變(immutable)變量
  • 內部函數訪問的外部函數的變量,可以定義在內部函數之後。

函數閉包

[編輯]

閉包是指嵌套函數作為外部函數的返回值。因為函數是頭等對象,所以函數對象可以作為實參、返回值。嵌套函數可以使用外部函數的變量,即使外部函數已經運行結束了。例如

 def adder(outer_argument): # outer function
   def adder_inner(inner_argument): # inner function, nested function
     return outer_argument + inner_argument # Notice outer_argument
   return adder_inner
 add5 = adder(5) # a function that adds 5 to its argument
 add7 = adder(7) # a function that adds 7 to its argument
 print add5(3) # prints 8
 print add7(3) # prints 10

匿名函數

[編輯]

匿名函數的語法如下:

 lambda 可选的形参表:expression

lambda的主體是一個表達式,而不是一個代碼塊。僅僅能在lambda表達式中封裝有限的邏輯進去。lambda 函數擁有自己的命名空間,可以訪問包含它的作用域中的變量。

def make_incrementor(n):
   return lambda x: x + n

偏函數

[編輯]

對一個函數的部分參數綁定值後,得到的函數為偏函數:

偏函数名 = partial(func, *args, **kwargs)

其中,func 指的是要封裝的原函數,*args 和 **kwargs 分別用於接收非命名實參和命名實參。

from functools import partial

#定义个原函数
def display(name,age):
    print("name:",name,"age:",age)

#定义偏函数,其封装了 display() 函数,并为 name 参数设置了默认参数
GaryFun = partial(display,name = 'Gary')
#由于 name 参数已经有默认值,因此调用偏函数时,可以不指定
GaryFun(age = 13) #必须以命名实参方式传入,否则会报错:TypeError: display() got multiple values for argument 'name'

def mod( n, m ):
    return n % m

#定义偏函数,并设置参数 n 对应的实参值为 100
mod_by_100 = partial( mod, 100 )
print(mod( 100, 7 ))
print(mod_by_100( 7 ))

eval()與exec()

[編輯]
eval(source, globals=None, locals=None, /)
exec(source, globals=None, locals=None, /)

expression參數是一個字符串,代表要執行的語句 。該語句受後面兩個字典類型參數 globals 和 locals 的限制,只有在 globals 字典和 locals 字典作用域內的函數和變量才能被執行。

globals參數管控的是一個全局的命名空間,即 expression 可以使用全局命名空間中的函數。如果只是提供了 globals 參數,而沒有提供自定義的 __builtins__,則系統會將當前環境中的 __builtins__ 複製到自己提供的 globals 中,然後才會進行計算;如果連 globals 這個參數都沒有被提供,則使用 Python 的全局命名空間。

locals參數管控的是一個局部的命名空間,和 globals 類似,當它和 globals 中有重複或衝突時,以 locals 的為準。如果 locals 沒有被提供,則默認為 globals。

eval() 執行完會返回結果,而 exec() 執行完不返回結果

map()、filter()、reduce()

[編輯]

由於 map() 函數是直接由用 C 語言寫的,運行時不需要通過 Python 解釋器間接調用,並且內部做了諸多優化,所以相比其他方法,此方法的運行效率最高。

reduce() 函數在 Python 3.x 中已經被移除,放入了 functools 模塊,因此在使用該函數之前,需先導入 functools 模塊。

函數註解

[編輯]

「函數註解是關於用戶自定義函數使用類型的可選的元信息」。也就是說,函數註解的用途為「函數中的形參和返回值提供類型提示信息」。注意,函數註解沒有任何語法上的意義,只是為函數參數和返回值做註解,並在運行獲取這些註解,僅此而已。換句話說,為函數做的註解,Python不做檢查,不做強制,不做驗證,什麼操作都不做,函數註解對Python解釋器沒任何意義。

    def f(ham:str,egg:str='eggs')->str:
      pass
    print(f.__annotations__)

#输出结果为:{'ham': <class 'str'>, 'egg': <class 'str'>, 'return': <class 'str'>}