您好,登錄后才能下訂單哦!
這篇文章主要介紹“Python函數如何自定義”,在日常操作中,相信很多人在Python函數如何自定義問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Python函數如何自定義”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
函數是一個非常重要的概念,它們存在于所有編程語言中。函數允許我們定義一個動作(代碼塊),然后執行該動作任意次數,而無需遵循DRY原則重復自己。到目前為止,我一直在使用Python提供的一些內置函數,例如print
input
len
等。
什么是函數?函數(function)是用于完成特定任務的程序代碼的自包含單元。在面向對象編程的類中,函數通常被稱作方法。不同的函數在程序中扮演著不同的角色,起著不同的作用,執行不同的動作。比如print()函數可以將對象打印到屏幕上;還有一些函數能夠返回一個值以供程序使用,比如len()將可計算長度的對象的元素個數返回給程序。
那么,為什么要使用函數呢?
第一、函數的使用可以重用代碼,省去重復性代碼的編寫,提高代碼的重復利用率。如果程序中需要多次使用某種特定的功能,那么只需要編寫一個合適的函數就可以了。程序可以在任何需要的地方調用該函數,并且同一個函數可以在不同的程序中調用,就像我們經常使用的print()和input()函數一樣。
第二、函數能封裝內部實現,保護內部數據,實現對用戶的透明。很多時候,我們把函數看做“黑盒子”,即對應一定的輸入會產生特定的結果或返回某個對象。往往函數的使用者并不是函數的編寫者,函數的使用者對黑盒子的內部行為并不需要考慮,可以把精力投入到自身業務邏輯的設計而不是函數的實現細節。只有函數的設計者或者說編寫者,才需要考慮函數內部實現的細節,如何暴露對外的接口,返回什么樣的數據,也就是API的設計。
第三、即使某種功能在程序中只使用一次,將其以函數的形式實現也是有必要的,因為函數使得程序模塊化,從“一團散沙”變成“整齊方隊”,從而有利于程序的閱讀、調用、修改和完善。例如,假設你正在編寫一個實現下面功能的程序:
讀入一行數字
對數字進行排序
找到它們的平均值
打印出一個柱狀圖
是時候創建一個函數了。
def blow_fire(): # 函數定義 print('fire ???? ???? ????') blow_fire() # 函數調用 blow_fire() # 可以調用多次
上面的函數看起來不錯吧,但它也有一些限制。它只能執行相同的操作。讓我們使它更具可擴展性,并通過向其傳遞一些數據來使其隨意執行操作。
在定義函數時,當我們向其提供一些數據以基于該數據執行某些操作時,提供的數據稱為參數。可以為函數提供任意數量的參數。
絕大多數函數接收一定數量的參數,然后根據實際調用時提供的參數的值的不同,輸出不同的結果。前面我們說過,將函數內部的參數名字,定義得和外部變量的名字一樣是一種不好的習慣,它容易混淆思維,甚至發生錯誤。通常我們定義和給函數傳遞參數是這樣的:
x, y, z = 1, 2, 3 def add(a, b, c): return a+b+c add(x, y, x) # 使用變量,傳遞參數 add(4, 5, 6) # 直接傳遞值也是可以的。
在上面的例子中,a,b,c叫做形式參數,簡稱形參。而x,y,z和4,5,6叫做實際參數,簡稱實參,也就是實際要傳遞的值。而我們通常討論的參數,指的都是形參。
定義函數時,參數的名字和位置確定下來,函數的接口就固定了。對于函數的調用者來說,只需要知道如何傳遞正確的參數,以及函數將返回什么樣的值就夠了,函數內部的復雜邏輯被封裝起來,調用者無需了解。Python函數的參數定義靈活度非常大。除了正常定義的位置參數外,還可以使用默認參數、動態參數和關鍵字參數,這些都是形參的種類。
也叫必傳參數,順序參數,是最重要的,也是必須在調用函數時明確提供的參數!位置參數必須按先后順序,一一對應,個數不多不少的傳遞!
上面例子中的a,b,c就是位置參數,我們在使用add(4, 5, 6)
調用時,就是將4傳給a,5傳給b,6傳給c的一一對應傳遞。類似add(4, 5, 6, 7)
、add(4)
和add(5, 4, 6)
這種“畫蛇添足”、“缺胳膊少腿”和“嫁錯郎”類型的調用都是錯誤的。其中,add(5, 4, 6)
的調用在語法上沒問題,但是輸出結果可能和預期的不一致。
注意: Python在做函數參數傳遞的時候不會對數據類型進行檢查,理論上你傳什么類型都可以!
def add(a, b, c): return a+b+c result = add("haha", 2, 3)
但是,上面的add函數,如果你傳遞了一個字符串和兩個數字,結果是彈出異常,因為字符串無法和數字相加。這就是Python的弱數據類型和動態語言的特點。在簡單、方便的時候,需要你自己去實現數據類型檢查。
Traceback (most recent call last): File "F:/Python/pycharm/201705/func.py", line 33, in <module> result = add("haha", 2, 3) File "F:/Python/pycharm/201705/func.py", line 31, in add return a+b+c TypeError: must be str, not int
在函數定義時,如果給某個參數提供一個默認值,這個參數就變成了默認參數,不再是位置參數了。在調用函數的時候,我們可以給默認參數傳遞一個自定義的值,也可以使用默認值。
def power(x, n = 2): return x**n ret1 = power(10) # 使用默認的參數值n=2 ret2 = power(10, 4) # 將4傳給n,實際計算10**4的值
上面例子中的n就是個默認參數。默認參數可以簡化函數的調用,在為最常用的情況提供簡便調用的同時,還可以在特殊情況時傳遞新的值。但是在設置默認參數時,有幾點要注意:
默認參數必須在位置參數后面!
如果你違反了這點,在語法層面直接是通不過的。
# 這是一個錯誤的例子 def power(n = 2,x): return x**n
當有多個默認參數的時候,通常將更常用的放在前面,變化較少的放后面。
def student(name, sex, age, classroom="101", tel="88880000", address="..."): pass
在調用函數的時候,盡量給實際參數提供默認參數名。
def student(name, sex, age, classroom="101", tel="88880000", address="..."): pass student('jack','male',17) # 其它全部使用默認值 student('tom','male',18,'102','666666','beijing') # 全部指定默認參數的值 student('mary','female',18,'102',tel='666666') # 挑著來 student('mary','female',18,tel='666666','beijing') # 這是錯誤的參數傳遞方式 student("mary","female",18,tel="666666",address="beijing")
注意最后兩種調用方式,倒數第二種是錯誤的,而最后一種是正確的。為什么會這樣?因為一切沒有提供參數名的實際參數,都會當做位置參數按順序從參數列表的左邊開頭往右匹配!
使用參數名傳遞參數
通常我們在調用函數時,位置參數都是按順序先后傳入,而且必須在默認參數前面。但如果在位置參數傳遞時,給實參指定位置參數的參數名,那么位置參數也可以不按順序調用,例如:
def student(name, age, classroom, tel, address="..."): pass student(classroom=101, name="Jack", tel=66666666, age=20)
注意指定的參數名必須和位置參數的名字一樣。
默認參數盡量指向不變的對象!
使用不可變的數據類型作為默認值!
def func(a=None): # 注意下面的if語句 if a is None: a = [] a.append("A") return a print(func()) print(func()) print(func())
將默認參數a設置為一個類似None,數字或字符串之類的不可變對象。在函數內部,將它轉換為可變的類型,比如空列表。這樣一來,不管調用多少次,運行結果都是['A']了。
顧名思義,動態參數就是傳入的參數的個數是動態的,可以是1個、2個到任意個,還可以是0個。在不需要的時候,你完全可以忽略動態函數,不用給它傳遞任何值。
Python的動態參數有兩種,分別是*args
和**kwargs
,這里面的關鍵是一個和兩個星號的區別,而不是args
和kwargs
在名字上的區別,實際上你可以使用*any
或**whatever
的方式。但就如self一樣,默認大家都使用*args
和**kwargs
。
注意:動態參數,必須放在所有的位置參數和默認參數后面!
def func(name, age, sex='male', *args, **kwargs): pass
一個星號表示接收任意個參數。調用時,會將實際參數打包成一個元組傳入形式參數。如果參數是個列表,會將整個列表當做一個參數傳入。例如:
def func(*args): for arg in args: print(arg) func('a', 'b', 'c') li = [1, 2, 3] func(li)
運行結果是:
a b c [1, 2, 3]
通過循環args,我們可以獲得傳遞的每個參數。但是li這個列表,我們本意是讓它內部的1,2,3分別當做參數傳遞進去,但實際情況是列表本身被當做一個整體給傳遞進去了。怎么辦呢?使用一個星號!調用函數,傳遞實參時,在列表前面添加一個星號就可以達到目的了。實際情況是,不光列表,任何序列類型數據對象,比如字符串、元組都可以通過這種方式將內部元素逐一作為參數,傳遞給函數。而字典,則會將所有的key逐一傳遞進去。
def func(*args): for arg in args: print(arg) li = [1, 2, 3] func(*li)
兩個星表示接受鍵值對的動態參數,數量任意。調用的時候會將實際參數打包成字典。例如:
def func(**kwargs): for kwg in kwargs: print(kwg, kwargs[kwg]) print(type(kwg)) func(k1='v1', k2=[0, 1, 2])
運行結果是:
k1 v1 <class 'str'> k2 [0, 1, 2] <class 'str'>
而如果我們這樣傳遞一個字典dic呢?我們希望字典內的鍵值對能夠像上面一樣被逐一傳入。
def func(**kwargs): for kwg in kwargs: print(kwg, kwargs[kwg]) dic = { 'k1': 'v1', 'k2': 'v2' } func(dic)
實際結果卻是彈出錯誤,為什么?
Traceback (most recent call last): File "F:/Python/pycharm/201705/func.py", line 10, in <module> func(dic) TypeError: func() takes 0 positional arguments but 1 was given
因為這時候,我們其實是把dic當做一個位置參數傳遞給了func函數。而func函數并不接收任何位置函數。那怎么辦呢?使用兩個星號!
def func(**kwargs): for kwg in kwargs: print(kwg, kwargs[kwg]) dic = { 'k1': 'v1', 'k2': 'v2' } func(**dic)
有了前面一個星號的基礎,這里我們應該很好理解了。兩個星號能將字典內部的鍵值對逐一傳入**kwargs
。
當*args
和**kwargs
組合起來使用,理論上能接受任何形式和任意數量的參數,在很多代碼中我們都能見到這種定義方式。需要注意的是,*args
必須出現在**kwargs
之前。
def func(*args, **kwargs): for arg in args: print(arg) for kwg in kwargs: print(kwg, kwargs[kwg]) lis = [1, 2, 3] dic = { 'k1': 'v1', 'k2': 'v2' } func(*lis, **dic)
現在我們結合一下普通參數和萬能參數,看看會有什么情況發生:
def func(a, b, c=1, *args, **kwargs): for arg in args: print(arg) for kwg in kwargs: print(kwg, kwargs[kwg]) lis = ['aaa', 'bbb', 'ccc'] dic = { 'k1': 'v1', 'k2': 'v2' } func(1, 2, *lis, **dic)
打印結果是:
bbb ccc k1 v1 k2 v2
列表lis中的第一個元素‘aaa’怎么沒有打印出來?
我們改一下代碼,打印一下參數c的結果就知道了:
def func(a, b, c=1, *args, **kwargs): print('c的值是:', c) for arg in args: print(arg) for kwg in kwargs: print(kwg, kwargs[kwg]) lis = ['aaa', 'bbb', 'ccc'] dic = { 'k1': 'v1', 'k2': 'v2' } func(1, 2, *lis, **dic)
打印結果為:
c的值是: aaa bbb ccc k1 v1 k2 v2
原來,lis的第一個元素被傳遞給參數c了!這就是Python的參數傳遞規則之一。
對于*args
和**kwargs
參數,函數的調用者可以傳入任意不受限制的參數。比如:
def func(*args): pass func("haha", 1, [], {}) func(1,2,3,4,5,6)
對于這樣的參數傳遞方式,雖然靈活性很大,但是風險也很大,可控性差,必須自己對參數進行過濾和判定。例如下面我只想要姓名、年齡和性別,就要自己寫代碼檢查:
def student(name, age, **kwargs): if 'sex' in kwargs: student_sex = kwargs['sex']
但是實際上,用戶任然可以隨意調用函數,比如student("jack", 18, xxx='male')
,并且不會有任何錯誤發生。而我們實際期望的是類似student("jack", 18, sex='male')
的調用。那么如何實現這種想法呢?
可以用關鍵字參數!關鍵字參數前面需要一個特殊分隔符*
和位置參數及默認參數分隔開來,*
后面的參數被視為關鍵字參數。在函數調用時,關鍵字參數必須傳入參數名,這和位置參數不同。如果沒有傳入參數名,調用將報錯。不同于默認參數,關鍵字參數必須傳遞,但是關鍵字參數也可以有缺省值,這時就可以不傳遞了,從而簡化調用。
我們把前面的函數改寫一下:
def student(name, age, *, sex): pass student(name="jack", age=18, sex='male')
注意函數的定義體首行。
如果函數定義中已經有了一個*args
參數,后面跟著的命名關鍵字參數就不再需要一個特殊分隔符*
了。
def student(name, age=10, *args, sex, classroom, **kwargs): pass student(name="jack", age=18, sex='male', classroom="202", k1="v1")
Python的函數參數種類多樣、形態多變,既可以實現簡單的調用,又可以傳入非常復雜的參數。需要我們多下功夫,多寫實際代碼,多做測試,逐步理清并熟練地使用參數。
return
是 Python 中的一個關鍵字,用于從函數返回值。為了使函數更有用,它需要根據表達式的計算返回一些值。如果未指定 return 語句,或者 return 語句的表達式的計算結果未計算為數據類型,則該函數將返回None
。在 JavaScript 世界中,這個None
可以鏈接到void
。
return
語句終止該函數并退出該函數。return
def multiplier(num1, num2): return num1 * num2 result = multiplier(2,3) print(result) # 6
是時候使用return
語句做一些很酷的事情了。
def sum(num1): def child(num2): return num1 + num2 return child add_10 = sum(10) print(add_10(20)) # 30 (Closure!!!) print(add_10(50)) # 60 print(add_10(100)) # 110
我剛剛驗證了Python中也有閉包的概念,就像在JavaScript中一樣。它在創建工廠功能方面非常有效。在上面的代碼塊中,我能夠創建一個通用函數add_10,并向其傳遞動態參數以生成不同的結果。這是多么酷啊!
下周將在學習Python中的函數式編程概念時對此進行更多探討。
方法只是在對象內部定義的函數,或者換句話說,它們由對象“擁有”。使用對象名后跟
.
運算符調用它們以執行或調用它們。
return可以返回什么?
什么都不返回,僅僅return:return
數字/字符串/任意數據類型:return 'hello'
一個表達式:return 1+2
一個判斷語句:return 100 > 99
一個變量:return a
一個函數調用:return func()
甚至是返回自己!:return self
多個返回值,以逗號分隔:return a, 1+2, "hello"
簡而言之,函數可以return幾乎任意Python對象。
在某些特定的位置,用三引號包括起來的部分,也被當做注釋。但是,這種注釋有專門的作用,用于為__doc__提供文檔內容,這些內容可以通過現成的工具,自動收集起來,形成幫助文檔。比如,函數和類的說明文檔:
def func(a, b): """ 這個是函數的說明文檔。 :param a: 加數 :param b: 加數 :return: 和 """ return a + b class Foo: """ 這個類初始化了一個age變量 """ def __init__(self, age): self.age = age
需要強調的是這類注釋必須緊跟在定義體下面,不能在任意位置。
簡單來說,作用域意味著“我有權訪問哪些變量?這是解釋器在讀取代碼以查找變量范圍時提出的問題。在Python中,變量具有函數作用域,這意味著在函數內部定義的變量不能在函數外部訪問。
作用域指的是變量的有效范圍。變量并不是在哪個位置都可以訪問的,訪問權限取決于這個變量是在哪里賦值的,也就是在哪個作用域內的。
通常而言,在編程語言中,變量的作用域從代碼結構形式來看,有塊級、函數、類、模塊、包等由小到大的級別。但是在Python中,沒有塊級作用域,也就是類似if語句塊、for語句塊、with上下文管理器等等是不存在作用域概念的,他們等同于普通的語句。
num = 1 def confusing_function(): num = 10 return num print(num) # 1 => Global Scope print(confusing_function()) # 10 => Local Scope
這些是Python解釋器遵循的作用域規則:
從本地開始。變量是否存在?然后獲取該值。如果沒有,請繼續
變量是否在父函數本地作用域中定義?如果存在,則獲取值,否則繼續
全局范圍內是否存在該變量?如果存在,則獲取值,否則繼續
該變量是內置函數嗎?獲取值否則退出
通常,函數內部的變量無法被函數外部訪問,但內部可以訪問;類內部的變量無法被外部訪問,但類的內部可以。通俗來講,就是內部代碼可以訪問外部變量,而外部代碼通常無法訪問內部變量。
變量的作用域決定了程序的哪一部分可以訪問哪個特定的變量名稱。Python的作用域一共有4層,分別是:
L (Local) 局部作用域
E (Enclosing) 閉包函數外的函數中
G (Global) 全局作用域
B (Built-in) 內建作用域
x = int(2.9) # 內建作用域,查找int函數 global_var = 0 # 全局作用域 def outer(): out_var = 1 # 閉包函數外的函數中 def inner(): inner_var = 2 # 局部作用域
前面說的都是變量可以找得到的情況,那如果出現本身作用域沒有定義的變量,那該如何尋找呢?
Python以L –> E –> G –>B
的規則查找變量,即:在局部找不到,便會去局部外的局部找(例如閉包),再找不到就會去全局找,最后去內建中找。如果這樣還找不到,那就提示變量不存在的錯誤。例如下面的代碼,函數func內部并沒有定義變量a,可是print函數需要打印a,那怎么辦?向外部尋找!按照L –> E –> G –>B
的規則,層層查詢,這個例子很快就從外層查找到了a,并且知道它被賦值為1,于是就打印了1。
a = 1 def func(): print(a)
定義在函數內部的變量擁有一個局部作用域,被叫做局部變量,定義在函數外的擁有全局作用域的變量,被稱為全局變量。(類、模塊等同理)
所謂的局部變量是相對的。局部變量也有可能是更小范圍內的變量的外部變量。
局部變量只能在其被聲明的函數內部訪問,而全局變量可以在整個程序范圍內訪問。調用函數時,所有在函數內聲明的變量名稱都將被加入到作用域中。
a = 1 # 全局變量 def func(): b = 2 # 局部變量 print(a) # 可訪問全局變量a,無法訪問它內部的c def inner(): c = 3 # 更局部的變量 print(a) # 可以訪問全局變量a print(b) # b對于inner函數來說,就是外部變量 print(c)
我們先看下面的例子:
total = 0 # total是一個全局變量 def plus( arg1, arg2 ): total = arg1 + arg2 # total在這里是局部變量. print("函數內局部變量total= ", total) print("函數內的total的內存地址是: ", id(total)) return total plus(10, 20) print("函數外部全局變量total= ", total) print("函數外的total的內存地址是: ", id(total))
很明顯,函數plus內部通過total = arg1 + arg2
語句,新建了一個局部變量total,它和外面的全局變量total是兩碼事。而如果我們,想要在函數內部修改外面的全局變量total呢?使用global關鍵字!
global:指定當前變量使用外部的全局變量
total = 0 # total是一個全局變量 def plus( arg1, arg2 ): global total # 使用global關鍵字申明此處的total引用外部的total total = arg1 + arg2 print("函數內局部變量total= ", total) print("函數內的total的內存地址是: ", id(total)) return total plus(10, 20) print("函數外部全局變量total= ", total) print("函數外的total的內存地址是: ", id(total))
打印結果是:
函數內局部變量total= 30 函數內的total的內存地址是: 503494624 函數外部全局變量total= 30 函數外的total的內存地址是: 503494624
我們再來看下面的例子:
a = 1 print("函數outer調用之前全局變量a的內存地址: ", id(a)) def outer(): a = 2 print("函數outer調用之時閉包外部的變量a的內存地址: ", id(a)) def inner(): a = 3 print("函數inner調用之后閉包內部變量a的內存地址: ", id(a)) inner() print("函數inner調用之后,閉包外部的變量a的內存地址: ", id(a)) outer() print("函數outer執行完畢,全局變量a的內存地址: ", id(a))
如果你將前面的知識點都理解通透了,那么這里應該沒什么問題,三個a各是各的a,各自有不同的內存地址,是三個不同的變量。打印結果也很好的證明了這點:
函數outer調用之前全局變量a的內存地址: 493204544 函數outer調用之時閉包外部的變量a的內存地址: 493204576 函數inner調用之后閉包內部變量a的內存地址: 493204608 函數inner調用之后,閉包外部的變量a的內存地址: 493204576 函數outer執行完畢,全局變量a的內存地址: 493204544
那么,如果,inner內部想使用outer里面的那個a,而不是全局變量的那個a,怎么辦?用global關鍵字?先試試看吧:
a = 1 print("函數outer調用之前全局變量a的內存地址: ", id(a)) def outer(): a = 2 print("函數outer調用之時閉包外部的變量a的內存地址: ", id(a)) def inner(): global a # 注意這行 a = 3 print("函數inner調用之后閉包內部變量a的內存地址: ", id(a)) inner() print("函數inner調用之后,閉包外部的變量a的內存地址: ", id(a)) outer() print("函數outer執行完畢,全局變量a的內存地址: ", id(a))
運行結果如下,很明顯,global使用的是全局變量a。
函數outer調用之前全局變量a的內存地址: 494384192 函數outer調用之時閉包外部的變量a的內存地址: 494384224 函數inner調用之后閉包內部變量a的內存地址: 494384256 函數inner調用之后,閉包外部的變量a的內存地址: 494384224 函數outer執行完畢,全局變量a的內存地址: 494384256
那怎么辦呢?使用nonlocal
關鍵字!它可以修改嵌套作用域(enclosing 作用域,外層非全局作用域)中的變量。將global a
改成nonlocal a
,代碼這里我就不重復貼了,運行后查看結果,可以看到我們真的引用了outer函數的a變量。
函數outer調用之前全局變量a的內存地址: 497726528 函數outer調用之時閉包外部的變量a的內存地址: 497726560 函數inner調用之后閉包內部變量a的內存地址: 497726592 函數inner調用之后,閉包外部的變量a的內存地址: 497726592 函數outer執行完畢,全局變量a的內存地址: 497726528
到此,關于“Python函數如何自定義”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。