Python函數(shù)高級(jí)(命名空間、作用域、裝飾器)
一、名稱空間和作用域
1、命名空間(Namespace)
命名空間是從名稱到對(duì)象的映射,大部分的命名空間都是通過 Python 字典來實(shí)現(xiàn)的。
命名空間提供了在項(xiàng)目中避免名字沖突的一種方法。各個(gè)命名空間是獨(dú)立的,沒有任何關(guān)系的,所以一個(gè)命名空間中不能有重名,但不同的命名空間是可以重名而沒有任何影響。
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é)束。
因此,我們無法從外部命名空間訪問內(nèi)部命名空間的對(duì)象。
如下圖所示,相同的對(duì)象名稱可以存在于多個(gè)命名空間中。

2、作用域:
作用域就是一個(gè) Python 程序可以直接訪問命名空間的正文區(qū)域。
全局名稱空間和局部名稱空間中可能會(huì)存在名字相同的變量,但是這兩個(gè)變量互不影響。
Python 中,程序的變量并不是在哪個(gè)位置都可以訪問的,訪問權(quán)限決定于這個(gè)變量是在哪里賦值的。
變量的作用域決定了在哪一部分程序可以訪問哪個(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 中的名稱來說 A 中的作用域就為 nonlocal。
- G(Global):當(dāng)前腳本的最外層,比如當(dāng)前模塊的全局變量。
- B(Built-in): 包含了內(nèi)建的變量/關(guān)鍵字等。,最后被搜索
對(duì)于變量作用域,變量的訪問以: L –> E –> G –>B 的 規(guī)則查找。
在局部找不到,便會(huì)去局部外的局部找(例如閉包),再找不到就會(huì)去全局找,再者去內(nèi)置中找。

舉例:
x = 1
def func():
print(x) #10
x = 10
func()內(nèi)置作用域是通過一個(gè)名為 builtin 的標(biāo)準(zhǔn)模塊來實(shí)現(xiàn)的,但是這個(gè)變量名自身并沒有放入內(nèi)置作用域內(nèi),所以必須導(dǎo)入這個(gè)文件才能夠使用它。
在Python3.0中,可以使用以下的代碼來查看到底預(yù)定義了哪些變量:
import builtins print(dir(builtins))
Python 中只有模塊(module),類(class)以及函數(shù)(def、lambda)才會(huì)引入新的作用域,其它的代碼塊(如 if/elif/else/、try/except、for/while等)是不會(huì)引入新的作用域的,也就是說這些語(yǔ)句內(nèi)定義的變量,外部也可以訪問,
如下代碼:實(shí)例中 msg 變量定義在 if 語(yǔ)句塊中,但外部還是可以訪問的。如果將 msg 定義在函數(shù)中,則它就是局部變量,外部不能訪問。
if True:
msg = 'I am from Runoob'
print(msg)
# 'I am from Runoob'3、全局變量和局部變量
定義在函數(shù)內(nèi)部的變量擁有一個(gè)局部作用域,定義在函數(shù)外的擁有全局作用域。
局部變量只能在其被聲明的函數(shù)內(nèi)部訪問,而全局變量可以在整個(gè)程序范圍內(nèi)訪問。調(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無法直接訪問
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)可以訪問全局變量,但不能直接更新(修改)其值,可以加上 global 引用以更新變量值 :
x = 1
def f1():
x = 2
def f2():
global x # 修改全局
x = 3
f2()
f1()
print(x) # 36、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ù)無論在何處調(diào)用,優(yōu)先使用自己外層包裹的作用域。
應(yīng)用領(lǐng)域:
延遲計(jì)算(原來我們是傳參,現(xiàn)在我們是包起來)、爬蟲領(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ù),只不過該函數(shù)的功能是用來為其他函數(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í)例就是裝飾器。
四、無參裝飾器
舉例:
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()的返回值相同,也就是說,我們需要同步原始的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: 1232、被裝飾函數(shù)需要傳參:
如果原始的被裝飾函數(shù)index()方法需要傳參,那么我們之前的裝飾器是無法實(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: egon3、裝飾器模板
def deco(func):
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
return res
return wrapper4、裝飾器語(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ù)的裝飾器
注意無參裝飾器只套兩層。
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
六、類裝飾器
沒錯(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ù)的元信息不見了,比如函數(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)在我們來看一下裝飾器在哪些地方特別耀眼,以及使用它可以讓一些事情管理起來變得更簡(jiǎn)單。
授權(quán)(Authorization)
裝飾器能有助于檢查某個(gè)人是否被授權(quán)去使用一個(gè)web應(yīng)用的端點(diǎn)(endpoint)。它們被大量使用于Flask和Django web框架中。這里是一個(gè)例子來使用基于裝飾器的授權(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問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08
從零學(xué)python系列之?dāng)?shù)據(jù)處理編程實(shí)例(二)
這篇文章主要介紹了python數(shù)據(jù)處理編程實(shí)例,需要的朋友可以參考下2014-05-05
python之Socket網(wǎng)絡(luò)編程詳解
這篇文章主要為大家詳細(xì)介紹了python之Socket網(wǎng)絡(luò)編程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
Python用Try語(yǔ)句捕獲異常的實(shí)例方法
在本篇文章中小編給大家整理了關(guān)于Python用Try語(yǔ)句如何捕獲異常的相關(guān)知識(shí)點(diǎn)內(nèi)容,需要的朋友們參考下。2019-06-06
python3 實(shí)現(xiàn)調(diào)用串口功能
今天小編就為大家分享一篇python3 實(shí)現(xiàn)調(diào)用串口功能,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-12-12
Python Flask基礎(chǔ)到登錄功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了Python Flask基礎(chǔ)到登錄功能的實(shí)現(xiàn)代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05
python抓取網(wǎng)頁(yè)圖片并放到指定文件夾
這篇文章主要介紹了python抓取網(wǎng)頁(yè)圖片并放到指定文件夾,需要的朋友可以參考下2014-04-04
python try except返回異常的信息字符串代碼實(shí)例
這篇文章主要介紹了python try except返回異常的信息字符串代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08

