Author: ACatSmiling
Since: 2024-09-27
函式簡介
函式
:也是一個物件,物件是記憶體中專門用來儲存資料的一塊區域。函式可以用來儲存一些可執行的程式碼,並且可以在需要時,對這些語句進行多次的呼叫。
建立函式:
def 函式名([形參1, 形參2, ... 形參n]):
程式碼塊
- 函式名必須要符號識別符號的規範(可以包含字母、數字、下劃線,但是不能以數字開頭)。
- 函式中儲存的程式碼不會立即執行,需要呼叫函式程式碼才會執行。
呼叫函式:
函式名()
- 定義函式一般都是要實現某種功能的。
示例:
# 定義一個函式
def fn():
print('這是我的第一個函式!')
print('hello')
print('今天天氣真不錯!')
# 列印 fn
print(fn) # <function fn at 0x00000175FD0261F0>
print(type(fn)) # <class 'function'>
# fn 是函式物件,fn() 呼叫函式
# print 是函式物件,print() 呼叫函式
fn()
# 定義一個函式,可以用來求任意兩個數的和
def sum():
a = 123
b = 456
print(a + b)
sum() # 579
# 定義函式時指定形參
def fn2(a, b):
print(a, "+", b, "=", a + b)
# 呼叫函式時,來傳遞實參
fn2(10, 20) # 10 + 20 = 30
fn2(123, 456) # 123 + 456 = 579
函式的引數
在定義函式時,可以在函式名後的()
中定義數量不等的形參,多個形參之間使用,
隔開。
形參(形式引數)
:定義形參就相當於在函式內部宣告瞭變數,但是並不賦值。實參(實際引數)
:呼叫函式時,傳入的真正的引數值。- 如果函式定義時,指定了形參,那麼在呼叫函式時也必須傳遞實參, 實參將會賦值給對應的形參,簡單來說,有幾個形參就得傳幾個實參。
# 求任意三個數的乘積
def mul(a, b, c):
print(a * b * c)
mul(1, 2, 3) # 6
# 根據不同的使用者名稱顯示不同的歡迎資訊
def welcome(username):
print('歡迎 ', username, ' 光臨')
welcome('孫悟空') # 歡迎 孫悟空 光臨
引數的傳遞方式
函式的傳參方式有多種:
預設值引數
:定義形參時,可以為形參指定預設值。指定了預設值以後,如果使用者傳遞了引數,則預設值沒有任何作用;如果使用者沒有傳遞引數,則預設值就會生效。位置引數
:即將對應位置的實參複製給對應位置的形參。關鍵字引數
:可以不按照形參定義的順序去傳遞,而直接根據引數名去傳遞引數。- 位置引數和關鍵字引數可以混合使用,混合使用時,必須將位置引數寫到前面。
- 函式在呼叫時,解析器不會檢查實參的型別,實參可以傳遞任意型別的物件。
- 在函式中對形參進行重新賦值,不會影響其他的變數。
- 如果形參指向的是一個物件,當我們透過形參去修改物件時,會影響到所有指向該物件的變數。
示例:
# 定義函式,併為形參指定預設值
def fn(a=5, b=10, c=20):
print('a =', a)
print('b =', b)
print('c =', c)
fn(1, 2, 3)
fn(1, 2)
fn()
# 位置引數,根據位置傳遞對應的實參
fn(1, 2, 3)
# 關鍵字引數,根據形參名傳遞對應的實參
fn(b=1, c=2, a=3)
print('hello', end='')
# 位置引數和關鍵字引數混合使用,位置引數必須在關鍵字引數前面
fn(1, c=30)
def fn2(a):
print('a =', a)
# 函式在呼叫時,解析器不會檢查實參的型別
# 實參可以傳遞任意型別的物件
b = 123
fn2(b) # a = 123
b = True
fn2(b) # a = True
b = 'hello'
fn2(b) # a = hello
b = None
fn2(b) # a = None
b = [1, 2, 3]
fn2(b) # a = [1, 2, 3]
fn2(fn) # a = <function fn at 0x0000025AB8B561F0>
# 型別不檢查的缺陷,在傳參時需要額外注意
def fn3(a, b):
print(a + b)
# fn3(123, "456") # TypeError: unsupported operand type(s) for +: 'int' and 'str'
# 在函式中對形參進行重新賦值,不會影響其他的變數
def fn4(a):
a = 20
print('a =', a, id(a)) # a = 20 140708519029120
c = 10
fn4(c)
print(c) # 10
# 如果形參指向的是一個物件,當我們透過形參去修改物件時,會影響到所有指向該物件的變數
def fn5(a):
# a 是一個列表,嘗試修改列表中的元素
a[0] = 30
print('a =', a, id(a)) # a = [30, 2, 3] 2056358531968
c = [1, 2, 3]
fn5(c)
print('c =', c, id(c)) # c = [30, 2, 3] 2056358531968
# 透過淺複製,或者切片,實現不修改 c 本身
fn4(c.copy())
fn4(c[:])
Java 的引數傳遞機制
在 Java 中,基本資料型別(如 int、double、char 等)是按值傳遞的,即傳遞的是變數的值的副本。對於引用資料型別(如物件、陣列等),實際上傳遞的是物件引用的副本,但從效果上看,可以透過這個引用副本去操作物件本身。
Python 的引數傳遞機制
Python 的引數傳遞通常被認為是 "物件引用傳遞"。不可變物件(如整數、字串、元組等)在函式內部的行為看起來像是值傳遞,因為不能直接修改不可變物件,一旦嘗試修改,實際上是建立了新的物件。而可變物件(如列表、字典、集合等)在函式內部的修改會影響到外部的物件,因為傳遞的是物件的引用。
二者對比
基本資料型別的傳遞:
-
Java:對於基本資料型別(如 int、double、char 等),Java 是嚴格的值傳遞。這意味著當把一個基本資料型別的變數作為引數傳遞給方法時,會將該變數的值複製一份傳遞過去,在方法內部對這個引數的修改不會影響到原始變數的值。
public class JavaValuePassing { public static void main(String[] args) { int num = 10; changeNumber(num); System.out.println(num); // 輸出仍然是 10 } public static void changeNumber(int n) { n = 20; } }
-
Python:Python 中沒有嚴格意義上的基本資料型別和引用資料型別的區分。但對於不可變的基本資料型別類似物(如整數、字串等),在函式呼叫時的行為有點類似於值傳遞。然而,實際上是物件引用的傳遞,只是因為不可變物件一旦被修改就會建立新的物件,所以看起來像是值傳遞。
def change_number(num): num = 20 a = 10 change_number(a) print(a) # 輸出仍然是 10
引用資料型別的傳遞:
-
Java:對於引用資料型別(如物件、陣列等),Java 傳遞的是物件引用的副本。這意味著在方法內部可以透過這個引用副本去操作物件本身,修改物件的屬性會影響到外部的物件。但是,如果將這個引用重新指向一個新的物件,不會影響到原始的引用。
class Person { String name; public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } public class JavaValuePassing { public static void main(String[] args) { Person person = new Person("Alice"); changePerson(person); System.out.println(person.getName()); // 可能輸出修改後的名字 } public static void changePerson(Person p) { p.setName("Bob"); // 如果重新建立一個新的物件並賦值給引數 p // p = new Person("Charlie"); 這樣不會影響到外部的 person 引用 } }
-
Python:對於可變的引用資料型別類似物(如列表、字典、集合等),傳遞的是物件引用。在函式內部對這個物件的修改會影響到外部的物件。對於不可變的引用資料型別類似物(如元組),雖然傳遞的也是物件引用,但由於元組本身不可變,在函式內部無法修改其內容。
def change_list(lst): lst.append(20) my_list = [1, 2, 3] change_list(my_list) print(my_list) # 輸出為 [1, 2, 3, 20]
小結:
- Java:
- Java 的值傳遞機制相對較為明確和嚴格,基本資料型別和引用資料型別的傳遞方式有明顯的區分,並且對於引用資料型別的操作有一定的限制,不會因為意外的重新賦值而影響到外部的引用。
- 開發者在使用時可以比較清楚地知道引數傳遞的效果,不容易出現意外的錯誤。
- Python:
- Python 的引數傳遞機制更加靈活,但也可能因為這種靈活性而導致一些難以察覺的錯誤。開發者需要根據物件的可變性來理解引數傳遞的行為,並且要注意在函式內部對可變物件的修改可能會影響到外部的物件。
- Python 的這種特性在某些情況下可以使程式碼更加簡潔和高效,但也需要開發者更加小心地處理引數傳遞和物件的修改。
不定長的引數
在定義函式時,可以在形參前邊加上一個*
,這樣這個形參將會獲取到所有的實參,並將所有的實參儲存到一個元組中。
- 帶
*
的形參只能有一個。 - 帶
*
的引數,可以和其他引數配合使用。 - 可變引數不是必須寫在最後,但是注意,帶
*
的引數後的所有引數,必須以關鍵字引數的形式傳遞,否則報錯。 - 如果在形參的開頭直接寫一個
*
,則要求所有的引數必須以關鍵字引數的形式傳遞。 *
形參只能接收位置引數,而不能接收關鍵字引數。**
形參可以接收其他的關鍵字引數,它會將這些引數統一儲存到一個字典中。字典的 key 就是引數的名字,字典的 value 就是引數的值。**
形參只能有一個,並且必須寫在所有引數的最後。- 傳遞實參時,也可以在序列型別的引數前新增
*
,這樣會自動將序列中的元素依次作為引數傳遞給函式,但要求序列中元素的個數必須和形參的個數一致。 - 如果是字典,透過
**
來進行解包操作。
# 定義一個函式,可以求任意個數字的和
def sum(*nums):
# 定義一個變數,來儲存結果
result = 0
# 遍歷元組,並將元組中的數進行累加
for n in nums:
result += n
print(result)
sum(10, 20, 30, 40) # 100
sum(10, 20, 30, 40, 50, 60, 70) # 280
# *a 會接受所有的位置實參,並且會將這些實參統一儲存到一個元組中(裝包)
def fn(*a):
print("a =", a, type(a))
fn(1, 2, 3) # a = (1, 2, 3) <class 'tuple'>
fn(1, 2, 3, 4, 5) # a = (1, 2, 3, 4, 5) <class 'tuple'>
# 帶星號的形參只能有一個
# 帶星號的引數,可以和其他引數配合使用
# 下面的函式,第一個引數給a,第二個引數給b,剩下的都儲存到c的元組中
def fn2(a, b, *c):
print('a =', a)
print('b =', b)
print('c =', c)
fn2(1, 2, 3, 4, 5)
# 可變引數不是必須寫在最後,但是注意,帶 * 的引數後的所有引數,必須以關鍵字引數的形式傳遞
# 下面的函式,第一個引數給 a,剩下的位置引數給 b 的元組,c 必須使用關鍵字引數
def fn3(a, *b, c):
print('a =', a)
print('b =', b)
print('c =', c)
fn3(1, 2, 3, 4, c=5)
# 下面的函式,所有的位置引數都給 a,b 和 c 必須使用關鍵字引數
def fn4(*a, b, c):
print('a =', a)
print('b =', b)
print('c =', c)
fn4(1, 2, 3, b=4, c=5)
# 如果在形參的開頭直接寫一個 *,則要求我們的所有的引數必須以關鍵字引數的形式傳遞
def fn5(*, a, b, c):
print('a =', a)
print('b =', b)
print('c =', c)
fn5(a=3, b=4, c=5)
# * 形參只能接收位置引數,而不能接收關鍵字引數
# def fn3(*a) :
# print('a =',a)
# ** 形參可以接收其他的關鍵字引數,它會將這些引數統一儲存到一個字典中
# 字典的 key 就是引數的名字,字典的 value 就是引數的值
# ** 形參只能有一個,並且必須寫在所有引數的最後
def fn6(b, c, **a):
print('a =', a, type(a))
print('b =', b)
print('c =', c)
fn6(b=1, c=2, h=3, e=10, f=20)
fn6(6, 7, g=1, d=2, h=3, e=10, f=20)
print('##########################################################')
# 引數的解包(拆包)
def fn7(a, b, c):
print('a =', a)
print('b =', b)
print('c =', c)
# 傳遞實參時,也可以在序列型別的引數前新增星號,這樣他會自動將序列中的元素依次作為引數傳遞給函式
# 這裡要求序列中元素的個數必須和形參的個數的一致
t = (10, 20, 30)
fn7(*t)
# 透過 **來對一個字典進行解包操作
d = {'a': 100, 'b': 200, 'c': 300}
fn7(**d)
函式的返回值
函式的返回值就是函式執行以後返回的結果,可以透過return
來指定函式的返回值。
- return 後邊可以跟任意的物件,甚至可以是一個函式。return 後邊跟什麼值,函式就會返回什麼值。
- 如果僅僅寫一個 return 或者不寫 return,則相當於 return None。
- 在函式中,return 後的程式碼都不會執行,return 一旦執行函式自動結束。
示例:
# return 後邊跟什麼值,函式就會返回什麼值
# return 後邊可以跟任意的物件,返回值甚至可以是一個函式
def fn():
# return 'Hello'
# return [1, 2, 3]
# return {'k': 'v'}
def fn1():
print('hello')
return fn1 # 返回值也可以是一個函式
r = fn() # 這個函式的執行結果就是它的返回值
print(r) # <function fn.<locals>.fn1 at 0x000001F3430BCF70>
r() # hello
# 如果僅僅寫一個 return 或者不寫 return,則相當於 return None
# 在函式中,return 後的程式碼都不會執行,return 一旦執行函式自動結束
def fn2():
a = 10
return
print('abc') # 不會執行
r = fn2()
print(r) # None
def fn3():
for i in range(5):
if i == 3:
# break 用來退出當前迴圈
# continue 用來跳過當次迴圈
return # return 用來結束函式
print(i)
print('迴圈執行完畢!')
fn3()
def fn4():
return 10
# fn4 和 fn4()的區別
print(fn4) # fn4 是函式物件,列印 fn4 實際是在列印函式物件:<function fn5 at 0x00000229B3CFD670>
print(fn4()) # fn4() 是在呼叫函式,列印 fn4() 實際上是在列印 fn4() 函式的返回值:10
文件字串
文件字串
:在定義函式時,可以在函式內部編寫文件字串,文件字串就是函式的說明。文件字串非常簡單,其實直接在函式的第一行寫一個字串就是文件字串,當我們編寫了文件字串時,就可以透過help()
函式來檢視函式的說明。
示例:
# help() 是 Python 中的內建函式
# 透過 help() 函式可以查詢 Python 中的函式的用法
# 語法:help(函式物件)
help(print) # 獲取 print() 函式的使用說明
# 文件字串(doc str)
def fn(a: int, b: bool, c: str = 'hello') -> int: # 函式引數後跟著型別,返回值是一個 int
"""
這是一個文件字串的示例
函式的作用:。。。。。。
函式的引數:
a,作用,型別,預設值。。。
b,作用,型別,預設值。。。
c,作用,型別,預設值。。。
"""
return 10
help(fn)
作用域
作用域(scope)
:指的是變數生效的區域。在 Python 中一共有兩種作用域:
全域性作用域
:全域性作用域在程式執行時建立,在程式執行結束時銷燬。- 所有函式以外的區域都是全域性作用域。
- 在全域性作用域中定義的變數,都屬於全域性變數,全域性變數可以在程式的任意位置被訪問。
函式作用域
:函式作用域在函式呼叫時建立,在呼叫結束時銷燬。- 函式每呼叫一次就會產生一個新的函式作用域。
- 在函式作用域中定義的變數,都是區域性變數,它只能在函式內部被訪問。
- 如果希望在函式內部修改全域性變數,則需要使用 global 關鍵字,來宣告變數。
變數的查詢過程:當我們使用變數時,會優先在當前作用域中尋找該變數,如果有則使用;如果沒有則繼續去上一級作用域中尋找,如果有則使用;如果依然沒有則繼續去上一級作用域中尋找,以此類推,直到找到全域性作用域,依然沒有找到,則會丟擲異常 "NameError: name 'a' is not defined"。
示例:
b = 20 # 全域性變數
def fn():
a = 10 # a 定義在了函式內部,所以他的作用域就是函式內部,函式外部無法訪問
print('函式內部:', 'a =', a)
print('函式內部:', 'b =', b)
fn()
# print('函式外部:', 'a =', a) # NameError: name 'a' is not defined
print('函式外部:', 'b =', b)
def fn1():
def fn2():
print('fn3中:', 'a =', a)
fn2()
# fn1() # fn1 中的巢狀函式 fn2,找不到 a,報錯 NameError: name 'a' is not defined
a = 20
def fn3():
# a = 10 # 在函式中為變數賦值時,預設都是為區域性變數賦值
# 如果希望在函式內部修改全域性變數,則需要使用 global 關鍵字,來宣告變數
global a # 宣告在函式內部使用的 a 是全域性變數,此時再去修改 a 時,就是在修改全域性的 a
a = 10 # 修改全域性變數
print('函式內部:', 'a =', a)
fn3()
print('函式外部:', 'a =', a) # 函式外部: a = 10
名稱空間
名稱空間(namespace)
:指的是變數儲存的位置,每一個變數都需要儲存到指定的名稱空間當中。
- 每一個作用域都會有一個它對應的名稱空間。
- 全域性名稱空間,用來儲存全域性變數;函式名稱空間,用來儲存函式中的變數。
- 名稱空間實際上就是一個字典,是一個專門用來儲存變數的字典。
- 使用
locals()
函式,可以獲取當前作用域的名稱空間,其返回值是一個字典。- 如果在全域性作用域中呼叫 locals() 則獲取全域性名稱空間,如果在函式作用域中呼叫 locals() 則獲取函式名稱空間。
- 使用
globals()
函式,可以用來在任意位置獲取全域性名稱空間。
示例:
scope = locals() # 當前名稱空間
print(type(scope)) # <class 'dict'>
# 下面兩個列印效果相同
a = 20
print(a) # 20
print(scope['a']) # 20
# 向 scope 中新增一個 key-value
# scope['c'] = 1000 # 向字典中新增 key-value 就相當於在全域性中建立了一個變數(一般不建議這麼做)
# print(c) # 1000
def fn():
a = 10
scope = locals() # 在函式內部呼叫 locals() 會獲取到函式的名稱空間
print(type(scope)) # <class 'dict'>
# scope['b'] = 90 # 可以透過 scope 來操作函式的名稱空間,但是也是不建議這麼做
# print(b)
# globals() 函式可以用來在任意位置獲取全域性名稱空間
# 函式外面無法獲得函式的名稱空間
global_scope = globals()
print(global_scope['a']) # 20
# global_scope['a'] = 30 # 不建議這麼做
# print(global_scope['a']) # 30
fn()
遞迴
遞迴
:遞迴是解決問題的一種方式,它和迴圈很像,它的整體思想是,將一個大問題分解為一個個的小問題,直到問題無法分解時,再去解決問題。
遞迴式函式的兩個要件:
- 基線條件:問題可以被分解為的最小問題,當滿足基線條件時,遞迴就不在執行了。
- 遞迴條件:將問題繼續分解的條件。
示例:
# 10 的階乘
n = 10
for i in range(1, 10):
n *= i
print(n)
# 建立一個函式,可以用來求任意數的階乘
def factorial(n):
'''
該函式用來求任意數的階乘
引數:
n 要求階乘的數字
'''
# 建立一個變數,來儲存結果
result = n
for i in range(1, n):
result *= i
return result
print(factorial(10))
# 遞迴簡單理解就是自己去引用自己!
# 遞迴式函式,在函式中自己呼叫自己!
# 下面這個是無窮遞迴,如果這個函式被呼叫,程式的記憶體會溢位,效果類似於死迴圈
# def fn():
# fn()
# fn()
# 遞迴和迴圈類似,基本是可以互相代替的,
# 迴圈編寫起來比較容易,閱讀起來稍難
# 遞迴編寫起來難,但是方便閱讀
def factorial(n):
# 基線條件:判斷 n 是否為 1,如果為 1,則此時不能再繼續遞迴
if n == 1:
# 1 的階乘就是 1,直接返回 1
return 1
# 遞迴條件
return n * factorial((n - 1))
print(factorial(10))
# 建立一個函式 power,來為任意數字做冪運算 n ** i
def power(n, i):
'''
power() 用來為任意的數字做冪運算
引數:
n 要做冪運算的數字
i 做冪運算的次數
'''
# 基線條件
if i == 1:
# 求1次冪
return n
# 遞迴條件
return n * power(n, i - 1)
print(pow(3, 4))
# 建立一個函式,用來檢查一個任意的字串是否是迴文字串,如果是返回 True,否則返回 False
# 迴文字串,字串從前往後念和從後往前念是一樣的
# abcba
# abcdefgfedcba
# 先檢查第一個字元和最後一個字元是否一致,如果不一致則不是迴文字串
# 如果一致,則看剩餘的部分是否是迴文字串
# 檢查 abcdefgfedcba 是不是迴文
# 檢查 bcdefgfedcb 是不是迴文
# 檢查 cdefgfedc 是不是迴文
# 檢查 defgfed 是不是迴文
# 檢查 efgfe 是不是迴文
# 檢查 fgf 是不是迴文
# 檢查 g 是不是迴文
def hui_wen(s):
'''
該函式用來檢查指定的字串是否迴文字串,如果是返回 True,否則返回 False
引數:
s:就是要檢查的字串
'''
# 基線條件
if len(s) < 2:
# 字串的長度小於 2,則字串一定是迴文
return True
elif s[0] != s[-1]:
# 第一個字元和最後一個字元不相等,不是迴文字串
return False
# 遞迴條件
return hui_wen(s[1:-1])
# def hui_wen(s):
# '''
# 該函式用來檢查指定的字串是否迴文字串,如果是返回 True,否則返回 False
# 引數:
# s:就是要檢查的字串
# '''
# # 基線條件
# if len(s) < 2 :
# # 字串的長度小於 2,則字串一定是迴文
# return True
# # 遞迴條件
# return s[0] == s[-1] and hui_wen(s[1:-1])
print(hui_wen('abba'))
print(hui_wen('abcdefgfedcba'))
高階函式
高階函式
:接收函式作為引數,或者將函式作為返回值的函式是高階函式。當我們使用一個函式作為引數時,實際上是將指定的程式碼傳遞進了目標函式。
在 Python 中,函式是一等物件。一等物件一般都會具有如下特點:
- 物件是在執行時建立的。
- 能賦值給變數或作為資料結構中的元素。
- 能作為引數傳遞。
- 能作為返回值返回。
高階函式至少要符合以下兩個特點中的一個:
- 能接收一個或多個函式作為引數。
- 能將函式作為返回值返回。
示例:
# 定義一個函式,功能:可以將指定列表中的所有的偶數,儲存到一個新的列表中返回
# 待提取列表
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 常規寫法
def fn(lst):
'''
fn() 函式可以將指定列表中的所有偶數提取出來,並儲存到一個新列表中返回
引數:
lst:指定的要篩選的列表
'''
new_list = []
for n in lst:
if n % 2 == 0:
new_list.append(n)
return new_list
print(fn(l)) # [2, 4, 6, 8, 10]
# 高階函式
# 功能 1:定義一個函式,用來檢查一個任意的數字是否是偶數
def fn1(i):
if i % 2 == 0:
return True
return False
# 功能 2:定義一個函式,用來檢查指定的數字是否大於 5
def fn2(i):
if i > 5:
return True
return False
# 功能 2:定義一個函式,用來檢查一個任意的數字是否能被 3 整除
def fn3(i):
return i % 3 == 0
# 多功能的高階函式
def fn(func, lst):
'''
fn() 函式可以將指定列表中的資料按指定函式要求提取出來,並儲存到一個新列表中返回
引數:
func:指定的提取要求
lst:指定的要篩選的列表
'''
new_list = []
for n in lst:
if func(n):
new_list.append(n)
return new_list
print(fn(fn1, l)) # 獲取偶數:[2, 4, 6, 8, 10]
print(fn(fn2, l)) # 獲取大於 5 的數:[6, 7, 8, 9, 10]
print(fn(fn3, l)) # 獲取能被 3 整除的數:[3, 6, 9]
# filter() 函式的功能,就如上面自定義的 fn() 函式
# filter() 可以從序列中過濾出符合條件的元素,儲存到一個新的序列中
# 引數:
# 1. 函式,根據該函式來過濾序列(可迭代的結構)
# 2. 需要過濾的序列(可迭代的結構)
# 返回值:
# 過濾後的新序列(可迭代的結構)
iterator = filter(fn1, l)
# for n in iterator:
# print(n)
print(list(iterator)) # 返回的是一個可迭代的結構,需要轉換成 list 才能直接列印出來資料
print(list(filter(fn2, l)))
print(list(filter(fn3, l)))
匿名函式
匿名函式
:將一個或多個函式作為引數來接收。匿名函式一般都是作為引數使用,其他地方一般不會使用。
語法:lambda 引數列表 : 返回值
。
示例:
# 在前面的示例中,fn1 ~ fn3 是作為引數傳遞進 filter() 函式中
# 而 fn1 ~ fn3 實際上只有一個作用,就是作為 filter() 的引數
# filter() 呼叫完畢以後,fn1 ~ fn3 就已經沒用
# 這種情況可以用匿名函式簡化
# 匿名函式 lambda 函式表示式(語法糖)
# lambda 函式表示式專門用來建立一些簡單的函式,他是函式建立的又一種方式
# 語法:lambda 引數列表 : 返回值
# 匿名函式一般都是作為引數使用,其他地方一般不會使用
# 待提取列表
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 常規寫法
def fn5(a, b):
return a + b
print(fn5(10, 20)) # 常規寫法
print((lambda a, b: a + b)(10, 20)) # lambad 表示式寫法
fn6 = lambda a, b: a + b # 也可以將匿名函式賦值給一個變數,一般不會這麼做
print(fn6(10, 20))
# filter() 函式中可以很方便的使用 lambda 表示式
# 此時,lambda 表示式只會使用一次,使用完後記憶體中自動回收
r = filter(lambda i: i % 2 == 0, l) # lambda i: i % 2 == 0 是匿名函式
print(list(r)) # [2, 4, 6, 8, 10]
print(list(filter(lambda i: i > 5, l))) # [6, 7, 8, 9, 10],lambda i: i > 5 是匿名函式
# map() 函式可以對可迭代物件中的所有元素做指定的操作,然後將其新增到一個新的物件中返回
print(list(map(lambda i: i ** 2, l))) # 對列表中的每一個元素求平方,[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# sort() 函式用來對列表中的元素進行排序:
# sotr() 只能排序列表
# 預設是直接比較列表中的元素的大小
# 在 sort() 可以接收一個關鍵字引數 key:
# key 需要一個函式作為引數,當設定了函式作為引數
# 每次都會以列表中的一個元素作為引數來呼叫該函式
# 並且使用函式的返回值來比較元素的大小
l = ['bb', 'aaaa', 'c', 'ddddddddd', 'fff']
l.sort()
print(l) # 預設比較:['aaaa', 'bb', 'c', 'ddddddddd', 'fff']
l = ['bb', 'aaaa', 'c', 'ddddddddd', 'fff']
l.sort(key=len)
print(l) # 按長度比較:['c', 'bb', 'fff', 'aaaa', 'ddddddddd']
l = [2, 5, '1', 3, '6', '4']
l.sort(key=int)
print(l) # 把每一個元素轉換成整形後再比較:['1', 2, 3, '4', 5, '6']
l = [2, 5, '1', 3, '6', '4']
l.sort(key=str)
print(l) # 把每一個元素轉換成字串後再比較:['1', 2, 3, '4', 5, '6']
# sorted() 函式和 sort() 的用法基本一致,但是 sorted() 可以對任意的序列進行排序
# 並且使用 sorted() 排序不會影響原來的物件,而是返回一個新物件
l = [2, 5, '1', 3, '6', '4'] # 排序列表
print('排序前:', l) # 排序前:[2, 5, '1', 3, '6', '4']
print('排序中:', sorted(l, key=int)) # 排序中:['1', 2, 3, '4', 5, '6']
print('排序後:', l) # 排序後:[2, 5, '1', 3, '6', '4']
l = '123765816742634781' # 排序字串
print('排序前:', l) # 排序前:123765816742634781
print('排序中:', sorted(l, key=int)) # 排序中:['1', '1', '1', '2', '2', '3', '3', '4', '4', '5', '6', '6', '6', '7', '7', '7', '8', '8']
print('排序後:', l) # 排序後:123765816742634781
閉包
閉包
:是指在一個函式內部定義另一個函式,並且內部函式可以訪問外部函式的區域性變數。即使外部函式已經執行完畢,內部函式仍然可以訪問外部函式的區域性變數。
形成閉包的要件:
- 函式巢狀。
- 將內部函式作為返回值返回。
- 內部函式必須要使用到外部函式的變數。
示例:
# 將函式作為返回值返回,也是一種高階函式
# 這種高階函式我們也稱為叫做閉包,透過閉包可以建立一些只有當前函式能訪問的變數
# 可以將一些私有的資料藏到閉包中
def fn():
a = 10
# 函式內部再定義一個函式
def inner():
print('我是fn2', a)
# 將內部函式 inner 作為返回值返回
return inner
# r 是一個函式,是呼叫 fn() 後返回的函式
# 而且這個函式是在 fn() 內部定義,並不是全域性函式
# 所以這個函式總是能訪問到 fn() 函式內的變數
r = fn()
print(r) # <function fn.<locals>.inner at 0x000001CEC1142430>
r() # 我是fn2 10
# 求多個數的平均值
nums = [50, 30, 20, 10, 77]
# 常規寫法:sum() 用來求一個列表中所有元素的和
print(sum(nums) / len(nums)) # 37.4
# 如果 nums 中的資料是變化的,可以使用閉包
def make_averager():
# 建立一個列表,用來儲存數值
nums = []
# 建立一個函式,用來計算平均值
def averager(n):
# 將n新增到列表中
nums.append(n)
# 求平均值
return sum(nums) / len(nums)
return averager
# 函式返回的是 make_averager() 中定義的 averager() 函式
# 並建立了一個 nums 列表,這個列表外界無法訪問,只有 averager 物件可以訪問
averager = make_averager()
print(averager(10)) # 10/1=10
print(averager(20)) # (10+20)/2=15
print(averager(30)) # (10+20+30)/3=20
print(averager(40)) # (10+20+30+40)/4=25
裝飾器
# 建立幾個函式
def add(a, b):
'''
求任意兩個數的和
'''
r = a + b
return r
def mul(a, b):
'''
求任意兩個數的積
'''
r = a * b
return r
print(add(123, 456)) # 579
print(mul(10, 20)) # 200
# 現在,希望函式可以在計算前,列印開始計算,計算結束後列印計算完畢
# 我們可以直接透過修改函式中的程式碼來完成這個需求,但是會產生以下一些問題
# ① 如果要修改的函式過多,修改起來會比較麻煩
# ② 並且不方便後期的維護
# ③ 並且這樣做會違反開閉原則(OCP)
# 程式的設計,要求開發對程式的擴充套件,要關閉對程式的修改
# 我們希望在不修改原函式的情況下,來對函式進行擴充套件
def fn():
print('我是fn函式....')
# 只需要根據現有的函式,來建立一個新的函式
def fn2():
print('函式開始執行~~~')
fn()
print('函式執行結束~~~')
fn2()
# 建立新函式,擴充套件 add()
def new_add(a, b):
print('計算開始~~~')
r = add(a, b)
print('計算結束~~~')
return r
r = new_add(111, 222)
print(r)
print('##############################################################')
# 上邊的方式,已經可以在不修改原始碼的情況下對函式進行擴充套件了
# 但是,這種方式要求我們每擴充套件一個函式就要手動建立一個新的函式,實在是太麻煩了
# 為了解決這個問題,我們建立一個函式,讓這個函式可以自動的幫助我們生產函式
def begin_end(old):
'''
用來對其他函式進行擴充套件,使其他函式可以在執行前列印開始執行,執行後列印執行結束
引數:
old 要擴充套件的函式物件
'''
# 建立一個新函式,引數的個數是不確定的,使用*和**
def new_function(*args, **kwargs):
print('開始執行~~~~')
# 呼叫被擴充套件的函式
result = old(*args, **kwargs)
print('執行結束~~~~')
# 返回函式的執行結果
return result
# 返回新函式
return new_function
f = begin_end(fn) # 包裝 fn()
f2 = begin_end(add) # 包裝 add()
f3 = begin_end(mul) # 包裝 mul()
r = f()
print(r)
r = f2(123, 456)
print(r)
r = f3(123, 456)
print(r)
print('##############################################################')
# 像 begin_end() 這種函式我們就稱它為裝飾器
# 透過裝飾器,可以在不修改原來函式的情況下來對函式進行擴充套件
# 在開發中,我們都是透過裝飾器來擴充套件函式的功能的
# 在定義函式時,可以透過 @裝飾器,來使用指定的裝飾器,來裝飾當前的函式
# 可以同時為一個函式指定多個裝飾器,這樣函式將會安裝從內向外的順序被裝飾
def fn3(old):
'''
用來對其他函式進行擴充套件,使其他函式可以在執行前列印開始執行,執行後列印執行結束
引數:
old 要擴充套件的函式物件
'''
# 建立一個新函式
# *args:old 函式中的位置引數(形如:'a, b'),全都存放其中
# **kwargs:old 函式中的字典引數(形如:'a=x, b=y'),全部存放其中
def new_function(*args, **kwargs):
print('fn3裝飾~開始執行~~~~')
# 呼叫被擴充套件的函式
result = old(*args, **kwargs)
print('fn3裝飾~執行結束~~~~')
# 返回函式的執行結果
return result
# 返回新函式
return new_function
@fn3 # 第一層:fn3 裝飾
@begin_end # :第二層:begin_end 裝飾
def say_hello():
print('大家好~~~')
say_hello()
原文連結
https://github.com/ACatSmiling/zero-to-zero/blob/main/PythonLanguage/python.md