函式高階玩法

清风拂山岗(小高同学)發表於2024-05-24

分享幾個比較重要的知識點,web框架中大量的運用了,所以我給摘出來啦!

  • 函式的巢狀
  • 閉包
  • 裝飾器

1.函式巢狀

python中以函式為作用域,在作用域中定義的相關資料只能被當前作用域或子域使用

name = "隔壁老王"
print(name )

def func():
    print(name )

func()

1.函式在作用域中

實際上,函式也是定義在作用域中的資料,在執行函式時,同樣遵循:優先在自己的作用域中尋找,沒有則在上一級尋找

# 1. 在全域性作用域定義了函式func
def func():
    print("你好")

# 2.在全域性作用域找到func函式並執行。
func()

# 3.在全域性作用域中定義了send函式
def send():
    print("開始")
    # 優先在當前函式作用域找func函式,沒有則向上級作用域中尋找。
    func()
    print("結束")

# 4.在全域性作用域執行send函式
    send()

上兩個例項,更容易理解

def func():
    print("你好")
    
func()

def send():
    print("開始")
    func()
    print("結束")
    
send()

def func():
    print(666)
    
func()

def func():
    print("你好")
    
func()           # 你好

def send():
    print("開始")
    func()
    print("結束")

def func():      # 會覆蓋原來的func()函式,因為這個函式名和剛開始的函式名重名了
    print(666)

func()             # 666
send()
# 開始
# 666
# 結束
 

1.2函式定義的位置

上述示例中的函式定義在全域性作用域中,其實函式也可以定義在區域性在作用域中,這樣函式被區域性作用域和其子作用域中呼叫(函式的巢狀)。

def func():
    print("111")
    
def handler():
    print("222")
    def inner():
        print("333")
	inner()
    func()
    print("444")

handler()

只要理解資料定義時所存在的作用域,並根據從上到下程式碼執行過程進行分析,再怎麼巢狀都可以搞定。

現在的你可能有疑問:為什麼要這麼巢狀定義?把函式都定義在全域性不好嗎?

其實,大多數情況下我們都會將函式定義在全域性,不會巢狀著定義函式。不過,當我們定義一個函式去實現某功能,想要將內部功能拆分成N個函式,又擔心這個N個函式放在全域性會與其他函式名衝突時(尤其多人協同開發)可以選擇使用函式的巢狀

def f1():
    pass

def f2():
    pass

def func():
	f1()
    f2()
def func():
    def f1():
        pass

    def f2():
        pass
	f1()
    f2()

1.3 巢狀引發的作用域問題

基於記憶體和執行過程分析作用域

name = "eric"

def run():
    name = "job"
    def inner():
        print(name)
	inner()
    
run()

執行函式,內部會生成一個呼叫棧,理解成會建立一個作用域

name = "eric"

def run():
    name = "job"
    def inner():
        print(name)
	return inner
    
v1 = run()
v1()

v2 = run()
v2()

三句話搞定作用域:

  • 優先在自己的作用域找,自己沒有就去上級作用域。
  • 在作用域中尋找值時,要確保此次此刻值是什麼。
  • 分析函式的執行,並確定函式作用域鏈。(函式巢狀)

2.閉包

閉包 :基於函式的巢狀,可以將資料封裝到一個包中,以後再去呼叫

應用場景:

  • 1.封裝到一個包中,供多個函式使用
  • 2.封裝到一個個獨立的包中,供自己呼叫

閉包應用場景1:封裝資料防止汙染全域性。(有的函式不需要,只給需要的函式提供)

name = "eric"

def f1():
    print(name, age)

def f2():
	print(name, age)

def f3():
	print(name, age)
    
def f4():
    pass
def func(age):
    name = "eric"

    def f1():
        print(name, age)

    def f2():
        print(name, age)

    def f3():
        print(name, age)

    f1()
    f2()
    f3()

func(123)

閉包應用場景2:封裝資料封到一個包裡,使用時在取。

def task(arg):
    def inner():
        print(arg)
    return inner

v1 = task(11)  #建立一塊自己的作用域
v2 = task(22)
v3 = task(33)
v1()
v2()
v3()

不多說,直接上案例

""" 基於多執行緒去下載影片 """
from concurrent.futures.thread import ThreadPoolExecutor

import requests


def download_video(url):
    res = requests.get(
        url=url,
        headers={
            "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 FS"
        }
    )
    return res.content


def outer(file_name):
    def write_file(response):  # 上面的res.content會自動傳遞給response
        content = response.result()  # 要拿取到裡面的值必須要【.result】
        with open(file_name, mode='wb') as file_object: 
            file_object.write(content)

    return write_file


POOL = ThreadPoolExecutor(10)  # 建立執行緒池
	
