目錄
本文有視頻講解,視頻和實例源碼下載方式:點擊->我的主頁,查看個人簡介。
我儘量堅持每日更新一節。
更多python教程,請查看我的專欄《0基礎學python視頻教程》
1、什麼是“函數”
前面我們講過,所謂“程序”,就是通過邏輯去控制一系列數據按照預期處理,從而達成我們需要的某個特定功能。所以,理論上,我們只需要前面學的數據結構和控制語句,我們就可以寫所有的“程序”了。
但是,現實中,我們的程序要複雜得多。拿我平時工作中的產品爲例,其完整的代碼量可達數百萬行。如何組織這些代碼?如何更好的維護這些代碼?如何讓幾十幾百名工程師同時開發這些代碼?如何在這些代碼基礎上新增功能?等等,這些問題會比代碼語法本身更重要。解決這些問題的本質,是要對代碼進行合理的“抽象”和“封裝”,使其具備一定的組織結構,這就是所謂的“架構”。
作爲高級編程語言,python必須提供一些機制,以便於我們實現代碼“架構”。函數,就是這些機制中最基礎的一個。
接下來我們會花很多章節來學習這些機制,包括函數、模塊、包、類等等。
我們以實現list求和這樣一個簡單的功能爲例:
# author: Tiger, 關注公衆號“跟哥一起學python”,ID:tiger-python
# file: ./9/9_1.py
# 實現 list 求和
list_1 = [100, 200, 300, 400]
list_sum = 0
for item in list_1:
list_sum += item
print(list_sum)
上面一段代碼,實現了對list_1這個列表中所有元素的求和。沒有任何問題。代碼很清晰簡潔。下面我們對這個功能進行一些擴充,我們增加一個列表list_2,也需要求和。實現如下:
# author: Tiger, 關注公衆號“跟哥一起學python”,ID:tiger-python
# file: ./9/9_1.py
# 實現 list 求和
list_1 = [100, 200, 300, 400]
list_2 = list(range(10, 100, 5))
list_sum = 0
for item in list_1:
list_sum += item
print(list_sum)
list_sum = 0
for item in list_2:
list_sum += item
print(list_sum)
也能實現我們需要的功能,可是這段代碼總讓人看着不那麼完美,不那麼優美。裏面兩段通過for循環求和的代碼塊,非常類似。試想,如果我們想得到多個list的和,這段代碼是不是會一直重複寫下去?
基於這個簡單的例子,我們來理解“抽象”和“封裝”的概念。
“抽象”,就是提取出代碼中的一部分“共性”邏輯或者功能。
“封裝”,就是將這些“共性”邏輯或者功能組織在一起,並提供給別人使用。
抽象和封裝,是作爲程序員必須具備的基本能力和基本素質。不論你是初級程序員,還是架構師,這兩項能力都始終貫穿我們的工作中。
這個例子,我們可以通過函數,來實現對求和邏輯的封裝。
通過封裝函數func_list_sum(),我們實現了求和邏輯的複用,它不僅可以給list1、list2使用,甚至可以給所有的列表使用。代碼的複用,可以給我們帶來很多好處,比如:代碼更加簡潔、提升開發效率、提升代碼可靠性等。
經過函數封裝後,這個實例實現如下:
# author: Tiger, 關注公衆號“跟哥一起學python”,ID:tiger-python
# file: ./9/9_1.py
def func_list_sum(l):
"""
實現 list 求和
"""
s = 0
for item in l:
s += item
return s
list_1 = [100, 200, 300, 400]
list_2 = list(range(10, 100, 5))
print(func_list_sum(list_1))
print(func_list_sum(list_2))
封裝可以讓我們的代碼很方便的擴展,比如我們在求和時需要檢查成員是否是整數,函數封裝後,我們只需要修改這個函數裏面的代碼即可擴展該功能。
# author: Tiger, 關注公衆號“跟哥一起學python”,ID:tiger-python
# file: ./9/9_1.py
def func_list_sum(l):
"""
實現 list 求和
"""
s = 0
for item in l:
if type(item) is not int: # 必須是整數
continue
s += item
return s
list_1 = [100, 200, 300, 400]
list_2 = list(range(10, 100, 5))
print(func_list_sum(list_1))
print(func_list_sum(list_2))
2、函數的語法
2.1、語法定義
函數的語法定義如下:
def func_name(params…):
func_body
‘def’是定義函數的保留字,func_name是函數名,其命名規則和變量名是一致的。後面的小括號裏面是該函數對應的入參列表,多個參數用逗號隔開,可以不帶參數。關於入參的寫法,下面我們會詳細介紹。小括號後面是冒號。func_body是函數體,可以爲空語句pass,或者多語句組成的代碼塊,甚至是一個函數(後面章節會講函數的嵌套)。
2.2、形參與實參
形參和實參的概念,在所有高級編程語言中都類似。
形參:形式上的參數,它是我們在定義函數時用到的參數;
實參:實際的參數,它是我們在真正調用函數時傳遞的參數。
下面我們通過一個函數來生成著名的Fibonacci (斐波那契)數列:
# author: Tiger, 關注公衆號“跟哥一起學python”,ID:tiger-python
# file: ./9/9_2.py
def get_fib(max):
"""
打印斐波那契數列 Fibonacci
:param max:
:return:
"""
a, b = 0, 1
while a < max:
print(a, end=' ')
a, b = b, a + b
print()
input_max = int(input("input max number for Fibonacci:"))
print("\n1Fibonacci is:")
get_fib(input_max)
輸出爲:
input max number for Fibonacci:1000
Fibonacci is:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
這個例子中,我們定義了函數get_fib(max)。在定義這個函數的時候,參數max它並沒有實際意義上的值,也沒有給它分配對象空間,它就是所謂的“形參”。
下面這行代碼調用這個函數:
get_fib(input_max)
傳入了變量input_max,它是一個真正的變量,是有對象空間的,它被稱爲“實參”。
2.3、值傳遞和引用傳遞
這個概念我們講可變數據類型和不可變數據類型的時候提到過。
值傳遞(pass-by-value):傳遞給函數的,是變量對應的值。函數裏面會重新分配一個對象,並將該值拷貝過去。函數裏面對新對象進行操作,與原變量無關。不可變數據類型,採用值傳遞的方式,比如字符串、數字、元組、不可變集合、字節等。值傳遞方式,需要使用return來返回結果。
引用傳遞(pass-by-reference):傳遞給函數的,是變量指向對象的引用(CPython中就是內存地址)。函數裏面直接對這個對象進行操作,會直接影響原變量。可變數據類型,採用引用傳遞的方式,比如列表、字典、可變集合等。
值傳遞和引用傳遞,取決於變量指向的對象數據類型。同一個函數的參數列表中,可以同時包含值傳遞和引用傳遞。
這裏不再舉例,大家可以回過頭去看看可變數據類型和不可變數據類型章節的例子。
2.4、必選參數和可選參數
有時候,我們希望調用函數時,只傳入一些必需的參數,而忽略其它參數。比如我們前面使用到的很多python內建函數(built-in)都是這樣的。
def print(self, *args, sep=' ', end='\n', file=None): # known special case of print
"""
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
"""
pass
這是print函數的定義,它有很多參數,但通常我們只傳遞一個字符串進去,或者再追加一個end參數,其它參數都沒有填。
所以,我們定義的函數參數列表,是可以支持可選參數和必選參數。Python並沒有提供方法讓我們指定某個參數是否可選或者必選,而是通過設置參數的默認值,來達到此效果。
下面這個例子是官方文檔裏面的:
def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
ok = input(prompt)
if ok in ('y', 'ye', 'yes'):
return True
if ok in ('n', 'no', 'nop', 'nope'):
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)
函數ask_ok有三個參數,第一個參數prompt是必選參數,後面兩個指定了缺省值,是可選參數。
這個函數可以通過幾種方式調用:
- 只給出必需的參數:ask_ok('Do you really want to quit?')
- 給出一個可選的參數:ask_ok('OK to overwrite the file?', 2)
- 或者給出所有的參數:ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')
函數參數的默認值可以指定爲變量,但是其只會在函數定義時執行一次。如下面的實例:
# author: Tiger, 關注公衆號“跟哥一起學python”,ID:tiger-python
# file: ./9/9_3.py
# 參數默認值
x = 100
def func_test(a, b=x): # 參數b的默認值會設置爲100,後面x改變不會隨着變
return a + b
x = 200
print(func_test(200))
輸出爲:
300
參數b在函數定義時,設置了缺省值爲x也就是100,後面x改變爲200,b的缺省值還是100。
如果參數的缺省值是一個可變數據類型,它會怎麼樣呢?再看下面的實例:
# 參數默認值爲可變數據類型
def list_test(a, b=[]):
print(id(b))
b.append(a)
return b
print(list_test(300))
print(list_test(400))
print(list_test(500, [800]))
print(list_test(600))
輸出爲:
2658215980992
[300]
2658215980992
[300, 400]
2658217265856
[800, 500]
2658215980992
[300, 400, 600]
Process finished with exit code 0
還記得前面講的引用傳遞嗎?如果參數b缺省值設置爲一個list,那麼b缺省指向的是一個對象的引用。即使b=[ ],系統也會給它創建一個空的列表對象,並把其引用賦給它。
上面例子還有一個有意思的地方,當我們給b傳遞了一個實參[800 ],b指向了一個新的對象。之後,我們再次只傳遞實參a,b採用缺省值,我們發現系統還能記住之前的缺省值。這說明,python函數處理機制中,一旦給參數設置了缺省值,那麼這個值會一直存在。
對於這種可變類型作爲缺省值的情況,如果你不想讓這個缺省值被共享下去,可以將參數的缺省值設置爲None。None是一個空對象。如下實例:
# 參數默認值爲None
print('set to None'.center(30, '-'))
def list_test(a, b=None):
if b is None:
b = []
print(id(b))
b.append(a)
return b
print(list_test(300))
print(list_test(400))
輸出爲:
---------set to None----------
2176805826496
[300]
2176805826496
[400]
可以看出,當函數判斷b是缺省值None時,會執行b=[ ],這將b重新初始化爲一個空列表。這樣,重複多次調用函數時,b的值不會相互影響。
上面函數中,我們只有兩個參數,第一個沒有缺省值,第二個有缺省值。如果我們把兩個參數交換一下順序,會出現什麼情況呢?
答案是編譯時會報錯,如下:
File "D:/跟我一起學python/練習/9/9_3.py", line 29
def list_test(b=None, a):
^
SyntaxError: non-default argument follows default argument
錯誤是:非缺省值的參數不能放在缺省值參數後面。
Python設計這樣的規則是有原因的。因爲帶缺省值的參數,在我們調用函數傳遞實參時,是可選的。如果我們把這些可選參數放在了必選參數之前,那麼python將不知道實參和形參的對應關係。比如一個函數定義爲:
def func_test(arg1=None, arg2, arg3=None):
pass
如果我們這樣調用這個函數:
func_test(a, b)
python解釋器不知道你傳遞的實參a對應的是arg1,還是對應的arg2,因爲arg1是可選的。
所以,python制定了規則:必選參數列表必須放在可選參數列表前面。
這並沒有完全解決所有的問題,如果我們有多個可選參數,也會出現問題。比如一個函數定義爲:
def func_test(arg1, arg2=None, arg3=None):
pass
如果我們這樣調用這個函數:
func_test(a, b)
python解釋器會認爲b對應的是arg2,而可能我們想傳遞的是arg3。
這裏就得用到下節講的關鍵字參數。
2.5、位置參數和關鍵字參數
事實上,python有兩種方式來映射實參和形參:位置參數(position arguments)和關鍵字參數(keyword arguments)。
- 位置參數
按照實參和形參的位置順序依次映射。
- 關鍵字參數
調用函數時,採用“形參=實參”這種鍵值對的方式指明映射關係。採用關鍵字參數時,其順序不需要按照形參定義的順序。
位置參數和關鍵字參數是混用的,一次函數調用可以同時使用這兩種方式。
對於上節的例子,我們可以採用關鍵字參數的方式來明確指定arg3:
func_test(a, arg3=b)
這樣就不會出現歧義。
我們看一下參數比較多的例子:
def open(file, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True):
pass
這是python的內建函數,用於打開一個文件,它除了第一個參數file是必選,其它都是可選參數。以下的調用方式都是正確的:
f = open('hello.txt')
f = open('hello.txt', 'w')
f = open('hello.txt', encoding='utf-8', newline='\n')
f = open(file='hello.txt', encoding='utf-8', newline='\n')
但是下面的調用方式是錯誤的:
f = open(file='hello.txt', '\n', encoding='utf-8') # error
會報錯:
File "D:/跟我一起學python/練習/9/9_4.py", line 16
f = open(file='hello.txt', '\n', encoding='utf-8') # error
^
SyntaxError: positional argument follows keyword argument
在傳實參時,位置參數必須在關鍵字參數之前。
Python還提供了*和/方式來強制某些形參只能採用位置參數或者只能採用關鍵字參數。
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
/之前的參數只能採用位置參數;
*之後的參數只能採用關鍵字參數;
兩者之間的參數不限制,可以採用任意方式。
看下面的實例:
# author: Tiger, 關注公衆號“跟哥一起學python”,ID:tiger-python
# file: ./9/9_3.py
# 位置參數和關鍵字參數
def my_echo(name, age, /, sex, *, city):
print(name, age, sex, city)
my_echo('xiaowang', 24, 'male', city='Beijing') # ok
my_echo('xiaowang', 24, 'male', 'Beijing') # error
my_echo('xiaowang', age=24, 'male', city='Beijing') # error
後面兩種調用方法是錯誤的,會報語法錯誤。
參數列表中不能出現多個*或者/,如下的定義是錯誤的:
def my_echo(name, /, age, /, sex, *, city):
print(name, age, sex, city)
2.6、變長參數
有些函數,我們需要它的參數是變長的,也就是說它的參數個數是不確定的。Python提供了實現變長參數的機制。
在定義形參時,採用*或者**來定義變長參數。
*定義的形參,實參必須採用位置參數的方式傳遞;
**定義的形參,實參必須採用關鍵字參數的方式傳遞。
我們來看一個例子:
# author: Tiger, 關注公衆號“跟哥一起學python”,ID:tiger-python
# file: ./9/9_5.py
# 變長參數
def goods_attrs(name, **kwargs):
print(f"name: {name}, attributes: {kwargs}")
goods_attrs("跟哥一起學python", pages=300, price=45, version='2.0')
def courses(student, *args):
print(f"name: {student}, 選修課程: {args}")
courses("小王", '高等數學', '計算機科學', '信號與系統', '英語')
輸出爲:
name: 跟哥一起學python, attributes: {'pages': 300, 'price': 45, 'version': '2.0'}
name: 小王, 選修課程: ('高等數學', '計算機科學', '信號與系統', '英語')
可以看到,**kwargs會自動將傳入的實參打包爲一個字典類型的對象,函數內部可以直接使用這個對象。同理,*args會自動將傳入的實參打包爲一個元組類型對象,函數內部也可以直接使用。
在函數定義時,**kwargs後面不能再有其它形參,而*args後面可以其它形參,但是必須採用關鍵字參數的方式來傳遞實參。這一點也比較好理解,因爲*args和**kwargs都是可變長參數,其參數個數是不確定的,如果後面有其它形參,那麼必須要能區分開。因爲*args要求必須是位置參數,所以在它後面的形參可以用關鍵字參數予以區分。但是**kwargs本身就是關鍵字參數,那麼它後面無論怎麼傳遞參數都無法區分。
大家想過一個問題嗎?**kwargs採用的是關鍵字參數,那麼如果關鍵字和前面定義的其它參數名重複了,怎麼辦呢?比如前面的例子,我們這樣調用就會出錯:
goods_attrs("跟哥一起學python", name="hello", pages=300, price=45, version='2.0')
輸出爲:
Traceback (most recent call last):
File "D:/跟我一起學python/練習/9/9_5.py", line 15, in <module>
goods_attrs("跟哥一起學python", name="hello", pages=300, price=45, version='2.0')
TypeError: goods_attrs() got multiple values for argument 'name'
因爲無論如何,python都會認爲name是我們定義的第一個形參,這樣調用它會認爲是參數重複了。
爲了解決這個問題,上一節我們講到的/語法就可以派上用場了,我們可以限定那麼參數必須採用位置參數的方式傳遞實參,這樣系統就能區分開。
def goods_attrs(name, /, **kwargs):
print(f"name: {name}, attributes: {kwargs}")
goods_attrs("跟哥一起學python", name="hello", pages=300, price=45, version='2.0')
輸出爲:
name: 跟哥一起學python, attributes: {'name': 'hello', 'pages': 300, 'price': 45, 'version': '2.0'}
變長參數的極端用法,是全部參數都是變長,比如我們定義一個函數,用於計算所有入參的平均數:
def calc_avrg(*args):
total = 0
for item in args:
total += item
else:
if len(args) == 0:
return 0
else:
return total / len(args)
print("average is: %.2f" % (calc_avrg()))
print("average is: %.2f" % (calc_avrg(1, 2, 100, 29, 39, 19, 30, 29)))
輸出爲:
average is: 0.00
average is: 31.12
2.7、解包參數列表
*和**同樣可以用於在傳遞實參時解包參數列表。
比如上一節的例子,我們也可以這樣調用:
# 解包參數列表
courses("小王", *('高等數學', '計算機科學', '信號與系統', '英語'))
goods_attrs("跟哥一起學python", **{'name':"hello", 'pages':300, 'price':45, 'version':'2.0'})
*是用於將元組解包爲一個一個成員;
**是用於將字典解包爲一個一個鍵值對。
它和我們上一節講的變長參數的過程剛好相反。
但是,需要注意的是,它們用於解包,不一定非得是變長參數。
def func_add(x, y):
return x + y
print(func_add(1, 5)) # 直接賦值
tmp = (1, 5)
print(func_add(*tmp)) # 使用*解包
tmp_dict = {'y': 5, 'x': 1}
print(func_add(**tmp_dict)) # 使用**解包
3、lambda
有時候,某些函數會非常簡單,簡單得我們都懶得給它起名字,這種沒有名字的函數叫“匿名函數”。Python中採用lambda語法來定義匿名函數,其語法如下:
lambda [arg1 [,arg2,.....argn]]:expression
首先是保留字lambda,後面跟0個或者多個參數列表,隨後是冒號:,冒號後面跟一個表達式。
注意,expression只能是一個表達式,這個表達式的值會被缺省通過return返回出去。所以,lambda無法寫複雜邏輯,它非常簡單。
Lambda通常用於作爲一些函數的實參,比如下面官方手冊的一個例子:
# author: Tiger, 關注公衆號“跟哥一起學python”,ID:tiger-python
# file: ./9/9_6.py
# lambda函數
list_1 = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
list_1.sort(key=lambda item: item[1])
print(list_1)
輸出爲:
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
參數key是一個函數類型,這個函數需要返回一個用於排序的值。我們定義了一個lambda函數,它返回了成員元組的第二個值。
我們讓這種函數匿名,就是因爲它們太簡單了,並且沒有太大的複用價值。畢竟起名有時候也挺頭痛的。但是,這並不意味着匿名函數就不能複用,看看下面的例子:
# lambda函數
list_1 = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
list_2 = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
key_func = lambda item: item[1]
list_1.sort(key=key_func)
list_2.sort(key=key_func)
print(list_1)
print(list_2)
這個例子中,我們定義了一個變量key_func,這個變量指向了lambda函數(萬物皆對象,函數也是對象)。這個變量可以重複使用的,lambda函數被變相複用了。
這種使用方法,是被python官方所鄙視的。因爲你既然都定義變量了,爲啥不直接定義一個def的函數呢?這種情況下就不要用lambda了。
在很多公司的編程規範中,是不建議程序員使用或者大量使用lambda的,因爲它會影響程序的可讀性。下面的代碼你是否能一下子看明白是什麼意思:
lower =
(lambda
x, y: x if
x < y else
y)
lambda的功能可以完全使用def函數替代。我們使用lambda時,一定要保證其表達式是非常簡單明確且易讀的,否則就老老實實採用def函數的方式吧。
4、嵌套和閉包
函數是可以嵌套的,包括lambda也是可以嵌套的。
# author: Tiger, 關注公衆號“跟哥一起學python”,ID:tiger-python
# file: ./9/9_7.py
# 函數嵌套
def calc_avrg(*args):
def calc_sum(*args1):
total = 0
for item in args1:
total += item
else:
return total
tmp_total = calc_sum(*args)
return tmp_total / len(args) if len(args) > 0 else 0
list_1 = [100, 200, 3, 4, 55]
print(calc_avrg(*list_1))
# lambda嵌套
y = lambda N: (lambda x: N * x)
func = y(2)
print(func(2)) # 輸出 4
上面第一個函數calc_avrg的內部又定義了一個函數calc_sum,形成了嵌套關係。在calc_avrg之外是不能調用calc_sum的,也就是說calc_sum的作用域僅限於calc_avrg內部。
第二個函數是一個lambda函數,它的表達式也是一個lambda函數,這就形成了函數嵌套關係。有趣的是,它返回的是一個lambda函數對象。這樣,我們在外部是可以調用內部嵌套的lambda函數。這種機制我們稱之爲“閉包(closure)”。
閉包的概念理解起來會稍顯困難。我們對前面的def函數稍作改造:
# 函數嵌套
def calc_avrg_1(*args_outer):
tmp_total = 0
def calc_avrg_1_inner(*args):
nonlocal tmp_total # 這裏的tmp_total採用外層變量
for item in args:
tmp_total += item
else:
return tmp_total / len(args) if len(args) > 0 else 0
return calc_avrg_1_inner(*args_outer)
list_1 = [100, 200, 3, 4, 55]
print(calc_avrg_1(*list_1))
print(calc_avrg_1(*list_1))
print(calc_avrg_1(*list_1))
輸出爲:
72.4
72.4
72.4
我們在外層函數定義了變量tmp_total,內層函數也使用該變量。我們調用了3次calc_avrg,最後的結果都是一樣的72.4。你肯定會說,這不廢話嗎?同一個函數,相同的實參,輸出的結果肯定一樣啊!
但是大家想過沒有,這三次調用同一個函數,爲什麼相互之間互不影響呢?原因在於,我們每次調用函數時,系統會給這個函數分配獨立的上下文運行環境,這裏面保存了本次調用的各種參數、臨時變量等等。所以,我們每次調用除了代碼段是一樣的,運行環境都是獨立的。
“閉包”機制改變了這一切!
我們再對這個函數進行改造,如下:
def calc_avrg():
tmp_total = 0
def calc_avrg_inner(*args):
nonlocal tmp_total # 這裏的tmp_total採用外層變量
for item in args:
tmp_total += item
else:
return tmp_total / len(args) if len(args) > 0 else 0
return calc_avrg_inner
list_1 = [100, 200, 3, 4, 55]
func_calc = calc_avrg()
print(func_calc(*list_1))
print(func_calc(*list_1))
print(func_calc(*list_1))
輸出爲:
72.4
144.8
217.2
我們把這個函數改成了閉包的形式,calc_avrg()返回的是其內部函數calc_avrg_inner的對象引用。這樣,外部可以直接調用calc_avrg_inner。我們看到,調用三次,每次的結果都不一樣,就像tmp_total被共享了一樣。沒錯,形成閉包之後,其臨時變量會被共享,每次函數調用之間會相互影響。
形成閉包需要有兩個條件:
- 外部引用了內部嵌套函數對象;
- 內部函數引用了外層變量。
形成閉包之後,一個函數實例func_calc會攜帶一個屬性func_calc.__closure__,這個屬性就是閉包屬性,它裏面存儲了外層變量的引用,並且在實例化之後不能被修改。
所以,我們看到tmp_total的值在多次調用過程中被共享了。如果我們多創建幾個函數實例,它們相互之間是不會影響的。
list_1 = [100, 200, 3, 4, 55]
func_calc = calc_avrg()
print(func_calc(*list_1))
print(func_calc(*list_1))
func_calc2 = calc_avrg()
print(func_calc2(*list_1))
輸出爲:
72.4
144.8
72.4
func_calc和func_calc2創建了兩個函數實例,它們之間的閉包屬性是獨立的,互不影響。我們可以斷點查看閉包屬性,如下:
cell_contents裏面存的就是閉包函數引用的變量,每個實例會創建一個獨立的,並且在每次調用共享。
對於沒有閉包的函數calc_avrg_1,它的閉包屬性爲空None。
這就是閉包的概念,有點不好理解。閉包有啥用呢?其實我們後面要講到的裝飾器Decorator就是用到了閉包原理。
本文有視頻講解,視頻和實例源碼下載方式:點擊->我的主頁,查看個人簡介。
我儘量堅持每日更新一節。
更多python教程,請查看我的專欄《0基礎學python視頻教程》