Python函數(shù)高級(jí)(命名空間、作用域、裝飾器)
一、名稱空間和作用域
1、命名空間(Namespace)
命名空間是從名稱到對(duì)象的映射,大部分的命名空間都是通過(guò) Python 字典來(lái)實(shí)現(xiàn)的。
命名空間提供了在項(xiàng)目中避免名字沖突的一種方法。各個(gè)命名空間是獨(dú)立的,沒(méi)有任何關(guān)系的,所以一個(gè)命名空間中不能有重名,但不同的命名空間是可以重名而沒(méi)有任何影響。
1、一般有三種命名空間:
- 內(nèi)置名稱空間(built-in names):存放內(nèi)置的名字,如
len/eval/enumerate/bytes/max/min/sorted/map/filter....
- 全局名稱空間(global names):模塊中定義的名稱,記錄了模塊的變量,包括函數(shù)、類、其它導(dǎo)入的模塊、模塊級(jí)的變量和常量。
- 局部名稱空間(local names):函數(shù)內(nèi)部的名字都是局部名稱空間,不同函數(shù)內(nèi)部的名字互不干涉。
2、命名空間查找順序:
如果找不到變量 runoob,它將放棄查找并引發(fā)一個(gè) NameError 異常:
NameError: name 'runoob' is not defined。
- 查找順序:假設(shè)我們要使用變量 runoob,則 Python 的查找順序?yàn)椋?strong>局部的命名空間去 -> 全局命名空間 -> 內(nèi)置命名空間。
- 執(zhí)行順序:先內(nèi)置(Python解釋器啟動(dòng)的時(shí)候才會(huì)生成)-> 全局(文件執(zhí)行的時(shí)候才會(huì)生成)-> 局部(函數(shù)調(diào)用的時(shí)候才會(huì)生成)
3、命名空間的生命周期:
命名空間的生命周期取決于對(duì)象的作用域,如果對(duì)象執(zhí)行完成,則該命名空間的生命周期就結(jié)束。
因此,我們無(wú)法從外部命名空間訪問(wèn)內(nèi)部命名空間的對(duì)象。
如下圖所示,相同的對(duì)象名稱可以存在于多個(gè)命名空間中。
2、作用域:
作用域就是一個(gè) Python 程序可以直接訪問(wèn)命名空間的正文區(qū)域。
全局名稱空間和局部名稱空間中可能會(huì)存在名字相同的變量,但是這兩個(gè)變量互不影響。
Python 中,程序的變量并不是在哪個(gè)位置都可以訪問(wèn)的,訪問(wèn)權(quán)限決定于這個(gè)變量是在哪里賦值的。
變量的作用域決定了在哪一部分程序可以訪問(wèn)哪個(gè)特定的變量名稱。
Python的作用域一共有4種,分別是:
- L(Local):最內(nèi)層,包含局部變量,比如一個(gè)函數(shù)/方法內(nèi)部。
- E(Enclosing):包含了非局部(non-local)也非全局(non-global)的變量。比如兩個(gè)嵌套函數(shù),一個(gè)函數(shù)(或類) A 里面又包含了一個(gè)函數(shù) B ,那么對(duì)于 B 中的名稱來(lái)說(shuō) A 中的作用域就為 nonlocal。
- G(Global):當(dāng)前腳本的最外層,比如當(dāng)前模塊的全局變量。
- B(Built-in): 包含了內(nèi)建的變量/關(guān)鍵字等。,最后被搜索
對(duì)于變量作用域,變量的訪問(wèn)以: L –> E –> G –>B 的 規(guī)則查找。
在局部找不到,便會(huì)去局部外的局部找(例如閉包),再找不到就會(huì)去全局找,再者去內(nèi)置中找。
舉例:
x = 1 def func(): print(x) #10 x = 10 func()
內(nèi)置作用域是通過(guò)一個(gè)名為 builtin 的標(biāo)準(zhǔn)模塊來(lái)實(shí)現(xiàn)的,但是這個(gè)變量名自身并沒(méi)有放入內(nèi)置作用域內(nèi),所以必須導(dǎo)入這個(gè)文件才能夠使用它。
在Python3.0中,可以使用以下的代碼來(lái)查看到底預(yù)定義了哪些變量:
import builtins print(dir(builtins))
Python 中只有模塊(module),類(class)以及函數(shù)(def、lambda)才會(huì)引入新的作用域,其它的代碼塊(如 if/elif/else/、try/except、for/while等)是不會(huì)引入新的作用域的,也就是說(shuō)這些語(yǔ)句內(nèi)定義的變量,外部也可以訪問(wèn),
如下代碼:實(shí)例中 msg 變量定義在 if 語(yǔ)句塊中,但外部還是可以訪問(wèn)的。如果將 msg 定義在函數(shù)中,則它就是局部變量,外部不能訪問(wèn)。
if True: msg = 'I am from Runoob' print(msg) # 'I am from Runoob'
3、全局變量和局部變量
定義在函數(shù)內(nèi)部的變量擁有一個(gè)局部作用域,定義在函數(shù)外的擁有全局作用域。
局部變量只能在其被聲明的函數(shù)內(nèi)部訪問(wèn),而全局變量可以在整個(gè)程序范圍內(nèi)訪問(wèn)。調(diào)用函數(shù)時(shí),所有在函數(shù)內(nèi)聲明的變量名稱都將被加入到作用域中。
# 作用域注意點(diǎn) x = 1 def f1(): # 定義階段x=1 print(x) #1 def f2(): x = 2 #此x為f2函數(shù)的局部變量,f1無(wú)法直接訪問(wèn) f1() f2()
4、函數(shù)對(duì)象+作用域應(yīng)用
def f1(): def inner(): print('from inner') return inner f = f1() # from inner 。把局部定義的函數(shù)inner()放在全局之中 def bar(): f() bar()
5、global關(guān)鍵字修改全局作用域中的變量
函數(shù)內(nèi)可以訪問(wèn)全局變量,但不能直接更新(修改)其值,可以加上 global 引用以更新變量值 :
x = 1 def f1(): x = 2 def f2(): global x # 修改全局 x = 3 f2() f1() print(x) # 3
6、nonlocal關(guān)鍵字修改嵌套作用域中的變量。
如果要修改嵌套作用域(enclosing 作用域,外層非全局作用域)中的變量則需要 nonlocal 關(guān)鍵字了
x = 1 def f1(): x = 2 def f2(): nonlocal x x = 3 f2() print(x) # 3 f1()
二、閉包函數(shù)
閉包:閉是封閉(函數(shù)內(nèi)部函數(shù)),包是包含(該內(nèi)部函數(shù)對(duì)外部作用域而非全局作用域的變量的引用)。
閉包指的是:函數(shù)內(nèi)部函數(shù)對(duì)外部作用域而非全局作用域的引用。
def outter(x): x = 1 def inner(): print(x) return inner #返回的是函數(shù)名(函數(shù)對(duì)象) f = outter(2) f() # 1 f() # 1 f() # 1 # 查看閉包的元素 print(f.__closure__[0].cell_contents) # 1
閉包的意義:返回的函數(shù)對(duì)象,不僅僅是一個(gè)函數(shù)對(duì)象,在該函數(shù)外還包裹了一層作用域,這使得,該函數(shù)無(wú)論在何處調(diào)用,優(yōu)先使用自己外層包裹的作用域。
應(yīng)用領(lǐng)域:
延遲計(jì)算(原來(lái)我們是傳參,現(xiàn)在我們是包起來(lái))、爬蟲領(lǐng)域。
import requests def outter(url): def get(): response = requests.get(url) print(f"done: {url}") return get baidu = outter('https://www.baidu.com') python = outter('https://www.python.org') baidu() baidu() python() python()
三、函數(shù)裝飾器
裝飾器指的是為被裝飾器對(duì)象添加額外功能。因此定義裝飾器就是定義一個(gè)函數(shù),只不過(guò)該函數(shù)的功能是用來(lái)為其他函數(shù)添加額外的功能。裝飾器的實(shí)現(xiàn)必須遵循兩大原則:
- 不修改被裝飾對(duì)象的源代碼
- 不修改被裝飾對(duì)象的調(diào)用方式
裝飾器其實(shí)就是在遵循以上兩個(gè)原則的前提下為被裝飾對(duì)象添加新功能。
不改變函數(shù)體代碼,并且不改變函數(shù)調(diào)用方式,它本質(zhì)就是一個(gè)閉包函數(shù)。
def f1(x): def f2(): print(x) # 10 return f2 f2 = f1() f2() # f2()
在不改變當(dāng)前函數(shù)的情況下, 給其增加新的功能:
def log(pr): # 將被裝飾函數(shù)傳入 def wrapper(): print("**********") return pr() # 執(zhí)行被裝飾的函數(shù) return wrapper # 將裝飾完之后的函數(shù)返回(返回的是函數(shù)名) @log def pr(): print("我是小小洋") pr() # ********** # 我是小小洋
回調(diào)函數(shù)和返回函數(shù)的實(shí)例就是裝飾器。
四、無(wú)參裝飾器
舉例:
import time def index(): print('welcome to index') time.sleep(1) def time_count(func): # func = 最原始的index def wrapper(): start = time.time() func() end = time.time() print(f"{func} time is {start - end}") # time is -1.0038220882415771 return wrapper index = time_count(index) # index為被裝飾函數(shù)index的內(nèi)存地址,即index = wrapper index() # wrapper()
1、被裝飾函數(shù)有返回值:
如果原始的被裝飾函數(shù)index()有返回值的時(shí)候,wrapper()函數(shù)的返回值應(yīng)該和index()的返回值相同,也就是說(shuō),我們需要同步原始的index()和wrapper()方法的返回值。
import time def index(): print('welcome to index') time.sleep(1) return 123 def time_count(func): # func = 最原始的index def wrapper(): start = time.time() res1 = func() end = time.time() print(f"{func} time is {start - end}") # time is -1.0050289630889893 return res1 return wrapper index = time_count(index) res = index() print(f"res: {res}") # res: 123
2、被裝飾函數(shù)需要傳參:
如果原始的被裝飾函數(shù)index()方法需要傳參,那么我們之前的裝飾器是無(wú)法實(shí)現(xiàn)該功能的,由于有wrapper()=index(),所以給wrapper()方法傳參即可。
import time def index(): print('welcome to index') time.sleep(1) return 123 def home(name): print(f"welcome {name} to home page") time.sleep(1) return name def time_count(func): def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) end = time.time() print(f"{func} time is {start-end}") # time is -1.0039079189300537 return res return wrapper home = time_count(home) res = home('egon') print(f"res: {res}") #res: egon
3、裝飾器模板
def deco(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) return res return wrapper
4、裝飾器語(yǔ)法糖:
在被裝飾函數(shù)正上方,并且是單獨(dú)一行寫上@裝飾器名
import time def time_count(func): #裝飾器 # func = 最原始的index def wrapper(*args, **kwargs): start = time.time() res = func(*args, **kwargs) end = time.time() print(f"{func} time is {start-end}") # time is -1.0005171298980713 return res return wrapper @time_count # home = time_count(home) def home(name): print(f"welcome {name} to home page") #welcome egon to home page time.sleep(1) return name res = home('egon') print(f"res: {res}") #res: egon
五、帶參數(shù)的裝飾器
注意無(wú)參裝飾器只套兩層。
import time current_user = {'username': None} def login(func): # func = 最原始的index def wrapper(*args, **kwargs): if current_user['username']: res1 = func(*args, **kwargs) return res1 user = input('username: ').strip() pwd = input('password: ').strip() if user == 'nick' and pwd == '123': print('login successful') current_user['username'] = user res1 = func(*args, **kwargs) return res1 else: print('user or password error') return wrapper @login def index(): print('welcome to index') time.sleep(1) res = index() #username: nick #password: 123 #login successful #welcome to index
我們首先看看三層閉包怎么運(yùn)用。
def f1(y): def f2(): x = 1 def f3(): print(f"x: {x}") # x: 1 print(f"y: {y}") # x: 1 return f3 return f2 f2 = f1(2) f3 = f2() f3()
3、有參三層裝飾器:
在函數(shù)中嵌入裝飾器
import time current_user = {'username': None} def auth(engine='file'): def login(func): def wrapper(*args, **kwargs): if current_user['username']: res = func(*args, **kwargs) return res user = input('username: ').strip() pwd = input('password: ').strip() if engine == 'file': print('base of file') if user == 'nick' and pwd == '123': print('login successful') current_user['username'] = user res = func(*args, **kwargs) return res else: print('user or password error') elif engine == 'mysql': print('base of mysql, please base of file') return wrapper return login @auth(engine='file') def index(): print('welcome to index') time.sleep(1) res = index()
username: nick
password: 123
base of file
login successful
welcome to index
六、類裝飾器
沒(méi)錯(cuò),裝飾器不僅可以是函數(shù),還可以是類,相比函數(shù)裝飾器,類裝飾器具有靈活度大、高內(nèi)聚、封裝性等優(yōu)點(diǎn)。使用類裝飾器主要依靠類的__call__方法,當(dāng)使用 @ 形式將裝飾器附加到函數(shù)上時(shí),就會(huì)調(diào)用此方法。
class Foo(object): def __init__(self, func): self._func = func def __call__(self): print ('class decorator runing') self._func() print ('class decorator ending') @Foo def bar(): print ('bar') bar() functools.wraps
使用裝飾器極大地復(fù)用了代碼,但是他有一個(gè)缺點(diǎn)就是原函數(shù)的元信息不見(jiàn)了,比如函數(shù)的docstring、__name__、參數(shù)列表,先看例子:
# 裝飾器 def logged(func): def with_logging(*args, **kwargs): print func.__name__ # 輸出 'with_logging' print func.__doc__ # 輸出 None return func(*args, **kwargs) return with_logging # 函數(shù) @logged def f(x): """does some math""" return x + x * x logged(f)
不難發(fā)現(xiàn),函數(shù) f 被with_logging取代了,當(dāng)然它的docstring,__name__就是變成了with_logging函數(shù)的信息了。好在我們有functools.wraps,wraps本身也是一個(gè)裝飾器,它能把原函數(shù)的元信息拷貝到裝飾器里面的 func 函數(shù)中,這使得裝飾器里面的 func 函數(shù)也有和原函數(shù) foo 一樣的元信息了。
from functools import wraps def logged(func): @wraps(func) def with_logging(*args, **kwargs): print func.__name__ # 輸出 'f' print func.__doc__ # 輸出 'does some math' return func(*args, **kwargs) return with_logging @logged def f(x): """does some math""" return x + x * x
七、裝飾器順序
一個(gè)函數(shù)還可以同時(shí)定義多個(gè)裝飾器,比如:
@a @b @c def f (): pass
它的執(zhí)行順序是從里到外,最先調(diào)用最里層的裝飾器,最后調(diào)用最外層的裝飾器,它等效于
f = a(b(c(f)))
八、裝飾器使用場(chǎng)景
現(xiàn)在我們來(lái)看一下裝飾器在哪些地方特別耀眼,以及使用它可以讓一些事情管理起來(lái)變得更簡(jiǎn)單。
授權(quán)(Authorization)
裝飾器能有助于檢查某個(gè)人是否被授權(quán)去使用一個(gè)web應(yīng)用的端點(diǎn)(endpoint)。它們被大量使用于Flask和Django web框架中。這里是一個(gè)例子來(lái)使用基于裝飾器的授權(quán):
from functools import wraps def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): auth = request.authorization if not auth or not check_auth(auth.username, auth.password): authenticate() return f(*args, **kwargs) return decorated
日志(Logging)
日志是裝飾器運(yùn)用的另一個(gè)亮點(diǎn)。這是個(gè)例子:
from functools import wraps def logit(func): @wraps(func) def with_logging(*args, **kwargs): print(func.__name__ + " was called") return func(*args, **kwargs) return with_logging @logit def addition_func(x): """Do some math.""" return x + x result = addition_func(4) # Output: addition_func was called
到此這篇關(guān)于Python函數(shù)高級(jí)用法的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
python如何繪制極坐標(biāo)輪廓圖contourf
這篇文章主要介紹了python如何繪制極坐標(biāo)輪廓圖contourf問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08從零學(xué)python系列之?dāng)?shù)據(jù)處理編程實(shí)例(二)
這篇文章主要介紹了python數(shù)據(jù)處理編程實(shí)例,需要的朋友可以參考下2014-05-05python之Socket網(wǎng)絡(luò)編程詳解
這篇文章主要為大家詳細(xì)介紹了python之Socket網(wǎng)絡(luò)編程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09Python用Try語(yǔ)句捕獲異常的實(shí)例方法
在本篇文章中小編給大家整理了關(guān)于Python用Try語(yǔ)句如何捕獲異常的相關(guān)知識(shí)點(diǎn)內(nèi)容,需要的朋友們參考下。2019-06-06python3 實(shí)現(xiàn)調(diào)用串口功能
今天小編就為大家分享一篇python3 實(shí)現(xiàn)調(diào)用串口功能,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-12-12Python Flask基礎(chǔ)到登錄功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了Python Flask基礎(chǔ)到登錄功能的實(shí)現(xiàn)代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05python抓取網(wǎng)頁(yè)圖片并放到指定文件夾
這篇文章主要介紹了python抓取網(wǎng)頁(yè)圖片并放到指定文件夾,需要的朋友可以參考下2014-04-04python try except返回異常的信息字符串代碼實(shí)例
這篇文章主要介紹了python try except返回異常的信息字符串代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08