1. 程式人生 > 實用技巧 >11.函式物件,函式巢狀,名稱空間與作用域,閉包函式,裝飾器

11.函式物件,函式巢狀,名稱空間與作用域,閉包函式,裝飾器

  • 引子
  • 函式物件

  • 函式巢狀

  • 名稱空間與作用域

  • 閉包函式

  • 裝飾器


  • 函式物件

在python中,函式是第一類物件,即函式可以當做資料傳遞,函式又稱第一等公民

本質:函式可以當變數用

  • 1.可以賦值
def func(): # func = 函式的記憶體地址
    print('from func')

f = func   # func 可以賦值給其它變數
print(f)
f()
  • 2.可以當做引數傳給另外一個函式
def func(): 
    print('from func')
    
def foo(x):
    print(x)
    x()      # 可以加()觸發內部程式碼的執行
foo(func)
  • 3.可以當做函式的返回值
def func(): 
    print('from func')
    
def foo(x):        # 把func丟進去
    return x       # 緊接著又扔出來func
 
res = foo(func)
print(res)
  • 4.可以當做容器型別的元素
def func():
    print('from func')
    
l = [func,]  # 列表呼叫
print(l)
l[0]()


# 容器型別的元素的應用
# 購物車程式

def withdraw():
    print("提款")
    
def tranfer():
    print("轉賬")
    
def check_balance():
    print("查詢餘額")
    
def save():
    print("存款")
  
func_dic = {
    "1":["提款",withdraw],
    "2":["轉賬",tranfer],
    "3":["查詢餘額",chenk_balance],
    "4":["存款",save]
}

while True:
    print("0 退出")
    for k,v in func_dic.items():
        print(k,v[0])
        
    choice = input("請輸入您要操作的編號>>>:").strip()
    if choice == '0':
        break
    if choice in func_dic:
        func_dic[choice][1]()
    else:
        print("輸入錯誤")
  • 函式巢狀

  • 函式的巢狀呼叫
def max2(x,y):   # 比較兩個值的大小
    if x > y:
        return x
    else:
        return y
    
def max4(a,b,c,d):  # 求四個值的最大值
    res1 = max2(a,b)
    res2 = max2(res1,c)
    res3 = max2(res2,d)
    return res3

print(max4(11,99,33,12))
# 把一個大功能拆解成幾個小功能,然後再把每個小功能分別去實現,最後在拼接回來
  • 函式的巢狀定義
def f1():
    x = 10           # 變數丟到函式裡面是一個封閉的效果
    def f2():
        print('from f2')
    print(x)         #1 這兩行程式碼是站在內部去看的可以看見
    print(f2)        #2
f1()
print(x)          #1 這兩行程式碼是站在外面去看的是看不見的
print(f2)         #2
# 函式層級帶來的變數訪問的限制,到底是因為什麼?定義在某一個位置的東西到底在哪才能看得見?
  • 名稱空間與作用域

  • namespaces名稱空間:存放名字的地方

    內建名稱空間:存放內建的名字
    生命週期:python直譯器啟動則產生,關閉則銷燬
    全域性名稱空間:存放的是頂級的名字
    生命週期:執行python檔案時則產生,python檔案執行完畢則銷燬
區域性名稱空間:存放的是函式內的名字
生命週期:呼叫函式則產生,函式呼叫完畢則銷燬
# 內建名稱空間:
>>> input
<built-in function input> # built-in 內建

# 全域性名稱空間:
x = 10    # 變數名X
y = 20    # 變數名y
f 1 > 0:
    z = 30
    
with open('a.txt', mode='wt') as f:
    a = 333
    
while True:
    c = 444
# 以上都是屬於頂級的

# 區域性名稱空間:
x = 10        # 全域性名稱空間
def foo(m):   # 全域性名稱空間
    m = 111   # 呼叫函式時,才會執行函式程式碼,名字m和n都存放於該函式的區域性名稱空間中
    n = 222   # 函式內
foo(111)      # 呼叫產生函式名稱空間
名稱空間的載入順序是:內建名稱空間--->全域性名稱空間--->區域性名稱空間
而查詢一個名字,必須從三個名稱空間之一找到
查詢順序為:區域性名稱空間--->全域性名稱空間--->內建名稱空間
核心:名字的訪問優先順序
基於當前所在的位置向外查詢
# 案例1
len = 10  # 全域性名稱空間

def func(): # 函式
    len = 20
    # print(len)  # 站在區域性查詢,找到的是20
    
func()      # 呼叫產生區域性名稱空間
print(len)  # 站在全域性找,全域性有找全域性,全域性沒有找內建

# 案例2
def f1():
	x = 555
	def f2():
		x = 666
		print(x)
	f2()
x = 444
f1()
  • 作用域(******)

    名稱空間與作用域的關係是在函式定義階段(掃描語法時)就確立的,
    與什麼時候呼叫以及呼叫位置無關
x = 111
def f1():
    print(x)
    
