python | 自定義函數


 

1 函數的定義

函數是一段具有特定功能的、可復用的語句組。python中函數用函數名來表示,並通過函數名進行功能調用。它是一種功能抽象,與黑盒類似,所以只要了解函數的輸入輸出方式即可,不用深究內部實現原理。函數的最突出優點是:

  • 實現代碼復用:減少重復性工作
  • 保證代碼一致:只需要修改該函數代碼,則所有調用均能受影響

在python中可以把函數分為:系統自帶函數、第三方庫函數、自定義函數。需要重點掌握的是「自定義函數」。

 
自定義函數

自定義函數語法:

def 函數名([參數列表]):
	函數體
    return語句

# 示例
def add1(x):
    x = x + 1
    return x

函數通過「參數」和「返回值」來傳遞信息,並通過「參數列表」和「return語句」實現對兩者的控制,詳見下圖:

注意事項:

  • 函數定義時無需聲明形參類型(由調用時的實參類型確定);也無需指定返回值類型(由return語句確定)
  • 自定義函數即使沒有任何參數,也必須保留一隊空括號()
  • 括號后面的冒號(:)必不可少
  • 函數體相對於def關鍵字必須有縮進關系
  • python允許嵌套定義函數
  • return語句作用是結束函數調用,並將結果返回給調用者
    • return語句是可選的,可以出現在函數體任意位置
    • 無return語句、有return語句沒有執行、有return語句而沒有返回值三種情況,函數都返回None

 

2 函數的調用

在定義好函數之后,有兩種方式對其進行調用:

  • 從本文件調用:直接使用函數名 + 傳入參數,如add1(9)
  • 從其他文件調用:這種方法有兩種實現手段
    • 先指定文件路徑 + import 文件名,再用文件名.函數名(參數列表)調用
    • 先指定文件路徑 + from 文件名 import 函數名,再用文件名.函數名(參數列表)調用
# 從本文件調用
def add1(x):
    x = x+2
    return x

add1(10)

# 從其他文件調用:從名為addx的文件調用已經定義好的add1函數
import os
os.chdir('D:\\data\\python_file') 

# 從其他文件調用方法1
import addx
addx.add1(4)

# 從其他文件調用方法2
from addx import add1
add1(9)

 

3 函數的參數

3.1 形參與實參

從上面可知,函數最重要的三部分就是參數、函數體、返回值,而參數分為形參和實參:

  • 形參:定義函數時,函數名后面圓括號中的變量
  • 實參:調用函數時,函數名后面圓括號中的變量

注意事項:

  1. 形參只在函數內部有效,一個函數可以沒有形參,但必須有括號()
  2. 通常修改形參不影響實參;但如果傳遞給函數的是「可變序列」(列表、字典、集合),修改形參會影響實參
def printmax(a,b):       # a, b是形參
    if a>b:
        print(a)
 printmax(3,4)           # 3, 4是實參

# 形參修改不影響實參
def add2(x):
    x = x+2
    return x

x = 10
print(add2(x))  
print(x)

# 形參修改影響實參
def add2(x):              
    x.append(2)           
    return x

y = [1, 1]                # 實參y是變序列(列表、字典、集合)
print(add2(y))            # 函數返回值
print(y)                  # 修改形參影響實參

 

3.2 參數的傳遞

在定義函數時無需指定形參類型,在調用函數時,python會根據實參類型來自動推斷。而定義函數和調用函數的過程,可以簡化為下面圖中三步,實質就是通過參數和返回值傳遞信息,而參數的傳遞發生在第一和第三步。

函數參數多種多樣,根據參數傳遞發生的先后順序,可以從兩個角度學習常見的一些參數:

  • 定義函數(形參):默認值參數、可變參數
  • 調用函數(實參):位置參數、關鍵字參數、命名關鍵字參數

同時,這些參數可以組合使用(可變參數無法和關鍵字參數組合),且參數定義的順序從左至右分別是:位置參數 >> 默認值參數 >> 可變參數 / 關鍵字參數 / 命名關鍵字參數。參數傳遞還有一種高級用法——參數傳遞的序列解包。

 

3.2.1 默認值參數

默認參數就是在調用函數的時候使用一些包含默認值的參數。

# 默認值參數b=5, c=10
def demo(a, b=5, c=10):
    print(a, b, c)

demo(1, 2)

