1. 程式人生 > 其它 >Python學習筆記(4)-函式

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中一共有全域性作用域與函式作用域兩種作用域

全域性作用域

  • 全域性作用域在程式執行時建立,在程式執行結束時銷燬
  • 所有函式以外的區域都是全域性作用域
  • 在全域性作用域中定義的變數,都屬於全域性變數,全域性變數可以在程式的任意位置被訪問

函式作用域

  • 函式作用域在函式呼叫時建立,在呼叫結束時銷燬
  • 函式每呼叫一次就會產生一個新的函式作用域
  • 在函式作用域中定義的變數,都是區域性變數,它只能在函式內部被訪問
  • 變數的查詢
  1. 當我們使用變數時,會優先在當前作用域中尋找該變數,如果有則使用,
    如果沒有則繼續去上一級作用域中尋找,如果有則使用,如果依然沒有則繼續去上一級作用域中尋找,以此類推,直到找到全域性作用域,依然沒有找到,則會丟擲異NameError: name 'a' is not defined
  2. 如果希望在函式內部修改全域性變數,則需要使用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)

  1. 像begin_end()這種函式我們就稱它為裝飾器
  2. 通過裝飾器,可以在不修改原來函式的情況下來對函式進行擴充套件
  3. 在開發中,我們都是通過裝飾器來擴充套件函式的功能的
  4. 在定義函式時,可以通過@裝飾器,來使用指定的裝飾器,來裝飾當前的函式
  5. 可以同時為一個函式指定多個裝飾器,這樣函式將會安裝從內向外的順序被裝飾
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()