video_dict = [
    ("東北F4模仿秀.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0300f570000bvbmace0gvch7lo53oog"),
    ("卡特扣籃.mp4", "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f3e0000bv52fpn5t6p007e34q1g"),
]
for item in video_dict:
    future = POOL.submit(download_video, url=item[1]) #  執行download_video方法,並傳遞引數url
    # outer(item[0]) 這裡涉及到閉包的知識點 
    future.add_done_callback(outer(item[0]))  # 上面執行完之後立刻執行括號中的方法【write_file函式】

POOL.shutdown()

3.裝飾器

實現原理:基於@語法和閉包,將原函式封裝到內部,並將函式賦值成新的函式【內層函式】,執行函式時在內層函式中執行閉包中封裝的原函式

實現效果:可以在不改變原函式內部程式碼 和 呼叫方式的前提下,實現在函式執行和執行擴充套件功能。

適用場景:多個函式系統統一·在 執行前後自定義一些功能。

先提個小需求,在不改變原始碼之前,實現一個函式執行之前和執行之後分別輸入"before"和"after"

對於剛剛入門的我,會這麼寫

def func():
    print("before")
    print("我是func函式")
    value = [11, 22, 33]
    print("before")
    return value

result = func()

對於我來說,我會這樣做

def outer(origin):
    def inner():
        print("before")
        res = origin()
        print("after")
    return inner

def func():
    print("我是func函式")
    value = [11, 22, 33, 44]
    return value

res = outer(func)
res()

如果涉及多個地方要同時在操作前和操作後進行分別輸出

"""
由於程式碼是從上往下執行的,那麼裝飾器那裡 eg: func1 = outer(func1) 
此時,func1已經是inner函式,那麼此時func1()是執行的inner函式,

"""
def outer(origin):
    def inner():
        print("before")
        res = origin()
        print("after")
        return res

    return inner


@outer  # func1 = outer(func1)
def func1():  
    print("我是func1函式")
    value = [11, 22, 33]
    return value


@outer  # func2 = outer(func2)
def func2():
    print("我是func2函式")
    value = [11, 22, 33]
    return value



@outer # func3 = outer(func3)
def func3():
    print("我是func3函式")
    value = [11, 22, 33]
    return value

func1()
func2()
func3()

下面,是我對裝飾器進行了最佳化【函式帶引數】

def outer(origin):
    def inner(*args,**kwargs):
        print("before")
        res = origin(*args,**kwargs)
        print("after")
        return res

    return inner


@outer  # func1 = outer(func1)
def func1(a1):
    print("我是func1函式")
    value = [11, 22, 33]
    return value


@outer  # func2 = outer(func2)
def func2(a2, a3):
    print("我是func2函式")
    value = [11, 22, 33]
    return value


@outer  # func3 = outer(func3)
def func3(a4):
    print("我是func3函式")
    value = [11, 22, 33]
    return value


func1(1)
func2(2, a3=4)
func3(5)

會發現裝飾器實際上就是將原函式更改為其他的函式,然後再此函式中再去呼叫原函式。

好了,裝飾器示例


def outer(origin):
    def inner(*args, **kwargs):
        # 執行前
        res = origin(*args, **kwargs)
        # 執行後

    return inner

@outer
def func():
    pass

func()

但是,上面的裝飾器只能夠解決百分之99的問題,下面介紹一個模組functools
加與不加的區別在於原函式.__name原函式.__doc

#  先認識不加的

def outer(origin):
    def inner(*args, **kwargs):
        """我是小淘氣"""
        # 執行前
        res = origin(*args, **kwargs)
        return res
        # 執行後

    return inner


@outer
def func():
    """我是圖圖"""
    value = [11, 22]
    return value


print(func.__name__)  # 目前func函式 -> inner函式 所以會列印inner函式的名稱【inner】  
print(func.__doc__)   # 那麼這麼會列印inner函式的註釋    【我是小淘氣】
# 再認識加的
import functools

def outer(origin):
    @functools.wraps(origin)
    def inner(*args, **kwargs):
        """我是小淘氣"""
        # 執行前
        res = origin(*args, **kwargs)
        return res
        # 執行後

    return inner


@outer
def func():
    """我是圖圖"""
    value = [11, 22]
    return value


print(func.__name__)  # 不管目前func是什麼函式,我就列印原來函式的名稱【func】  
print(func.__doc__)   # 那麼這麼會列印func函式的註釋    【我是圖圖】

因為其實,一般情況下不用functools也可以實現裝飾器的基本功能,但在專案開發時,不加functools會出錯(內部會讀取__name__,且__name__重名的話就報錯)

好了,今天就寫到這裡叭,重新認識一下函式的高階玩法!

相關文章