注意事項:

  • 默認值參數必須出現在參數列表最右端
  • 調用帶有默認值參數的函數時,可以對默認值參數進行賦值,也可以不賦值
  • 默認值參數只能是不可變對象,使用可變序列作為參數默認值時,程序會有邏輯錯誤
  • 可以使用 “函數名.defaults” 查看該函數所有默認參數的當前值

 

3.2.2 可變參數

可變參數就是允許在調用參數的時候傳入多個(≥0個)參數,可變參數分為兩種情況:

  • 可變位置參數:定義參數時,在前面加一個*,表示這個參數是可變的,可以接受任意多個參數,這些參數構成一個「元組」,只能通過位置參數傳遞
  • 可變關鍵字參數:定義參數時,在前面加**,表示這個參數可變,可以接受任意多個參數,這些參數構成一個「字典」,只能通過關鍵字參數傳遞
# 可變位置參數
def demo(*p):
    print(p)
    
demo(1, 2, 3)                 # 參數在傳入時被自動組裝成一個元組

 
# 可變關鍵字參數
def demo(**p):
    print(p)

demo(b='2', c='5', a='1')     # 參數在傳入時被自動組裝成一個字典

 

3.2.3 位置參數

位置參數特點是調用函數時,要保證實參和形參的順序一致、數量相同。

# 位置參數
def demo(a, b, c):
    print(a, b, c)

demo(1, 2, 3)

 

3.2.4 關鍵字參數

關鍵字參數允許在調用時以字典形式傳入0個或多個參數,且在傳遞參數時用等號(=)連接鍵和值。關鍵字參數最大優點,就是使實參順序可以和形參順序不一致,但不影響傳遞結果。

# 關鍵參數
def demo(a, b, c):
    print(a, b, c)

demo(b=2, c=5, a=1)   # 改變參數順序對結果不影響

 

3.2.5 命名關鍵字參數

命名關鍵字參數是在關鍵字參數的基礎上,限制傳入的的關鍵字的變量名。和普通關鍵字參數不同,命名關鍵字參數需要一個用來區分的分隔符*,它后面的參數被認為是命名關鍵字參數。

# 這里星號分割符后面的city、job是命名關鍵字參數
def person_info(name, age, *, city, job):
    print(name, age, city, job)

person_info("Alex", 17, city="Beijing", job="Engineer")

 

3.2.6 參數傳遞的序列解包

參數傳遞的序列解包,是通過在實參序列前加星號(*)將其解包,然后按順序傳遞給多個形參。根據解包序列的不同,可以分為如下5種情況:

序列解包 示例
列表的序列解包 *[3,4,5]
元組的序列解包 *(3,4,5)
集合的序列解包 *{3,4,5}
字典的鍵的序列解包 若字典為dic={'a':1,'b':2,'c':3},則解包代碼為:*dic
字典的值的序列解包 若字典為dic={'a':1,'b':2,'c':3},則解包代碼為:*dic.values()

注意事項:

  • 對實參序列進行序列解包后,得到的實參值就變成了位置參數,要和形參一一對應
  • 當序列解包和位置參數同時使用時,序列解包相當於位置參數,且會優先處理
  • 序列解包不能在關鍵字參數解包之后,否則報錯
"""函數參數的序列解包"""
def demo(a, b, c):
    print(a+b+c)

demo(*[3, 4, 5])       # 列表的序列解包
demo(*(3, 4, 5))       # 元組的序列解包
demo(*{3, 4, 5})       # 集合的序列解包

dic = {'a': 1, 'b': 2, 'c': 3}
demo(*dic)			   # 字典的鍵的序列解包				
demo(*dic.values())    # 字典的值的序列解包

"""位置參數和序列解包同時使用"""
def demo(a, b, c):  
    print(a, b, c)

demo(*(1, 2, 3))       # 元組的序列解包
demo(1, *(2, 3))       # 位置參數和序列解包同時使用
demo(c=1, *(2, 3))     # 序列解包相當於位置參數,優先處理,正確用法
demo(*(3,), **{'c': 1, 'b': 2})  # 序列解包必須在關鍵字參數解包之前,正確用法

 

4 全局變量與局部變量

變量起作用的代碼范圍稱為「變量的作用域」。不同作用域內變量名可以相同,但互不影響。從變量作用的范圍分類,可以把變量分類為:

  • 全局變量:指函數之外定義的變量,在程序執行全過程有效
  • 局部變量:指在函數內部使用的變量,僅在函數內部有效,當函數退出時變量將不存在

