本文多參考《流暢的python》,在此基礎上增加了一些範例便於理解
姊妹篇
Python裝飾器範例講解(一),讓你簡單的會用
Python裝飾器範例講解(二),主要講了一個萬能公式(原理)
本文其實反而是最最基礎的部分,當然也回答了好幾個關鍵的問題,也有一些是重複的地方
範例
def func():
print('hello')
my_func = func # 此處不要寫成func()
my_func() # hello
func() # hello
這樣的使用比比皆是,比如在pytest中的一個應用
import pytest
xfail = pytest.mark.xfail # 就是這裡
@xfail # 這樣看就比較簡潔了
def test_hello():
assert 1
if __name__ == '__main__':
pytest.main(['-sv',__file__])
較為為典型的應用就是lambda,它是匿名的,但它同樣可以賦值給一個變數
my_add = lambda x,y:x+y
result = my_add(1,2)
print(result) # 3
範例
def double(x):
return x*2
def triple(x):
return x*3
def calc(funcion_name,x):
return funcion_name(x)
print(double(2)) # 4
print(triple(2)) # 6
print(calc(double,2)) # 4
print(calc(triple,2)) # 6
在上面的例子中你可以看到calc這個函數接收的第一個引數是函數名字
呼叫的時候你傳入的是double、triple這樣的名字
仔細觀察程式碼,calc的實現其實的本意就是把第一個引數當做函數名,第二個引數是第一個引數的引數。所以本質上你可以做任何事情,只要這個函數僅接收一個引數即可
print(calc(bin,10)) # 返回的是bin(10)的結果 0b1010
print(calc(max,(2,5,3))) # 執行的是max((2,5,3))
高階函數如map/filter/reduce/sort等,如果你接觸過,他們的引數不都是函數名嗎?
範例
def add(x,y):
return x+y
def func():
print('calling func')
return add
print(func()(1,2))
# 輸出如下
# calling func
# 3
# func() 就是 add , 跟你執行add(1,2)的效果是一樣的
你也可以這樣
new_add = func()
print(new_add(1,2))
# calling func
# 3
如果你看過前面的兩篇文章,到這裡就應該很熟悉了
除了函數是可呼叫的,還有很多(其實也沒多少)都是可呼叫物件
按照流暢的python的說法,有這麼多可呼叫物件
可呼叫物件 | 說明 |
---|---|
使用者定義的函數 | 使用 def 語句或 lambda 表示式建立 |
內建函數 | 使用 C 語言(CPython)實現的函數,如 len 或 time.strftime |
內建方法 | 使用 C 語言實現的方法,如 dict.get |
方法 | 在類的定義體中定義的函數 |
類 | 呼叫類時會執行類的 new 方法建立一個範例,然後執行 init 方法,初始化實 例,最後把範例返回給呼叫方。因為 Python 沒有 new 運運算元,所以呼叫類相當於呼叫函數。 |
類的範例 | 如果類定義了 call 方法,那麼它的範例可以作為函數呼叫。 |
生成器函數 | 使用 yield 關鍵字的函數或方法。呼叫生成器函數返回的是生成器物件。 |
對普通的初學者而言其實就是函數和類,類的呼叫分2級,Obj()這是範例化,同時呼叫new和init。
new和init魔術方法,後面會單獨開篇講解,單例跟這個是息息相關的。
生成器後面也考慮單獨開文章說一下。
範例程式碼(說明new和init)
class Person:
def __new__(cls, *args, **kwargs):
print('calling new')
cls.instance = super().__new__(cls)
return cls.instance
def __init__(self):
print('calling init')
wuxianfeng = Person()
範例輸出
calling new
calling init
但此時wuxianfeng這個Person類的範例並不是可呼叫的物件
如果你寫wuxianfeng(),會給你提示
TypeError: 'Person' object is not callable
你需要在Person類中定義一個__call__方法
class Person:
...
def __call__(self, *args, **kwargs):
print('callable')
此時再次執行wuxianfeng()就可以得到callable了
當然如果你執行Person()()結果也是這樣的
calling new
calling init
callable
python提供了一個內建的callable()函數來檢測物件是否可呼叫
print([callable(obj) for obj in (abs, str, 13)]) # [True, True, False]
雖然你可能已經學到裝飾器三了,但請你清空下你瞭解的裝飾器,倒也不是從0開始,帶點複習
範例程式碼
def decorate(function_name):
def inner():
print('calling inner')
function_name()
return inner
@decorate
def target():
print('calling target')
target()
輸出結果
calling inner
calling target
根據萬能公式,分析下執行過程
當你在執行target()的時候,由於target上有個裝飾器,實際上發生的事情是target = decorate(target)
前面的target 是新的(一個變數),後面的decorate(target)中的target是你之前定義的函數
decorate(target)就會去呼叫decorate函數傳入target引數,返回inner
卡....返回了inner,是你加了裝飾器的效果,至此都沒有執行函數
正是由於最終的target(),就是去呼叫了inner(),對應的語句是
print('calling inner')
function_name() # 你傳入的是target就是此處的function_name
關於被替換
def decorate(function_name):
def inner():
print('calling inner')
function_name()
print('這是inner的id:',id(inner))
return inner
@decorate
def target():
print('calling target')
print('這是target的id:',id(target))
範例輸出(你輸出的id跟我肯定不一樣,但2者應該是一致的,從這個角度也能看出來你執行的target不再是原來的target了)
這是inner的id: 1804087435904
這是target的id: 1804087435904
日常程式碼中還是有一些場景能看到一個函數被多個裝飾器裝飾的情況,比如pytest的allure
這個執行順序就是如你所想的那般,先裝飾的先執行
範例程式碼
def decorate1(function_name):
def inner1():
print('calling inner1')
function_name()
return inner1
def decorate2(function_name):
def inner2():
print('calling inner2')
function_name()
return inner2
@decorate1
@decorate2
def target():
print('calling target')
target()
# 輸出
# calling inner1
# calling inner2
# calling target
但這種情況下的萬能公式是怎樣的呢???你知道不~
萬能公式1
@decorate1
def target():
print('calling target')
# 等價於做了一件事
target = decorate1(target)
萬能公式2
@decorate1
@decorate2
def target():
print('calling target')
# 等價於做了2件事
# 第一件事,注意,就近原則
target = decorate2(target) # 前面的target是新的變數,後面的target是def的最初的、原始的函數
# 第二件事
target = decorate1(target) # 前面的target又是一個新的變數,後面的target是line8的前面的target
# 你也可以理解為做了一件事(合併上面2行)
target = decorate1(decorate2(target) ) # 最近的@的先呼叫
不信請看
def decorate1(function_name):
def inner1():
print('calling inner1')
function_name()
return inner1
def decorate2(function_name):
def inner2():
print('calling inner2')
function_name()
return inner2
def target():
print('calling target')
target = decorate2(decorate1(target) )
target()