函數(shù)
什么是函數(shù)? 將具有某種功能的代碼放到一起, 構(gòu)成一個函數(shù).
為什么說函數(shù)? 因為需要研究一個問題, 函數(shù)可以嵌套調(diào)用, 那么可不可以嵌套定義?
函數(shù)的嵌套調(diào)用
def func1():
print("func1")
def func2():
# 嵌套調(diào)用
func1()
func1()
func2()
# 運行結(jié)果
# func1
# func1
函數(shù)能使用函數(shù)名調(diào)用, 那么函數(shù)名是什么? 標(biāo)識符, 標(biāo)識符里包含變量 / 函數(shù)名, 那么函數(shù)能不能通過變量傳遞 / 調(diào)用嗎?
def func1():
print("func1")
def func2():
# 函數(shù)通過變量傳遞
f2 = func1
print("f2 type:%s" % type(f2))
# 通過變量調(diào)用函數(shù)
f2()
func1()
func2()
# 運行結(jié)果
# func1
# f2 type:<class 'function'>
# func1
函數(shù)可以通過變量傳遞 / 調(diào)用,那么回到最初的問題, 函數(shù)能嵌套調(diào)用, 函數(shù)能嵌套定義嗎?
閉包
def func2():
# 定義函數(shù)func1
def func1():
print("func1")
func2()
# 運行結(jié)果
通過運行結(jié)果可以得出, 可以嵌套定義 ( 沒報錯:) ) , 那么為什么沒有結(jié)果?
函數(shù)能夠傳遞,函數(shù)在定義的時候不會執(zhí)行, 那么可以明確一點func2 被調(diào)用,所以func2 被執(zhí)行了.
func2 被執(zhí)行 ,那么func1 定義在func2 函數(shù)體內(nèi), 所以func1 被定義了, 但是func1 沒有被調(diào)用.那么能不能通過函數(shù)傳遞調(diào)用func1?
def func2():
# 定義函數(shù)func1
def func1():
print("func1")
return func1
f = func2() # 得到func1 的引用
print(type(f)) # 查看f 的類型
f() # 調(diào)用函數(shù)
# 運行結(jié)果
# <class 'function'>
# func1
這種形式叫閉包[1], 在函數(shù)內(nèi)部定義另一個函數(shù), 并且外部函數(shù)func2 返回內(nèi)部函數(shù)func1 的引用.
雖然這是一個閉包, 但是沒人會這么用, 用兩個函數(shù)去做一個函數(shù)可以完成的事, 而且還這么麻煩.
相信大家都畫過數(shù)學(xué)中的函數(shù)圖像, 畫圖像需要確定坐標(biāo), 今天就用程序確定 x = 某值時 , y的值 ( y= x的平方 )
def func2(x):
# 內(nèi)部函數(shù)func1 要使用一個X 的值, 需要從外部傳入
# 為什么要從外部傳入?
def func1():
print(x ** 2)
return func1
f = func2(2) # 得到func1 的引用
f() # 調(diào)用函數(shù)
# 運行結(jié)果
# 4
一定要從外部傳入嗎?不一定,就上面這個例子只是簡單演示閉包的用法, 而且上面的例子用一個函數(shù)也可以完成, 那么閉包大多數(shù)的應(yīng)該用在哪? 裝飾器或者叫語法糖.
函數(shù)裝飾器
只要你寫過類方法 / 靜態(tài)方法, 那么你就用過裝飾器.下面來看看裝飾器
print("start") # 開始Debug
# 函數(shù)定義 會創(chuàng)建 不會執(zhí)行
def func2(x):
print("func2 被調(diào)用")
print("x type = %s" % type(x))
def func1():
print("func1 被調(diào)用")
x()
return func1
@func2 # 程序暫停,執(zhí)行 func3 = func2(func3)
def func3():
print("func3 被調(diào)用")
func3() # 調(diào)用函數(shù)
# 運行結(jié)果
# start
# func2 被調(diào)用
# x type = <class 'function'>
# func1 被調(diào)用
# func3 被調(diào)用
裝飾器的運行 debug
裝飾器運行 圖解
裝飾器運行 文字描述
- 程序執(zhí)行到 line:5, func2 被定義 ,
- 程序執(zhí)行到 line:16是一個裝飾器,不執(zhí)行,
- 運行(檢測) line:17 定義func3, 同時檢測到是一個函數(shù),返回 line:16 執(zhí)行func2
- 如果line:17 不是函數(shù),繼續(xù)向下執(zhí)行檢測,直到遇到函數(shù), 再次返回
- func2 被執(zhí)行 ( 因為@func2 ==> func3 = func2(func3) ),
- x = 原func3 ,既 x 指向原func3 的函數(shù)體;
- print("func2 被調(diào)用") # func2 被調(diào)用
print("x type = %s" % type(x)) # x type = <class 'function'> - func1 被定義
- return func1, 既 func3 = func2(func3) = func1,裝飾完成func3 被指向func1
- 程序執(zhí)行到 line:21,因為func3 在上面被裝飾過,所以現(xiàn)在的func3( 裝飾完的 ) 指向func1
- func3() ==> func1(), 既 line:10 被執(zhí)行, print("func1 被調(diào)用")
- line:11 因為x = 原func3 ,所以 x() 原func3 的方法體被執(zhí)行, print("func3 被調(diào)用")
- 函數(shù)調(diào)用完成, 返回被調(diào)用處 ,所以 print("func3 被調(diào)用") ( line:18) 完成后 返回 x() 處(line:11) ,
- x() 執(zhí)行完(line:11) , 返回func3() 處 (line:21), 程序執(zhí)行完成
特殊記憶方法
根據(jù)上面的裝飾器運行過程, 我們嘗試推出一個方法, 便于記憶和解決裝飾器的過程.
使用特殊記憶方法
print("start") # 開始Debug
# 函數(shù)定義 不會執(zhí)行
def func2(x):
print("func2 被調(diào)用")
print("x type = %s" % type(x))
def func1():
print("func1 被調(diào)用")
x()
return func1
def func4(x):
print("func4 被調(diào)用")
print("x type = %s" % type(x))
def func5():
print("func5 被調(diào)用")
x()
return func5
@func4
@func2
def func3():
print("func3 被調(diào)用")
func3() # 調(diào)用函數(shù)
# 運行結(jié)果
# start
# func2 被調(diào)用
# x type = <class 'function'>
# func4 被調(diào)用
# x type = <class 'function'>
# func5 被調(diào)用
# func1 被調(diào)用
# func3 被調(diào)用
裝飾過程:
- func3 是一個函數(shù)的定義壓棧
- @func2 得到func3 函數(shù)的引用 壓棧
- func2 被調(diào)用 func1 壓棧 ( func2 被調(diào)用 x type = <class 'function'>)
- @func4 的到func1 函數(shù)的引用 壓棧
- func4 被調(diào)用 func5 壓棧 ( func4 被調(diào)用 x type = <class 'function'>)
調(diào)用過程:
- 裝飾后調(diào)用func3 ,func3 指向func5 ,func5被調(diào)用
- func5 出棧, func5 被調(diào)用
- x(),x 指向func1 func1 被調(diào)用
- func1 被調(diào)用 func4 出棧 ( 裝飾過程,執(zhí)行過,出棧無效果, 只有func4出棧, func1 在棧頂次可以調(diào)用) ,func1 被調(diào)用
- func1 出棧, func1 被調(diào)用
- x(),x 指向func3 func3 被調(diào)用
通過上面的我們大概了解裝飾過程,以及裝飾后的調(diào)用過程, 那么如果是有參有返回值的函數(shù)怎么裝飾呢?
首先我們知道了 閉包外部函數(shù)的形參存儲著原函數(shù)( 被裝飾函數(shù)) , 閉包內(nèi)部調(diào)用了原函數(shù)( 被裝飾函數(shù)),因為裝飾器的調(diào)用是由解釋器完成的,既@閉包外部函數(shù) , 外部函數(shù)的參數(shù)個數(shù)我們無法改變, 那么只能改變閉包內(nèi)部函數(shù)給 原函數(shù)( 被裝飾函數(shù))傳參.
如圖, func3 需要有參數(shù),@func2 的參數(shù)個數(shù)無法改變, 我們嘗試改變func1 的參數(shù),同時x 最終指向原函數(shù),所以x 必須和func3 的參數(shù)一致.
print("start") # 開始Debug
# 函數(shù)定義 不會執(zhí)行
def func2(x):
def func1(*args, **kwargs):
print("func1 被調(diào)用")
print("args %s " % args.__str__())
print("kwargs %s " % kwargs.__str__())
x(*args, **kwargs)
return func1
@func2
def func3(*args, **kwargs):
print("func3 被調(diào)用")
print("args %s " % args.__str__())
print("kwargs %s " % kwargs.__str__())
func3("Dragon", "Fang", Dragon=18, Fang="nan") # 調(diào)用函數(shù)
# 運行結(jié)果
# start
# func1 被調(diào)用
# args ('Dragon', 'Fang')
# kwargs {'Dragon': 18, 'Fang': 'nan'}
# func3 被調(diào)用
# args ('Dragon', 'Fang')
# kwargs {'Dragon': 18, 'Fang': 'nan'}
通過改變閉包內(nèi)部函數(shù)可以給帶參被裝飾函數(shù)傳參, 那么返回值怎么辦?
- 調(diào)用函數(shù)都會回到被調(diào)用處,裝飾后func3() 調(diào)用,既func1 被調(diào)用
- func1 執(zhí)行, x() 被調(diào)用==>原func3 被調(diào)用, 原func3執(zhí)行完成有返回值,返回到 x() 處,
- x() 得到值, 如果不將值返回, 那么func1 執(zhí)行完默認(rèn)返回None,既 裝飾后func3() 處的到None 值,所以 x() 處需要將得到的值返回.
print("start") # 開始Debug
# 函數(shù)定義 不會執(zhí)行
def func2(x):
def func1(*args, **kwargs):
print("func1 被調(diào)用")
return x(*args, **kwargs)
return func1
@func2
def func3(*args, **kwargs):
print("func3 被調(diào)用")
return "DragonFang"
print(func3("Dragon", "Fang", Dragon=18, Fang="nan"))
# 運行結(jié)果
# start
# func1 被調(diào)用
# func3 被調(diào)用
# DragonFang
上面的裝飾器可以裝飾以下四種函數(shù)
- 無參數(shù),無返回值
- 無參數(shù),有返回值
- 有參數(shù),無返回值
- 有參數(shù),有返回值
裝飾器應(yīng)用場景
裝飾器可以在不改變原函數(shù)的基礎(chǔ)上,添加新的功能 ( 符合開閉原則[2]), 例如計算函數(shù)運行耗時, 在優(yōu)化時常用.
import time
def func(func_args):
def in_func(*args, **kwargs):
# 在函數(shù)開始運行前,得到當(dāng)前時間
start_time = time.time()
result_value = func_args(*args, **kwargs)
# 在函數(shù)運行結(jié)束后,獲取當(dāng)前時間,作差 ==> 得到函數(shù)的運行時間
print(time.time() - start_time)
return result_value
return in_func
@func
def test():
time.sleep(2)
test()
# 運行結(jié)果
# 2.00081205368042
函數(shù)裝飾器-給裝飾器傳參
有個問題,函數(shù)內(nèi)部嵌套函數(shù)的定義是兩層, 如果是三層呢?或者更多層呢?先看看三層,三層往上不討論,層級太深不是一件好事.
def out_test():
def test(x):
def in_test(*args, **kwargs):
print("func1 被調(diào)用")
return x(*args, **kwargs)
return in_test
return test
如上, 三層函數(shù), 首先這是一個函數(shù), 可用通過out_test() 得到test(x) 函數(shù)的引用, 然后可以通過test(x) 的到in_test (*args, **kwargs)函數(shù)的引用.
那么如果使用這么一個函數(shù)作為裝飾器,會是什么效果?
報錯?缺少參數(shù)?添加參數(shù),添加不定長參數(shù),一步到位 :)
print("start") # 開始Debug
def out_test(*args, **kwargs):
print("out_test 被調(diào)用")
def test(x):
print("test 被調(diào)用")
def in_test(*args, **kwargs):
print("in_test 被調(diào)用")
return x(*args, **kwargs)
return in_test
return test
@out_test("Dragon", "Fang", Dragon=18, Fang="nan")
def func3(*args, **kwargs):
print("func3 被調(diào)用")
return "DragonFang"
print(func3("Dragon", "Fang", Dragon=18, Fang="nan"))
# 運行結(jié)果
# start
# out_test 被調(diào)用
# test 被調(diào)用
# in_test 被調(diào)用
# func3 被調(diào)用
# DragonFang
最外層被調(diào)用,然后內(nèi)層被調(diào)用, out_test 被調(diào)用, 然后 test 被調(diào)用, 最后 in_test 被調(diào)用, 那么他們分別作了什么事?
def out_test(*args, **kwargs):
print("out_test 被調(diào)用")
print(args)
print(kwargs)
def test(x):
print("test 被調(diào)用")
print(x)
def in_test(*args, **kwargs):
print("in_test 被調(diào)用")
print(args)
print(kwargs)
return x(*args, **kwargs)
return in_test
return test
@out_test("test", Dragon=20)
def func3(*args, **kwargs):
print("func3 被調(diào)用")
return "DragonFang"
print(func3("Dragon", "Fang", Dragon=18, Fang="nan"))
# 運行結(jié)果
# out_test 被調(diào)用
# ('test',)
# {'Dragon': 20}
# test 被調(diào)用
# <function func3 at 0x000001BAA0C48A60>
# in_test 被調(diào)用
# ('Dragon', 'Fang')
# {'Dragon': 18, 'Fang': 'nan'}
# func3 被調(diào)用
# DragonFang
out_test 被調(diào)用打印了值, 然后test 被調(diào)用 , func3 被打印, 因此我們可以推測出@out_test("test", Dragon=20) ==> out_test("test", Dragon=20) 然后 使用 out_test 的返回值對func3 進行裝飾
那么可以通過三層函數(shù)的形式給裝飾器傳參.
類裝飾器
@classmethod / @staticmethod 都是類裝飾器
# classmethod 源碼
class classmethod(object):
"""
classmethod(function) -> method
Convert a function to be a class method.
A class method receives the class as implicit first argument,
just like an instance method receives the instance.
To declare a class method, use this idiom:
class C:
@classmethod
def f(cls, arg1, arg2, ...):
...
It can be called either on the class (e.g. C.f()) or on an instance
(e.g. C().f()). The instance is ignored except for its class.
If a class method is called for a derived class, the derived class
object is passed as the implied first argument.
Class methods are different than C++ or Java static methods.
If you want those, see the staticmethod builtin.
"""
def __get__(self, *args, **kwargs): # real signature unknown
""" Return an attribute of instance, which is of type owner. """
pass
def __init__(self, function): # real signature unknown; restored from __doc__
pass
@staticmethod # known case of __new__
def __new__(*args, **kwargs): # real signature unknown
""" Create and return a new object. See help(type) for accurate signature. """
pass
__func__ = property(lambda self: object(), lambda self, v: None, lambda self: None) # default
__isabstractmethod__ = property(lambda self: object(), lambda self, v: None, lambda self: None) # default
__dict__ = None # (!) real value is ''
仿照@classmethod ,制作一個類裝飾器.
報錯?init魔法方法缺少參數(shù)?給參數(shù)
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print(args)
print(kwargs)
@ClassMethod
def func3(*args, **kwargs):
print("func3 被調(diào)用")
return "DragonFang"
# 運行結(jié)果
# (<function func3 at 0x000001CC350C2E18>,)
# {}
@ClassMethod 調(diào)用了init 魔法方法? 那么應(yīng)該有一個ClassMethod 類的對象被創(chuàng)建,
既@ClassMethod ==> ClassMethod() 得到對象, 使用對象裝飾func3.
驗證,調(diào)用裝飾后的func3
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print(args)
print(kwargs)
@ClassMethod
def func3(*args, **kwargs):
print("func3 被調(diào)用")
return "DragonFang"
func3()
# 運行結(jié)果
# Traceback (most recent call last):
# File "E:/workspace/pycharm/pycharm/model.py", line 12, in <module>
# func3()
# (<function func3 at 0x00000276789F2E18>,)
# TypeError: 'ClassMethod' object is not callable
# {}
報錯? 'ClassMethod' object is not callable ,對象不可調(diào)用, 證明有一對象被創(chuàng)建, 而且對象需要有一個回調(diào)方法. 魔法方法__call__ [3] 可以使一個實例對象變?yōu)橐粋€可調(diào)用對象.
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print("__init__")
print(args)
print(kwargs)
def __call__(self, *args, **kwargs):
print("__call__")
print(args)
print(kwargs)
@ClassMethod
def func3(*args, **kwargs):
print("func3 被調(diào)用")
return "DragonFang"
func3()
# 運行結(jié)果
# __init__
# (<function func3 at 0x00000204124F2E18>,)
# {}
# __call__
# ()
# {}
成功運行,證明我們的類裝飾器沒問題, 那么函數(shù)裝飾器能傳參, 類裝飾器怎么傳參? call 魔法方法有兩個空值, 是不是可以利用一下?
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print("__init__")
print(args)
print(kwargs)
def __call__(self, *args, **kwargs):
print("__call__")
print(args)
print(kwargs)
@ClassMethod("Dragon", "Fang", Dragon=18, Fang="nan")
def func3(*args, **kwargs):
print("func3 被調(diào)用")
return "DragonFang"
func3()
# 運行結(jié)果
# Traceback (most recent call last):
# __init__
# File "E:/workspace/pycharm/pycharm/model.py", line 19, in <module>
# ('Dragon', 'Fang')
# func3()
# {'Dragon': 18, 'Fang': 'nan'}
# TypeError: 'NoneType' object is not callable
# __call__
# (<function func3 at 0x000001FCCACB2E18>,)
# {}
call魔法方法被執(zhí)行, 輸出 (<function func3 at 0x000001FCCACB2E18>,) 和 {} ,然后
'NoneType' object is not callable ,空對象不能調(diào)用? 我們知道函數(shù) 或者方法 默認(rèn)是有默認(rèn)返回值的, 默認(rèn)值是None ,也就是說 call 魔法方法返回了一個None 給調(diào)用處,既@ClassMethod("Dragon", "Fang", Dragon=18, Fang="nan") 處, 使用 @None 裝飾func3 是不對的, 那么我們返回一個函數(shù), 既使用函數(shù)裝飾器裝飾func3
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print("__init__")
self.args = args
self.kwargs = kwargs
def __call__(self, func):
print("__call__")
self.func = func
return self.test
def test(self):
print("test")
self.func(*self.args, **self.kwargs)
@ClassMethod("Dragon", "Fang", Dragon=18, Fang="nan")
def func3(*args, **kwargs):
print("func3 被調(diào)用")
print(args)
print(kwargs)
func3()
# 運行結(jié)果
# __init__
# __call__
# test
# func3 被調(diào)用
# ('Dragon', 'Fang')
# {'Dragon': 18, 'Fang': 'nan'}
運行成功,給類裝飾器傳參完成,有幾點需要注意
- 參數(shù),參數(shù)是傳給init 魔法方法的,需要使用屬性接收
- call 魔法方法返回的是一個函數(shù)的引用
- test方法中,self.func 是原函數(shù), 通過屬性傳不定長參數(shù)需要拆包
類裝飾器傳參-方式2
上面是在初始化時傳參,那能不能將通過方法傳參?
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print("__init__")
self.args = args
self.kwargs = kwargs
def __call__(self, func):
print("__call__")
self.func = func
return self.test
@classmethod
def test(cls, *args, **kwargs):
cls.args = args
cls.kwargs = kwargs
@ClassMethod.test("Dragon", "Fang", Dragon=18, Fang="nan")
def func3(*args, **kwargs):
print("func3 被調(diào)用")
print(args)
print(kwargs)
func3()
# 運行結(jié)果
# Traceback (most recent call last):
# test
# File "E:/workspace/pycharm/pycharm/model.py", line 18, in <module>
# @ClassMethod.test("Dragon", "Fang", Dragon=18, Fang="nan")
# TypeError: 'NoneType' object is not callable
test 被打印,說明test 類方法被調(diào)用, 'NoneType' object is not callable 又是這錯誤! 給test類方法一個返回值
class ClassMethod(object):
def __init__(self, *args, **kwargs):
print("__init__")
print(args)
print(kwargs)
def __call__(self):
print("__call__")
@classmethod
def test(cls, *args, **kwargs):
cls.args = args
cls.kwargs = kwargs
return cls
@ClassMethod.test("Dragon", "Fang", Dragon=18, Fang="nan")
def func3(*args, **kwargs):
print("func3 被調(diào)用")
print(args)
print(kwargs)
func3()
# 運行結(jié)果
# __init__
# (<function func3 at 0x0000023DD1962E18>,)
# {}
# __call__
通過以上實驗可以推斷@ClassMethod.test("Dragon", "Fang", Dragon=18, Fang="nan")
==> 先調(diào)用test 類方法, 然后 調(diào)用 init魔法方法初始化對象, 最后通過實例對象裝飾func3
那么按照這個順序完善一下ClassMethod 類
class ClassMethod(object):
def __init__(self, func):
print("__init__")
self.func = func
def __call__(self):
return self.func(*ClassMethod.args, **ClassMethod.kwargs)
@classmethod
def test(cls, *args, **kwargs):
cls.args = args
cls.kwargs = kwargs
return cls
@ClassMethod.test("Dragon", "Fang", Dragon=18, Fang="nan")
def func3(*args, **kwargs):
print("func3 被調(diào)用")
print(args)
print(kwargs)
func3()
# 運行結(jié)果
# __init__
# func3 被調(diào)用
# ('Dragon', 'Fang')
# {'Dragon': 18, 'Fang': 'nan'}
perfect 完美
總結(jié):
- 閉包:函數(shù)內(nèi)部嵌套函數(shù)的定義,構(gòu)成閉包
- 閉包的作用:大部分用在裝飾器
- 裝飾器:@函數(shù)名 / @類名 的形式叫做裝飾器或者語法糖
- 裝飾器的作用:在不改變原函數(shù)的基礎(chǔ)上添加功能
- 裝飾前的函數(shù)原函數(shù),被閉包外部函數(shù)的形參保存
- 裝飾后的函數(shù),實際上是閉包的內(nèi)部函數(shù),
- 通用裝飾器格式:
# 不需要給裝飾器傳參
def func(func_args):
def in_func(*args, **kwargs):
print("添加功能")
return func_args(*args, **kwargs)
return in_func
# 需要給裝飾器傳參
def out_func(*out_args, **out_kwargs):
def func(func_args):
def in_func(*args, **kwargs):
print("添加功能")
return func_args(*args, **kwargs)
return in_func
return func
- 裝飾器特殊記憶方式,出棧入棧,入棧: 裝飾過程,出棧:調(diào)用過程
- 類裝飾器,類需要包含 init 魔法方法 和 call 魔法方法 構(gòu)成基本的類裝飾器
- 類裝飾器傳參,除了init 魔法方法 和 call 魔法方法 之外需要定義另外的方法
- @ClassMethod("Dragon", "Fang", Dragon=18, Fang="nan"), 這種傳參方式需要定義另外的方法, 輔助調(diào)用原函數(shù)
- @ClassMethod.test("Dragon", "Fang", Dragon=18, Fang="nan"), 這種傳參方式需要定義另外的方式, 輔助保存參數(shù)
到此結(jié)?DragonFangQy 2018.5.26