需要特別指出的是,局部變量的引用比全局變量速度快,應考慮優先使用。

 
全局變量聲明

有兩種方式可以聲明全局變量:

  • 方式一:在函數外聲明
  • 方式二:在函數內部用global聲明,又分為兩種情況:
    • 情況1:變量已在函數外定義,使用global聲明。若進行了重新賦值,則賦值結果會覆蓋原變量值
    • 情況2:變量未在函數外定義,使用global在函數內部聲明,它將增加為新的全局變量

特殊情況,若局部變量和全局變量同名,那么全局變量會在局部變量的作用域內被隱藏掉,即在自定義函數內生效的是局部變量。

d = 2       # 全局變量
def func(a, b):
    c = a*b
    return c

func(2, 3)

def func(a, b):
    c = a*b
    d = 2   # 局部變量
    return c

func(2, 3)

"""聲明的全局變量,已在函數外定義"""
n = 1
def func(a, b):
    global n
    n = b
    c = a*b
    return c

s = func("knock~", 2)
print(s, n)

"""聲明的全局變量,未在函數外定義,則新增"""
def func(a, b):
    c = a*b
    global d  # 聲明d為全局變量
    d = 2
    return c

func(2, 3)

"""局部變量和全局變量同名,則全局變量在函數內會被隱藏"""
d = 10                   # 全局變量d
def func(a, b):
    d = 3                # 局部變量d
    c = a+b+d
    return c

func(1, 2)
d

 

5 lambda函數

lambda函數,又稱匿名函數,即沒有函數名字臨時使用的小函數。其語法如下:

lambda 函數參數:函數表達式

注意:
	- 匿名函數只能有一個表達式
	- 該表達式的結果,就是函數的返回值
	- 不允許包含其他復雜語句,但表達式中可以調用其他函數

lambda函數的使用場景,主要在兩方面:

  • 尤其適用於需要一個函數作為另一個函數參數的場合,比如排序
  • 把匿名函數賦值給一個變量,再利用變量來調用該函數
def f(x, y, z): 
    return x+y+z         # 位置參數
f(1, 2, 3)

def f1(x, y=10, z=10): 
    return x+y+z  		 # 默認值參數
f(1)

"""把匿名函數賦值給一個變量,再利用變量來調用該函數,作用等價於自定義函數"""
f=lambda x,y,z:x+y+z
f(y=1,x=2,z=3)			 #關鍵值參數

L = ['ab', 'abcd', 'dfdfdg', 'a']
L.sort(key=lambda x: len(x))               # 按長度排序
L

L=[('小明',90,80),('小花',70,90),('小張',98,99)]
L.sort(key=lambda x:x[1],reverse=True)     # 降序排序
L

 

6 遞歸

在函數內部,可以調用其他函數。如果一個函數在內部調用自身本身,這個函數就是遞歸函數。但不是的函數調用自己都是遞歸,遞歸有其自身的特性:

  • 必須有一個明確的遞歸結束條件,稱為遞歸出口(基例),一般使用if……else結構控制遞歸的進入和退出
  • 相鄰兩次重復之間有緊密的聯系,前一次要為后一次做准備(通常前一次的輸出就作為后一次的輸入)
  • 每一次遞歸,整體問題都要比原來減小,並且遞歸到一定層次時,要能直接給出結果

從上圖可知,遞歸過程是函數調用自己,自己再調用自己,...,當某個條件得到滿足(基例)的時候就不再調用,然后再一層一層地返回,直到該函數的第一次調用。遞歸函數的優點是邏輯簡單清晰,缺點是過深的調用會導致棧溢出,在Python中,通常情況下,這個深度是1000層,超過將拋出異常。

 
案例:用遞歸實現階乘

# 案例一:用遞歸實現階乘
def fact(n):
    if n==0:                  # 遞歸退出
        return 1
    else:
        return n*fact(n-1)    # 遞歸進入
fact(5)

# 案例二:實現字符串反轉
# 方法1,先轉成列表,調用列表的revers方法,再把列表轉成字符串
s = 'abcde'
l = list(s)
l.reverse()
''.join(l)

# 方法2,切片的方法
s = 'abcde'
s[::-1]

# 方法3,遞歸的方法
s = 'abc'
def reverse1(s):
    if s == '':
        return s
    else:
        print(s)
        return reverse1(s[1:])+s[0]

reverse1(s)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM