Python學習筆記(4)-函式
函式
函式的簡介
函式也是一個物件,函式可以用來儲存一些可執行的程式碼,並且可以在需要時,對這些語句進行多次的呼叫,函式中儲存的程式碼不會立即執行,需要呼叫函式程式碼才會執行
建立函式:
def 函式名([形參1,形參2,...形參n]) :
程式碼塊
呼叫函式:
函式物件()
函式的引數
- 在定義函式時,可以在函式名後的()中定義數量不等的形參,多個形參之間使用,隔開
- 形參(形式引數),定義形參就相當於在函式內部聲明瞭變數,但是並不賦值
- 實參(實際引數),如果函式定義時,指定了形參,那麼在呼叫函式時也必須傳遞實參,實參將會賦值給對應的形參,簡單來說,有幾個形參就得傳幾個實參
- 定義形參時,可以為形參指定預設值,指定了預設值以後,如果使用者傳遞了引數則預設值沒有任何作用,如果使用者沒有傳遞,則預設值就會生效
實參的傳遞方式
位置引數
位置引數就是將對應位置的實參複製給對應位置的形參,第一個實參賦值給第一個形參,第二個實參賦值給第二個形參...
關鍵字引數
關鍵字引數,可以不按照形參定義的順序去傳遞,而直接根據引數名去傳遞引數
fn(b=1 , c=2 , a=3)
print('hello' , end='')
位置引數和關鍵字引數可以混合使用,混合使用關鍵字和位置引數時,必須將位置引數寫到前面
fn(1,c=30)
不定長的引數
定義一個函式,可以求任意個數字的和
def sum(*nums): # 定義一個變數,來儲存結果 result = 0 # 遍歷元組,並將元組中的數進行累加 for n in nums : result += n print(result) sum(123,456,789,10,20,30,40)
在定義函式時,可以在形參前邊加上一個*,這樣這個形參將會獲取到所有的實參,它將會將所有的實參儲存到一個元組中
a,b,*c = (1,2,3,4,5,6)
*a會接受所有的位置實參,並且會將這些實參統一儲存到一個元組中(裝包)
def fn(*a):
print("a =",a,type(a))
fn(1,2,3,4,5)
帶星號的形參只能有一個
帶星號的引數,可以和其他引數配合使用
第一個引數給a,第二個引數給b,剩下的都儲存到c的元組中
def fn2(a,b,*c):
print('a =',a)
print('b =',b)
print('c =',c)
可變引數不是必須寫在最後,但是注意,帶*的引數後的所有引數,必須以關鍵字引數的形式傳遞
第一個引數給a,剩下的位置引數給b的元組,c必須使用關鍵字引數
def fn2(a,*b,c):
print('a =',a)
print('b =',b)
print('c =',c)
所有的位置引數都給a,b和c必須使用關鍵字引數
def fn2(*a,b,c):
print('a =',a)
print('b =',b)
print('c =',c)
如果在形參的開頭直接寫一個*,則要求我們的所有的引數必須以關鍵字引數的形式傳遞
def fn2(*,a,b,c):
print('a =',a)
print('b =',b)
print('c =',c)
fn2(a=3,b=4,c=5)
*形參只能接收位置引數,而不能接收關鍵字引數
def fn3(*a) :
print('a =',a)
**形參可以接收其他的關鍵字引數,它會將這些引數統一儲存到一個字典中,字典的key就是引數的名字,字典的value就是引數的值
**形參只能有一個,並且必須寫在所有引數的最後
def fn3(b,c,**a) :
print('a =',a,type(a))
print('b =',b)
print('c =',c)
fn3(b=1,d=2,c=3,e=10,f=20)
引數的解包(拆包)
def fn4(a,b,c):
print('a =',a)
print('b =',b)
print('c =',c)
建立一個元組
t = (10,20,30)
傳遞實參時,也可以在序列型別的引數前新增星號,這樣他會自動將序列中的元素依次作為引數傳遞,這裡要求序列中元素的個數必須和形參的個數的一致
fn4(*t)
建立一個字典
d = {'a':100,'b':200,'c':300}
通過**來對一個字典進行解包操作
fn4(**d)
返回值
返回值,返回值就是函式執行以後返回的結果
可以通過 return 來指定函式的返回值
可以之間使用函式的返回值,也可以通過一個變數來接收函式的返回值
def sum(*nums):
# 定義一個變數,來儲存結果
result = 0
# 遍歷元組,並將元組中的數進行累加
for n in nums :
result += n
print(result)
sum(123,456,789)
return 後邊跟什麼值,函式就會返回什麼值
return 後邊可以跟任意的物件,返回值甚至可以是一個函式
def fn():
# return 'Hello'
# return [1,2,3]
# return {'k':'v'}
def fn2() :
print('hello')
return fn2 # 返回值也可以是一個函式
r = fn() 這個函式的執行結果就是它的返回值
如果僅僅寫一個return或者不寫return,則相當於return None
def fn2() :
a = 10
return
在函式中,return後的程式碼都不會執行,return一旦執行函式自動結束
def fn3():
print('hello')
return
print('abc')
r = fn3()
print(r)
def fn4() :
for i in range(5):
if i == 3 :
# break 用來退出當前迴圈
# continue 用來跳過當次迴圈
return # return 用來結束函式
print(i)
print('迴圈執行完畢!')
def sum(*nums):
# 定義一個變數,來儲存結果
result = 0
# 遍歷元組,並將元組中的數進行累加
for n in nums :
result += n
return result
r = sum(123,456,789)
print(r + 778)
def fn5():
return 10
fn和fn()的區別
fn是函式物件,列印fn實際是在列印函式物件
fn()是在呼叫函式,列印fn()實際上是在列印fn()函式的返回值
文件字串
help()是Python中的內建函式
通過help()函式可以查詢python中的函式的用法
語法:help(函式物件)
help(print) # 獲取print()函式的使用說明
文件字串(doc str)
在定義函式時,可以在函式內部編寫文件字串,文件字串就是函式的說明,當我們編寫了文件字串時,就可以通過help()函式來檢視函式的說明
文件字串非常簡單,其實直接在函式的第一行寫一個字串就是文件字串
def fn(a:int,b:bool,c:str='hello') -> int:
'''
這是一個文件字串的示例
函式的作用:。。。。。
函式的引數:
a,作用,型別,預設值。。。。
b,作用,型別,預設值。。。。
c,作用,型別,預設值。。。。
'''
return 10
help(fn)
作用域與名稱空間
作用域指的是變數生效的區域
b = 20 # 全域性變數
def fn():
a = 10 # a定義在了函式內部,所以他的作用域就是函式內部,函式外部無法訪問
print('函式內部:','a =',a)
print('函式內部:','b =',b)
fn()
print('函式外部:','a =',a)
print('函式外部:','b =',b)
在Python中一共有全域性作用域與函式作用域兩種作用域
全域性作用域
- 全域性作用域在程式執行時建立,在程式執行結束時銷燬
- 所有函式以外的區域都是全域性作用域
- 在全域性作用域中定義的變數,都屬於全域性變數,全域性變數可以在程式的任意位置被訪問
函式作用域
- 函式作用域在函式呼叫時建立,在呼叫結束時銷燬
- 函式每呼叫一次就會產生一個新的函式作用域
- 在函式作用域中定義的變數,都是區域性變數,它只能在函式內部被訪問
- 變數的查詢
- 當我們使用變數時,會優先在當前作用域中尋找該變數,如果有則使用,
如果沒有則繼續去上一級作用域中尋找,如果有則使用,如果依然沒有則繼續去上一級作用域中尋找,以此類推,直到找到全域性作用域,依然沒有找到,則會丟擲異NameError: name 'a' is not defined - 如果希望在函式內部修改全域性變數,則需要使用global關鍵字,來宣告變數(global a),宣告在函式內部的使用a是全域性變數,此時再去修改a時,就是在修改全域性的a
名稱空間(namespace)
- 名稱空間指的是變數儲存的位置,每一個變數都需要儲存到指定的名稱空間當中
- 每一個作用域都會有一個它對應的名稱空間
- 全域性名稱空間,用來儲存全域性變數。函式名稱空間用來儲存函式中的變數
- 名稱空間實際上就是一個字典,是一個專門用來儲存變數的字典
- locals()用來獲取當前作用域的名稱空間
- 如果在全域性作用域中呼叫locals()則獲取全域性名稱空間,如果在函式作用域中呼叫locals()則獲取函式名稱空間,返回的是一個字典
- globals() 函式可以用來在任意位置獲取全域性名稱空間
scope = locals() # 當前名稱空間
scope['c'] = 1000 # 向字典中新增key-value就相當於在全域性中建立了一個變數(一般不建議這麼做)
def fn4():
a = 10
scope = locals() # 在函式內部呼叫locals()會獲取到函式的名稱空間
scope['b'] = 20 # 可以通過scope來操作函式的名稱空間,但是也是不建議這麼做
遞迴
嘗試求10的階乘(10!)
1! = 1
2! = 1*2 = 2
3! = 1*2*3 = 6
4! = 1*2*3*4 = 24
print(1*2*3*4*5*6*7*8*9*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(20))
遞迴式的函式
遞迴簡單理解就是自己去引用自己!
遞迴式函式,在函式中自己呼叫自己!
無窮遞迴,如果這個函式被呼叫,程式的記憶體會溢位,效果類似於死迴圈
遞迴是解決問題的一種方式,它和迴圈很像
它的整體思想是,將一個大問題分解為一個個的小問題,直到問題無法分解時,再去解決問題
遞迴式函式的兩個要件
1.基線條件
問題可以被分解為的最小問題,當滿足基線條件時,遞迴就不在執行了
2.遞迴條件
將問題繼續分解的條件
遞迴和迴圈類似,基本是可以互相代替的,迴圈編寫起來比較容易,閱讀起來稍難,遞迴編寫起來難,但是方便閱讀
10! = 10 * 9!
9! = 9 * 8!
8! = 8 * 7!
...
1! = 1
def factorial(n):
'''
該函式用來求任意數的階乘
引數:
n 要求階乘的數字
'''
基線條件 判斷n是否為1,如果為1則此時不能再繼續遞迴
if n == 1 :
# 1的階乘就是1,直接返回1
return 1
# 遞迴條件
return n * factorial(n-1)
print(factorial(10))
建立一個函式 power 來為任意數字做冪運算 n ** i
10 ** 5 = 10 * 10 ** 4
10 ** 4 = 10 * 10 ** 3
...
10 ** 1 = 10
def power(n , i):
'''
power()用來為任意的數字做冪運算
引數:
n 要做冪運算的數字
i 做冪運算的次數
'''
# 基線條件
if i == 1:
# 求1次冪
return n
# 遞迴條件
return n * power(n , i-1)
print(power(8,6))
建立一個函式,用來檢查一個任意的字串是否是迴文字串,如果是返回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('abcdefgfedcba'))
函數語言程式設計
在Python中,函式是一等物件,一等物件一般都會具有如下特點:
- 物件是在執行時建立的
- 能賦值給變數或作為資料結構中的元素
- 能作為引數傳遞
- 能作為返回值返回
高階函式
接收函式作為引數,或者將函式作為返回值的函式是高階函式
當我們使用一個函式作為引數時,實際上是將指定的程式碼傳遞進了目標函式
建立一個列表
l = [1,2,3,4,5,6,7,8,9,10]
定義一個函式
可以將指定列表中的所有的偶數,儲存到一個新的列表中返回
定義一個函式,用來檢查一個任意的數字是否是偶數
def fn2(i) :
if i % 2 == 0 :
return True
return False
這個函式用來檢查指定的數字是否大於5
def fn3(i):
if i > 5 :
return True
return False
def fn(func , lst) :
'''
fn()函式可以將指定列表中的所有偶數獲取出來,並儲存到一個新列表中返回
引數:
lst:要進行篩選的列表
'''
# 建立一個新列表
new_list = []
# 對列表進行篩選
for n in lst :
# 判斷n的奇偶
if func(n) :
new_list.append(n)
# if n > 5 :
# new_list.append(n)
# 返回新列表
return new_list
filter()
filter()可以從序列中過濾出符合條件的元素,儲存到一個新的序列中
引數:
1.函式,根據該函式來過濾序列(可迭代的結構)
2.需要過濾的序列(可迭代的結構)
返回值:
過濾後的新序列(可迭代的結構)
fn4是作為引數傳遞進filter()函式中
而fn4實際上只有一個作用,就是作為filter()的引數
filter()呼叫完畢以後,fn4就已經沒用
匿名函式lambda
lambda函式表示式專門用來建立一些簡單的函式,他是函式建立的又一種方式
語法:lambda 引數列表 : 返回值
匿名函式一般都是作為引數使用,其他地方一般不會使用
def fn5(a , b):
return a + b
(lambda a,b : a + b)(10,20)
也可以將匿名函式賦值給一個變數,一般不會這麼做
fn6 = lambda a,b : a + b
print(fn6(10,30))
r = filter(lambda i : i > 5 , l)
# print(list(r))
map()
map()函式可以對可迭代物件中的所有元素做指定的操作,然後將其新增到一個新的物件中返回
l = [1,2,3,4,5,6,7,8,9,10]
r = map(lambda i : i ** 2 , l)
print(list(r))
sort()
該方法用來對列表中的元素進行排序
sort()方法預設是直接比較列表中的元素的大小
在sort()可以接收一個關鍵字引數,key
key需要一個函式作為引數,當設定了函式作為引數
每次都會以列表中的一個元素作為引數來呼叫函式,並且使用函式的返回值來比較元素的大小
l = ['bb','aaaa','c','ddddddddd','fff']
l.sort(key=len)
l = [2,5,'1',3,'6','4']
l.sort(key=int)
print(l)
sorted()
這個函式和sort()的用法基本一致,但是sorted()可以對任意的序列進行排序
並且使用sorted()排序不會影響原來的物件,而是返回一個新物件
l = [2,5,'1',3,'6','4']
# l = "123765816742634781"
print('排序前:',l)
print(sorted(l,key=int))
print('排序後:',l)
閉包
將函式作為返回值返回,也是一種高階函式,這種高階函式我們也稱為叫做閉包,通過閉包可以建立一些只有當前函式能訪問的變數,可以將一些私有的資料藏到的閉包中
def fn():
a = 10
# 函式內部再定義一個函式
def inner():
print('我是fn2' , a)
# 將內部函式 inner作為返回值返回
return inner
r是一個函式,是呼叫fn()後返回的函式
這個函式實在fn()內部定義,並不是全域性函式
所以這個函式總是能訪問到fn()函式內的變數
求多個數的平均值
nums = [50,30,20,10,77]
sum()用來求一個列表中所有元素的和
print(sum(nums)/len(nums))
形成閉包的要件
① 函式巢狀
② 將內部函式作為返回值返回
③ 內部函式必須要使用到外部函式的變數
def make_averager():
# 建立一個列表,用來儲存數值
nums = []
# 建立一個函式,用來計算平均值
def averager(n) :
# 將n新增到列表中
nums.append(n)
# 求平均值
return sum(nums)/len(nums)
return averager
averager = make_averager()
print(averager(10))
裝飾器
# 建立幾個函式
def add(a , b):
'''
求任意兩個數的和
'''
r = a + b
return r
def mul(a , b):
'''
求任意兩個數的積
'''
r = a * b
return r
希望函式可以在計算前,列印開始計算,計算結束後列印計算完畢
我們可以直接通過修改函式中的程式碼來完成這個需求,但是會產生以下一些問題
① 如果要修改的函式過多,修改起來會比較麻煩
② 並且不方便後期的維護
③ 並且這樣做會違反開閉原則(OCP),程式的設計,要求開發對程式的擴充套件,要關閉對程式的修改
我們希望在不修改原函式的情況下,來對函式進行擴充套件
def fn():
print('我是fn函式'')
def fn2():
print('函式開始執行')
fn()
print('函式執行結束')
fn2()
def new_add(a,b):
print('計算開始')
r = add(a,b)
print('計算結束')
return r
r = new_add(111,222)
print(r)
上邊的方式,已經可以在不修改原始碼的情況下對函式進行擴充套件了
但是,這種方式要求我們每擴充套件一個函式就要手動建立一個新的函式,實在是太麻煩了
為了解決這個問題,我們建立一個函式,讓這個函式可以自動的幫助我們生產函式
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)
f2 = begin_end(add)
f3 = begin_end(mul)
r = f()
r = f2(123,456)
r = f3(123,456)
print(r)
- 像begin_end()這種函式我們就稱它為裝飾器
- 通過裝飾器,可以在不修改原來函式的情況下來對函式進行擴充套件
- 在開發中,我們都是通過裝飾器來擴充套件函式的功能的
- 在定義函式時,可以通過@裝飾器,來使用指定的裝飾器,來裝飾當前的函式
- 可以同時為一個函式指定多個裝飾器,這樣函式將會安裝從內向外的順序被裝飾
def fn3(old):
'''
用來對其他函式進行擴充套件,使其他函式可以在執行前列印開始執行,執行後列印執行結束
引數:
old 要擴充套件的函式物件
'''
# 建立一個新函式
def new_function(*args , **kwargs):
print('fn3裝飾~開始執行~~~~')
# 呼叫被擴充套件的函式
result = old(*args , **kwargs)
print('fn3裝飾~執行結束~~~~')
# 返回函式的執行結果
return result
# 返回新函式
return new_function
@fn3
@begin_end
def say_hello():
print('大家好~~~')
say_hello()