def f2():
    x = 222
    f1()
    
f2()


# 案例2
x = 111
def f1():
    print(x)
    x = 222
f1()
  • 作用域

全域性作用域:內建名稱空間 + 全域性名稱空間

特點:全域性存活(除非被刪除,否則在整個檔案執行過程中存活)

全域性有效(在任意位置都可以使用)

區域性作用域:區域性名稱空間

特點:臨時存活(即在函式呼叫時臨時生成,函式呼叫結束後就釋放)

區域性有效(只能在函式內使用)

  • global關鍵字

    ​ 在函式內,無論巢狀多少層,都可以檢視到全域性作用域的名字,若要在函式內修改全域性名稱空間中名字的值,當值為不可變型別時,則需要用到global關鍵字

# 案例1:
l = []
def func():
    l.append(1111)
   # l = [11,22,33]
func()               # 調func執行append
print(l)
# 當全域性變數是可變型別的時候,區域性是可以直接改的

# 案例2:
x = 111   # 全域性變數不可變型別
def func():   
    global x  # 宣告X是屬於全域性的,是可以改變的      
    x = 222   # 在區域性產生新的名字不影響全域性
func()
print(x)
# 要想在區域性修改一個全域性的不可變型別,可以用global修改
  • nonlocal關鍵字

    對於巢狀多層的函式,使用nonlocal關鍵字可以將名字宣告為來自外層巢狀函式定義的作用域(非全域性)

x = 111
def f1():
   # x = 222
    def f2():
       # global x   # 宣告變數名是來自全域性的
        nonlocal x  # 宣告變數名是來自於外層函式的,不是全域性
        x = 333   # 要想在這個位置用global把x=222改掉不可能
    f2()
    print(x)
    
f1()

  • 閉包函式

    閉:指的該函式是定義在函式內的函式

    包:指的就是該函式引用了一個外層函式作用域的名字(e作用域的名字)

def outter():
    x = 111      # 為wrapper函式的函式體程式碼傳參
    def wrapper():  
        print(x)
# 以後無論wrapper在哪調,它訪問的x以定義階段為準。
        
def outter():  # outter最外層函式
    x = 111
    def wrapper():  # 把wrapper閉到了outter裡面了,wrapper就只能在裡面用
        print(x)        
    return wrapper  # 千萬別加括號,要想把wrapper從裡面扔出來用就需要用return
f = outter()  # 把一個內部函式扔到全域性,這個內部函式就打破了層級限制,可以在任意位置呼叫
print(f)

def foo():
    x = 222
    f()   # 此時調f就相當於調wrapper
    
foo()
  • 為函式程式碼體傳參的方案

方案一:直接用引數傳
def wrapper(x):
    print(x)
    
wrapper(111)
wrapper(222)
wrapper(333)
方案二:閉包函式
def outter(x):       # 為了給wrapper函式傳參只能把它扔進去,wrapper本來是頂級的
    def wrapper():
        print(x)
    return wrapper   # 引數傳完就把wrapper用return扔出去

f1 = outter(111)    # 再想拿到wrapper,調outter拿到返回值,f1相當於當初的wrapper
f1()

f2 = outter(222)
f2()
  • 裝飾器

  • 1、什麼是裝飾器

    ​ 裝飾器就是一個用來為被裝飾物件新增新功能的工具

  • 2、為何要用裝飾器

    開放封閉原則:一旦軟體上線執行之後,應該對修改原始碼封閉,對擴充套件功能開放

原則:
1、不修改函式內的原始碼
2、不修改函式的呼叫方式
裝飾器就是在遵循原則1和2的前提下,為被裝飾物件新增上新功能

  • 3、如何實現裝飾器

​ 函式裝飾器分為:無參裝飾器和有參裝飾兩種,二者的實現原理一樣,都是

“函式巢狀+閉包+函式物件”的組合使用的產物

# 需求:為函式index新增統計執行時間的功能
import time    # 匯入時間模組

def index():
    time.sleep(1)
    print('from index')

index()

方案一:
import time

def index():
    start = time.time()   # 開始時間
    time.sleep(1)
    print('from index')
    stop = time.time()
    print('run time is %s' %(stop - start))

index()

方案二:
import time

def index():
    time.sleep(1)
    print('from index')

start = time.time()
index()
stop = time.time()
print('run time is %s' %(stop - start))

方案三:
import time

def index():
    time.sleep(1)
    print('from index')

def wrapper():
    start = time.time()
    index()
    stop = time.time()
    print('run time is %s' %(stop - start))

wrapper()
wrapper()


# 方案四:
import time

def index():
    time.sleep(1)
    print('from index')

def wrapper(func):
    start = time.time()
    func()
    stop = time.time()
    print('run time is %s' %(stop - start))

wrapper(index)

方案五:====》閉包函式
import time

def index():
    time.sleep(1)
    print('from index')

def wrapper():
    start = time.time()
    func()
    stop = time.time()
    print('run time is %s' %(stop - start))