1. 函式名的多種用法
1.1 方法一:函式名可以當成變數名賦值
def f1(x, y):
print(x + y)
fn = f1
fn(11, 22) # 33
1.2 方法二:函式名可以作為容器型別的元素
def f1(x, y):
print(x + y)
func_dict = {'Add': f1}
func_dict.get('Add')(33, 66) # 99
1.3 方法三:函式名可以作為引數傳遞給另一個函式使用
def f1(x, y):
return x + y
def f2(a, b, func):
return func(a, b) + a + b
result = f2(a=1, b=2, func=f1)
print(result) # 6
1.4 方法四:函式名可以作為函式的返回值
def f1(x, y):
return x + y
print(id(f1))
def f2():
return f1
print(id(f2()))
2. 閉包函式
2.1 概念
外層函式內部定義一個裡層函式,並且裡層函式用到了外層函式的變數
閉:定義在函式內部的函式
包:內層函式使用了外層函式名稱空間中的名字
def outer():
a = 666
def inner():
print('裡層函式', a) # 裡層函式使用了外層函式名稱空間中的名字
inner()
outer()
2.2 閉包函式的應用場景
閉包傳參方式1:外層函式的變數值固定
def outer():
a = 666
def inner():
print('裡層函式', a) # 裡層函式使用了外層函式名稱空間中的名字
return inner
res = outer()
res() # 裡層函式 666 相當於inner()
閉包傳參方式2:外層函式的變數值不固定
def outer(name):
# 當有實參傳入時,name=實參
def inner():
print('裡層函式', name) # 裡層函式使用了外層函式名稱空間中的名字
return inner
res = outer('leomessi')
res() # 裡層函式 leomessi 相當於inner()
3. 裝飾器
3.1 基礎概念
定義:
在不改變被裝飾物件原有的“呼叫方式”和“內部程式碼”的情況下,給被裝飾物件新增新的功能
裝飾器的原則:
對擴充套件開放,對修改封閉
key word:
名稱空間、函式名、閉包函式
裝飾器的分類:
無參裝飾器、有參裝飾器
3.2 推導
需求:計算函式的執行時間
# 預備知識:time模組
import time
print(time.time()) # 時間戳,是從1970年1月1日(UTC/GMT的午夜)開始所經過的秒數(不考慮閏秒),用於表示一個時間點。
實現方法1:
沒有修改被裝飾物件的呼叫方式,也沒修改內部程式碼,但是可擴充套件性差
import time
def f1():
start = time.time()
time.sleep(3)
end = time.time()
print(f'該函式執行耗時{end - start}s') # 該函式執行耗時3.0012145042419434s
f1()
def f2():
start = time.time()
time.sleep(3)
end = time.time()
print(f'該函式執行耗時{end - start}s') # 該函式執行耗時3.01076340675354s
f2()
實現方法2:
在很多地方都要計算f1、f2執行時間,就用到了函式
將函式名透過形參傳入,封裝成函式之後,呼叫方式變了,不符合裝飾器原則
import time
def f1():
time.sleep(3)
print('函式f1執行')
def f2():
time.sleep(3)
print('函式f2執行')
def cal(func):
start = time.time()
func()
end = time.time()
print(f'函式{func}執行耗時{end - start}s')
cal(f1)
# 函式f1執行
# 函式<function f1 at 0x000001F911B23E20>執行耗時3.0101499557495117s
cal(f2)
# 函式f2執行
# 函式<function f2 at 0x000001A3B73F3370>執行耗時3.015449047088623s
實現方法3:
方法2給函式體直接傳參無法實現裝飾器,於是採用另一種給函式體傳參的方式(閉包函式)
閉包函式的方式外層函式的變數值固定,無法將其它函式名傳入,可擴充套件性差
import time
def f1():
time.sleep(3)
print('函式f1執行')
def outer():
func = f1 # 真正的f1被outer區域性名稱空間儲存了
def inner():
start = time.time()
func() # 呼叫了真正的f1函式
end = time.time()
print(f'函式{func}執行耗時{end - start}s')
return inner
res = outer()
res()
# 函式f1執行
# 函式<function f1 at 0x0000025DCD183E20>執行耗時3.0119853019714355s
這種方式也不是真正的裝飾器,需要呼叫res函式才能呼叫f1函式
實現方法4:
為了解決方法3外層函式變數值固定的問題,採用向外層函式傳參的方式
將函式名透過形參傳入,增強可擴充套件性
import time
def f1():
time.sleep(3)
print('函式f1執行')
def f2():
time.sleep(3)
print('函式f2執行')
def outer(func):
def inner():
start = time.time()
func() # 呼叫了真正的f1函式
end = time.time()
print(f'函式{func}執行耗時{end - start}s')
return inner
# f1 = outer(f1) # 左側的f1就是一個普通的變數名
# f1() # 看似呼叫的是f1,其實呼叫的是inner
# 函式f1執行
# 函式<function f1 at 0x0000021BEC373E20>執行耗時3.0068602561950684s
f2 = outer(f2) # 左側的f2就是一個普通的變數名
f2() # 看似呼叫的是f2,其實呼叫的是inner
f1 = outer(f1)實現了裝飾器,f1()即可呼叫原f1函式,右邊的f1是原函式名,存到outer的區域性名稱空間了,
左邊的f1只是一個普通的變數名
小結:
outer()---返回值是inner的函式地址---將inner的函式名重新命名為f1或f2
f1=outer(f1)---f1就是outer內inner的記憶體地址
f1()---outer內的inner執行---func()---f1真正的函式記憶體地址---f1()
3.3 練習
# 練習:
"""
先定義一個存款、取款函式,使用者登入過才能存款、取款,沒有登入讓先登入
不能改變原本存款、取款的呼叫方式
"""
def deposit():
print('存款中...')
return True, '取款完成'
def withdraw():
print('存款中...')
return True, '取款完成'
login_dict = {'username': None, 'status': None}
def outer(func):
def inner():
# 在執行withdraw功能之前進行驗證是否登入
if login_dict.get('username') is not None:
res = func() # 接收真正函式的返回值
return res # 條件為真,將真正函式的返回值返回
else:
return False, '請先登入' # 條件為假,讓使用者先登入
return inner
withdraw = outer(withdraw)
res2 = withdraw()
print(res2)
deposit = outer(deposit)
res3 = deposit()
print(res3)
# 多層驗證,先驗證使用者名稱,再驗證密碼
def outer(func):
def inner():
# 在執行withdraw功能之前進行驗證是否登入成功
if login_dict.get('username') != 'messi':
return False, '使用者名稱錯誤'
elif login_dict.get('password') != '001':
return False, '請先登入' # 密碼錯誤
else:
res = func()
return res
return inner
withdraw = outer(withdraw)
res2 = withdraw()
print(res2)
3.4 無參裝飾器模板
def f1():
pass
def outer(func):
def inner():
執行真正函式之前,可以做的額外操作
# 其它如登入認證等操作一般放在執行真正函式之前
result = func() # 執行真正的函式
執行真正函式之後,可以做的額外操作
對返回的結果進行處理返回
return result # 返回真正函式的返回值
return inner
f1 = outer(f1)
f1()
3.5 有參裝飾器
引入
login_dict = {'name': 'messi', 'pwd': '001'}
def withdraw(name, money):
print(f'使用者{name}正在取款{money}元')
def outer(func):
def inner(name, money):
if login_dict.get('name'):
return func(name, money)
else:
return False, '請先登入'
return inner
withdraw = outer(withdraw)
withdraw('messi', 10) # 使用者messi正在取款10元
可變長引數,相容所有函式的引數
login_dict = {'name': 'messi', 'pwd': '001'}
def withdraw(name, money):
print(f'使用者{name}正在取款{money}元')
def transfer(name, to_other, money):
print(f'使用者{name}給{to_other}轉賬了{money}元')
def outer(func):
def inner(*args, **kwargs):
if login_dict.get('name'):
return func(*args, **kwargs)
else:
return False, '請先登入'
return inner
withdraw = outer(withdraw)
withdraw('messi', 10) # 使用者messi正在取款10元
transfer = outer(transfer)
transfer('ronaldo', 'kylian', 20) # 使用者ronaldo給kylian轉賬了20元
3.6 有參裝飾器模板
def f1(*args, **kwargs):
pass
def outer(func): # func用於接收被裝飾的物件(函式)
def inner(*args, **kwargs):
print('執行真正函式之前,可以做的額外操作')
res = func(*args, **kwargs) # 執行真正的被裝飾函式,res的作用是接收原函式被呼叫後的返回值
print('執行真正函式之後,可以做的額外操作')
對返回的結果進行處理返回
return res # 返回真正函式的返回值
return inner
f1 = outer(f1)
f1()
4. 語法糖
4.1 原理:
1.使用的時候放在真正函式的上方
2.語法糖會自動將下面緊挨著的函式名傳給 @ 後面的函式呼叫
login_dict = {'name': 'messi', 'pwd': '001'}
def outer(func):
def inner(*args, **kwargs):
if login_dict.get('name'):
return func(*args, **kwargs)
else:
return False, '請先登入'
return inner
@outer # 相當於:withdraw = outer(withdraw)
def withdraw(name, money):
print(f'使用者{name}正在取款{money}元')
@outer # 相當於:transfer = outer(transfer)
def transfer(name, to_other, money):
print(f'使用者{name}給{to_other}轉賬了{money}元')
4.2 多層語法糖
4.2.1 多層有參裝飾器
login_dict = {'name': 'messi', 'role': 'admin'}
def login(func):
# func是withdraw函式的記憶體地址
def inner(*args, **kwargs):
print('驗證登入程式')
if not login_dict.get('name'):
return '請先登入'
else:
return func(*args, **kwargs)
return inner
def permission(func):
# func是login的inner函式地址
def inner(*args, **kwargs):
print('驗證許可權程式')
if login_dict.get('role') != 'admin':
return '當前許可權不能訪問該功能'
return func(*args, **kwargs)
return inner
def withdraw(name, money):
print(f'使用者{name}正在取款{money}元')
# 必須是登入狀態且角色是管理員才能取款
withdraw = login(withdraw) # =左邊是一個普通的變數名,右邊是login函式返回值(inner函式記憶體地址)
withdraw = permission(withdraw) # 等價於withdraw = permission(login的inner函式地址)
# 上述語句在執行時,
# 第一步:permission(login的inner)
# 第二步:在permission的裡層函式中func為login的inner函式,因此進入到login函式中校驗登入
# 第三步:進入login函式執行inner函式,校驗登入,執行func,func為withdraw函式的記憶體地址
# 第四步:執行真正的withdraw
withdraw('ronaldo', 20)
# 驗證許可權程式
# 驗證登入程式
# 使用者ronaldo正在取款20元
# 上述程式碼按從上到下的順序編寫,先包login,再包permission,但是執行時先驗證許可權,再驗證登入,因此要將登入和許可權互換位置
# # 這種情況下就是先驗證登入,再驗證許可權
# withdraw = permission(withdraw)
# withdraw = login(withdraw)
# withdraw('ronaldo', 20)
4.2.2 多層語法糖
多層語法糖的包裝順序是:從下往上包裝⬆
執行順序是:從上往下執行⬇
login_dict = {'name': 'messi', 'role': 'admin'}
def login(func):
# func是withdraw函式的記憶體地址
def inner(*args, **kwargs):
print('驗證登入程式')
if not login_dict.get('name'):
return '請先登入'
else:
return func(*args, **kwargs)
return inner
def permission(func):
# func是login的inner函式地址
def inner(*args, **kwargs):
print('驗證許可權程式')
if login_dict.get('role') != 'admin':
return '當前許可權不能訪問該功能'
return func(*args, **kwargs)
return inner
@login # withdraw = login(withdraw) 左邊with普通函式名,右邊with取自上一步,是permission的inner函式
@permission # withdraw=permission(withdraw) 左邊withdraw普通變數名,右邊真正函式,執行結果是permission的inner函式
def withdraw(name, money):
print(f'使用者{name}正在取款{money}元')
# 上述語法糖含義:先包permission,再包login
# 呼叫函式時,以@login為參考,這裡withdraw等價於login(permission中inner函式地址)
withdraw('messi', 10)
4.3 有參語法糖
有參的有參裝飾器
login_dict = {'name': 'messi', 'role': 'admin'}
# 定義一個函式,當傳入引數為login時,返回login登入驗證函式
# 當傳入引數為permission時,返回permission許可權驗證函式
def choice(status):
if status == 'login':
def login(func):
# func是withdraw函式的記憶體地址
def inner(*args, **kwargs):
print('驗證登入程式')
if not login_dict.get('name'):
return '請先登入'
else:
return func(*args, **kwargs)
return inner
return login
elif status == 'permission':
def permission(func):
# func是login的inner函式地址
def inner(*args, **kwargs):
print('驗證許可權程式')
if login_dict.get('role') != 'admin':
return '當前許可權不能訪問該功能'
return func(*args, **kwargs)
return inner
return permission
def withdraw(name, money):
print(f'使用者{name}正在取款{money}元')
# 以下僅驗證登入
# login_a = choice('login') # 拿到login函式
# withdraw = login_a(withdraw)
# withdraw('messi', 10)
# 驗證登入程式
# 使用者messi正在取款10元
# 先驗證登入再驗證許可權
login_a = choice('login')
permission_a = choice('permission')
withdraw = permission_a(withdraw)
withdraw = login_a(withdraw)
withdraw('ronaldo', 20)
# 驗證登入程式
# 驗證許可權程式
# 使用者ronaldo正在取款20元
上述程式碼改寫為有參語法糖
login_dict = {'name': 'messi', 'role': 'admin'}
# 定義一個函式,當傳入引數為login時,返回login登入驗證函式
# 當傳入引數為permission時,返回permission許可權驗證函式
def choice(status):
if status == 'login':
def login(func):
# func是withdraw函式的記憶體地址
def inner(*args, **kwargs):
print('驗證登入程式')
if not login_dict.get('name'):
return '請先登入'
else:
return func(*args, **kwargs)
return inner
return login
elif status == 'permission':
def permission(func):
# func是login的inner函式地址
def inner(*args, **kwargs):
print('驗證許可權程式')
if login_dict.get('role') != 'admin':
return '當前許可權不能訪問該功能'
return func(*args, **kwargs)
return inner
return permission
@choice('login')
@choice('permission')
def withdraw(name, money):
print(f'使用者{name}正在取款{money}元')
withdraw('kylian', 20)
驗證登入程式
驗證許可權程式
使用者kylian正在取款20元