發(fā)布于:2021-01-19 16:43:18
0
320
0
讓我們看看編寫內(nèi)部函數(shù)的三個(gè)常見原因。
注意:在Python中,函數(shù)是“一等公民”。這意味著它們與任何其他對(duì)象(整數(shù)、字符串、列表、模塊等)相當(dāng)。您可以動(dòng)態(tài)地創(chuàng)建或銷毀它們、將它們傳遞給其他函數(shù)、將它們作為值返回等等。
本教程使用Python 3.4.1版。
封裝
您使用內(nèi)部函數(shù)來保護(hù)它們不受函數(shù)外部發(fā)生的任何事情的影響,這意味著它們被隱藏在全局范圍之外。
這里有一個(gè)簡單的例子突出了這個(gè)概念:
def outer(num1):
def inner_increment(num1): # Hidden from outer code
return num1 + 1
num2 = inner_increment(num1)
print(num1, num2)
inner_increment(10)
# outer(10)
嘗試調(diào)用inner_increment()
:
Traceback (most recent call last):
File "inner.py", line 7, ininner_increment()
NameError: name 'inner_increment' is not defined
現(xiàn)在注釋掉inner_increment()
調(diào)用并取消外部函數(shù)調(diào)用的注釋,outer(10)
,作為參數(shù)傳入:
10 11
注意:請(qǐng)記住這只是一個(gè)示例。盡管這段代碼確實(shí)達(dá)到了預(yù)期的效果,但是最好使用前導(dǎo)下劃線將inner_increment()
變成頂級(jí)的“私有”函數(shù):_inner_increment()。
下面的遞歸示例是嵌套函數(shù)的一個(gè)稍微好一點(diǎn)的用例:
def factorial(number):
# Error handling
if not isinstance(number, int):
raise TypeError("Sorry. 'number' must be an integer.")
if not number >= 0:
raise ValueError("Sorry. 'number' must be zero or positive.")
def inner_factorial(number):
if number <= 1:
return 1
return number*inner_factorial(number-1)
return inner_factorial(number)
# Call the outer function.
print(factorial(4))
也測試一下。使用此設(shè)計(jì)模式的一個(gè)主要優(yōu)點(diǎn)是,通過在外部函數(shù)中執(zhí)行所有參數(shù)檢查,可以安全地跳過內(nèi)部函數(shù)中的錯(cuò)誤檢查。
保持干燥
也許您有一個(gè)巨大的函數(shù),它在許多地方執(zhí)行相同的代碼塊。例如,您可能編寫了一個(gè)處理文件的函數(shù),并且希望接受打開的文件對(duì)象或文件名:
def process(file_name):
def do_stuff(file_process):
for line in file_process:
print(line)
if isinstance(file_name, str):
with open(file_name, 'r') as f:
do_stuff(f)
else:
do_stuff(file_name)
注意:同樣,通常只將do_stuff()
設(shè)為私有頂級(jí)函數(shù),但如果您希望將其作為內(nèi)部函數(shù)隱藏,則可以。一個(gè)實(shí)際的例子怎么樣?假設(shè)您想知道紐約市WiFi熱點(diǎn)的數(shù)量。是的,這個(gè)城市有原始數(shù)據(jù)告訴我們。訪問站點(diǎn)并下載CSV:
def process(file_name):
def do_stuff(file_process):
wifi_locations = {}
for line in file_process:
values = line.split(',')
# Build the dict and increment values.
wifi_locations[values[1]] = wifi_locations.get(values[1], 0) + 1
max_key = 0
for name, key in wifi_locations.items():
all_locations = sum(wifi_locations.values())
if key > max_key:
max_key = key
business = name
print(f'There are {all_locations} WiFi hotspots in NYC, '
f'and {business} has the most with {max_key}.')
if isinstance(file_name, str):
with open(file_name, 'r') as f:
do_stuff(f)
else:
do_stuff(file_name)
運(yùn)行函數(shù):
>>> process('NAME_OF_THE.csv')
There are 1251 WiFi hotspots in NYC, and Starbucks has the most with 212.
閉包和工廠函數(shù)
現(xiàn)在我們來討論使用內(nèi)部函數(shù)的最重要原因。到目前為止,我們看到的所有內(nèi)部函數(shù)示例都是普通函數(shù),只是碰巧嵌套在另一個(gè)函數(shù)中。換句話說,我們可以用另一種方式定義這些函數(shù)(如前所述)。沒有具體的理由說明為什么需要嵌套它們。
但是當(dāng)涉及到閉包時(shí),情況并非如此:必須使用嵌套函數(shù)。
什么是閉包?
閉包只會(huì)使內(nèi)部函數(shù)在調(diào)用時(shí)記住其環(huán)境的狀態(tài)。初學(xué)者通常認(rèn)為閉包是內(nèi)部函數(shù),但它實(shí)際上是由內(nèi)部函數(shù)引起的。閉包“關(guān)閉”堆棧上的局部變量,在堆棧創(chuàng)建完成后,這個(gè)問題仍然存在。
一個(gè)例子
這里有一個(gè)例子:
def generate_power(number):
"""
Examples of use:
>>> raise_two = generate_power(2)
>>> raise_three = generate_power(3)
>>> print(raise_two(7))
128
>>> print(raise_three(5))
243
"""
# Define the inner function ...
def nth_power(power):
return number ** power
# ... that is returned by the factory function.
return nth_power
示例中發(fā)生了什么
讓我們看看這個(gè)例子中發(fā)生了什么:
generate_power()是工廠函數(shù),僅表示每次調(diào)用它都會(huì)創(chuàng)建一個(gè)新函數(shù),然后返回新創(chuàng)建的函數(shù)。因此,raise_two()和raise_three()是新創(chuàng)建的功能。
這個(gè)新的內(nèi)部函數(shù)有什么作用?它只接受一個(gè)參數(shù)power,然后返回number**power。
內(nèi)部函數(shù)從哪里獲得價(jià)值number?這就是閉包起作用的地方:從外部函數(shù)(工廠函數(shù))nth_power()獲取值power。讓我們逐步完成此過程:
調(diào)用外部函數(shù):generate_power(2)。
Build nth_power(),它接受一個(gè)參數(shù)power。
拍攝的狀態(tài)快照nth_power(),其中包括number=2。
將該快照傳遞到中g(shù)enerate_power()。
返回nth_power()。
換句話說,閉包“初始化”其中的數(shù)字欄nth_power(),然后將其返回?,F(xiàn)在,無論何時(shí)調(diào)用該新返回的函數(shù),它都將始終看到其自己的私有快照,其中包括number=2。
結(jié)論
閉包和工廠函數(shù)的使用是內(nèi)部函數(shù)的最常見和最強(qiáng)大的用法。在大多數(shù)情況下,當(dāng)您看到修飾的函數(shù)時(shí),修飾器是一個(gè)工廠函數(shù),它將一個(gè)函數(shù)作為參數(shù)并返回一個(gè)新函數(shù),該新函數(shù)在閉包內(nèi)部包括舊函數(shù)。停止。深吸一口氣。喝杯咖啡。再讀一遍。
換句話說,裝飾器只是用于實(shí)現(xiàn)generate_power()示例中概述的過程的語法糖。
我給您留下最后一個(gè)示例:
def generate_power(exponent):
def decorator(f):
def inner(*args):
result = f(*args)
return exponent**result
return inner
return decorator
@generate_power(2)
def raise_two(n):
return n
print(raise_two(7))
@generate_power(3)
def raise_three(n):
return n
print(raise_two(5))
如果您的代碼編輯器允許,并排查看generate_power(exponent)
和generate_power(number)
以說明所討論的概念。(例如,Sublime Text具有列視圖。)
如果尚未對(duì)這兩個(gè)函數(shù)進(jìn)行編碼,請(qǐng)打開“代碼編輯器”并開始編碼。對(duì)于新程序員來說,編碼是一項(xiàng)實(shí)踐活動(dòng):就像騎自行車一樣,你只需要自己動(dòng)手,自己動(dòng)手。所以回到手頭的任務(wù)!
鍵入代碼后,您現(xiàn)在可以清楚地看到,它的相似之處在于產(chǎn)生相同的結(jié)果,但也存在差異。對(duì)于那些從未使用過decorators的人來說,如果你冒險(xiǎn)沿著這條路走下去,注意這些差異將是理解它們的第一步。
作者介紹