python | 自定義函數


第1節 自定義函數

1.1 函數的定義

函數是一段具備特定功能的、可複用的語句組。python中函數用函數名來表示,並經過函數名進行功能調用。它是一種功能抽象,與黑盒相似,因此只要瞭解函數的輸入輸出方式便可,不用深究內部實現原理。函數的最突出優勢是:數組

  • 實現代碼複用:減小重複性工做
  • 保證代碼一致:只須要修改該函數代碼,則全部調用均能受影響

在python中能夠把函數分爲:系統自帶函數、第三方庫函數、自定義函數。須要重點掌握的是「自定義函數」app



自定義函數函數

自定義函數語法:

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

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

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

注意事項:測試

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


1.2 函數的調用

在定義好函數以後,有兩種方式對其進行調用:code

  • 從本文件調用:直接使用函數名 + 傳入參數,如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)



程序入口對象

如C、C++等語言都有一個main函數做爲程序的入口,main去調用函數庫,函數庫之間不能相互調用(以下圖A和B之間),即程序的運行是從main函數開始。而python是腳本語言,沒有統一入口,函數庫之間能夠互相調用。排序

因此,python代碼執行有兩種狀況:遞歸

  • 狀況1:直接做爲腳本執行,即直接運行該模塊
  • 狀況2:先import到其餘python模塊中再調用執行(模塊重用),其實就是「第三方庫」

而python經過if__name__ == 'main' : 語句,控制這兩種不一樣狀況下的代碼執行。



if__name__ == 'main' : 模擬入口

基於python代碼執行機理,可用if__name__ == 'main' : 語句模擬程序入口,實現對python代碼執行的控制。

  • 以腳本直接執行時,if__name__ == 'main': 後面語句會執行(該語句多爲函數正確性驗證語句)
  • 被import到其餘模塊中執行時,if__name__ == 'main': 後面語句不會執行
# 直接做爲腳本執行
def add1(x):
    x = x+2
    return x
add1(10)

# 模塊重用執行
import os
os.chdir('D:\\data\\python_file') 
from addx import add1
add1(9)

因此,想運行以腳本直接執行時才執行的命令時,能夠將這些命令語句放在if__name__ == 'main': 判斷語句以後:

# 自定義一個函數add1
def add1(a):
    a=a+1
    return a
print(__name__)

if __name__ == "__main__":       # 腳本直接執行時,運行後面的語句;被import執行時,不運行後面的語句
    print(add1(2))               # 函數正確性驗證和測試


1.3 函數的參數

1.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)                  # 修改形參影響實參


1.3.2 參數的傳遞

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

函數參數多種多樣,根據參數傳遞發生的前後順序,能夠從兩個角度學習常見的一些參數:

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

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



默認值參數

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

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

demo(1, 2)

注意事項:

  • 默認值參數必須出如今參數列表最右端
  • 調用帶有默認值參數的函數時,能夠對默認值參數進行賦值,也能夠不賦值
  • 默認值參數只能是不可變對象,使用可變序列做爲參數默認值時,程序會有邏輯錯誤
  • 可使用 「函數名.defaults」 查看該函數全部默認參數的當前值



可變參數

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

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

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

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



位置參數

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

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

demo(1, 2, 3)



關鍵字參數

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

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

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



命名關鍵字參數

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

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

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



參數傳遞的序列解包

參數傳遞的序列解包,是經過在實參序列前加星號(*)將其解包,而後按順序傳遞給多個形參。根據解包序列的不一樣,能夠分爲以下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})  # 序列解包必須在關鍵字參數解包以前,正確用法


1.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


1.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


1.6 遞歸


在函數內部,能夠調用其餘函數。若是一個函數在內部調用自身自己,這個函數就是遞歸函數。但不是的函數調用本身都是遞歸,遞歸有其自身的特性:

  • 必須有一個明確的遞歸結束條件,稱爲遞歸出口(基例
  • 相鄰兩次重複之間有緊密的聯繫,前一次要爲後一次作準備(一般前一次的輸出就做爲後一次的輸入)
  • 每一次遞歸,總體問題都要比原來減少,而且遞歸到必定層次時,要能直接給出結果



從上圖可知,遞歸過程是函數調用本身,本身再調用本身,...,當某個條件獲得知足(基例)的時候就再也不調用,而後再一層一層地返回,直到該函數的第一次調用。遞歸函數的優勢是邏輯簡單清晰,缺點是過深的調用會致使棧溢出,在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)
相關文章
相關標籤/搜索