1. 程式人生 > >十、閉包函數、函數對象、裝飾器

十、閉包函數、函數對象、裝飾器

pri for urlopen 網信 imm python res 單例 函數返回

函數對象:

  函數是第一類對象

第一類對象:

  指可以在執行期創造並作為參數傳遞給其他函數或存入一個變量的實體     

第一類對象所特有的特性為:

  • 可以當容器被存入變量或其他結構
  • 可以被作為參數傳遞給其他函數
  • 可以被作為函數的返回值
  • 可以在執行期創造,而無需完全在設計期全部寫出
  • 即使沒有被系結至某一名稱,也可以存在

函數、類、模塊等所有對象都是第一類的


閉包函數:

  函數內定義的函數為內部函數

  內部函數包含對外部作用域而非全局作用域的引用

  定義閉包函數的基本形式:

def 外部函數名():
    內部函數需要的變量
    def 內部函數():
        引用外部變量
    return 內部函數

  舉個栗子

def f1():
    x=1
    def f2():
        print(x)
    return f2
f = f1()
print(f)        # <function f1.<locals>.f2 at 0x00000189C2226158>
f()
print(f.c.__closure__[0].cell_contents)  查看閉包元素   閉包函數都有 __closure__ 方法
這裏內部函數f2打印x,f2中沒有變量x,去上一層找,找到f1中的x,然後打印 這裏的 f 就是閉包函數,包含了對f1中x的引用
特點:返回的函數對象,不僅僅是一個函數對象,在該函數外還包裹了一層作用域,這使該函數無論在何處調用,優先使用自己外層包裹的作用域,永遠攜帶著一種狀態
   拿到函數的內存地址,什麽時候用就什麽時候執行,惰性計算

閉包函數和作用域:
money = 666
def foo(name):
    print(‘%s have %s money‘ %(name,money))

def foo1():
    money = 999
    foo(‘shuai‘)
foo1()                                    #  shuai have 666 money

這裏打印不會是999,函數在定義時就已經確定了作用域,所以當doo函數內沒有money時,會去找全局的money

  閉包函數,讓我一直有著一筆錢

money = 888
def func1():
    name = ‘shuai‘
    def func():
        money = 666
        def foo():
            print(‘%s have %s money‘ %(name,money))
        return foo
    return func
f = func1()
f1 = f()

def foo1():
    money = 999
    f1()
foo1()

包了兩層
    先看func1和func,這兩個函數返回閉包函數 func,包含了 name
    func 和 foo ,這倆函數返回閉包函數 foo,包含了 money

f = func1()   拿到 func,再執行f1 = f() 拿到 foo

  簡單應用:



from urllib.request import urlopen
def get(url):
    return urlopen(url).read()
print(get(‘http://www.xiaohua100.cn‘))

獲取校花網信息  
問題:每次都要傳url,網站過多,url是沒法記住的


閉包應用
from urllib.request import urlopen
def index(url):
    def get():
        return urlopen(url).read()
    return get

xiaohua = index(‘http://xiaohua100.cn‘)
print(xiaohua())
jidong = index(‘https://www.jd.com/‘)
print((jidong()))

這樣想要下載哪個就執行哪個,不用就放著,並且不用傳url;

  


裝飾器

  實際就是閉包函數的

  軟件開發的開發封閉原則

    對擴展是開放的,對修改是封閉的

裝飾器本身可以是任意可調用對象,被裝飾者也可以是任意可調用對象

裝飾器的原則:1 不修改被裝飾對象的源代碼 
                    2 不修改被裝飾對象的調用方式
	
裝飾器的目標:在遵循裝飾器原則前提下,為被裝飾對象添加上新功能        

  通過裝飾器來檢測程序運行時間:

import time
import random
# 裝飾器
def func(func):     # func = index
    def timmer():
        start_time = time.time()
        func()      # index()
        end_time = time.time()
        print(‘運行時間:%s‘ %(end_time-start_time))
    return timmer
#被裝飾對象
def index():
    time.sleep(random.randrange(1,6))
    print(‘歡迎!‘)

index = func(index)
index()

  上面的問題:

如果還有其他的被裝飾對象,那麽裝飾誰就得寫一遍代碼:
index = func(index)
index()

admin_page = func(admin_page )
admin_page ()

這樣很麻煩

    @語法:

      寫在在被裝飾對象的正上方 :@裝飾器  

      流程跟上方是一樣的:將下方函數當做參數傳給裝飾器,把返回的結果重新賦值給下方函數的名字   

上面的代碼可以這樣寫,省去了   index = func(index)

@func           # func(index)
def index():
    time.sleep(random.randrange(1,6))
    print(‘歡迎!‘)
index()

  舉個栗子

import time
import random
def timmer(func):
    def f_time():
        start_time = time.time()
        func()
        end_time = time.time()
        print(‘運行時間 %s‘ %(end_time-start_time))
    return f_time
def func(func):
    def foo():
        name = input(‘用戶名:‘)
        pwd = input(‘密碼:‘)
        if name == ‘shuai‘ and pwd == ‘123‘:
            print(‘登錄成功!‘)
            func()
        else:
            print(‘登錄失敗!‘)
    return foo
@func
@timmer        # 在下面的先執行,這樣就把認證也計算在內
def index():
    # time.sleep(random.randrange(1,6))
    time.sleep(2)
    print(‘歡迎!‘)
index()
註:
  被裝飾的對象,上面的裝飾器,離得近的先執行

分析:
@timmer  是 index = timmer(index) 這裏傳入的是最原始的index,得到包含了f_time的
@func  是 index = auth(timmer(index)) 這裏傳入的index是@timmer執行後的index,然後再賦值給index

  

    有參裝飾器:

db_path=r‘F:\python基礎\a.txt‘

login_dic={
    ‘user‘:None,
    ‘status‘:False,
}
def deco(auth_type=‘170‘):
    def auth(func):
        def wrapper(*args,**kwargs):
            if auth_type == ‘170‘:
                if login_dic[‘user‘] and login_dic[‘status‘]:
                    res = func(*args, **kwargs)
                    return res
                name=input(‘用戶名: ‘)
                password=input(‘密碼: ‘)
                with open(db_path,‘r‘,encoding=‘utf-8‘) as f:
                    user_dic=eval(f.read())
                if name in user_dic and password == user_dic[name]:
                        print(‘登錄成功‘)
                        login_dic[‘user‘]=name
                        login_dic[‘status‘]=True
                        res=func(*args,**kwargs)
                        return res
                else:
                    print(‘登錄失敗‘)
            elif auth_type == ‘160‘:
                print(‘長的矮的‘)
            elif auth_type == ‘180‘:
                print(‘長的高的‘)
            else:
                print(‘沒有的選項‘)
        return wrapper
    return auth
@deco(auth_type=‘170‘) #@auth #index=auth(index)   這裏先執行deco函數,執行的結果auth作為index的裝飾器
def index():
    print(‘歡迎登錄‘)
@deco(auth_type=‘160‘)
def home(name):
    print(‘歡迎%s登錄‘ %name)
index()
home(‘shuai‘)



這裏是簡單例子,根據身高來執行不同的操作,這個身高是一個條件,使用閉包把這個變量包進去,在使用裝飾器deco時,需要傳入這個值,這時deco執行,返回autn,所以裝飾器就變成了@auth,然後就像無參裝飾器一樣運行了

  

練練

(1)

tag = True
while tag:
    name = input(‘用戶名:‘)
    pwd = input(‘密碼:‘)
    f = open(‘a.txt‘,encoding=‘UTF-8‘)
    for line in f:
        s = eval(line)
        # print(type(s))
        if name == s[‘name‘] and pwd == s[‘password‘]:
            print(‘登錄成功!‘)
            tag = False
            break
        print(‘登錄失敗!‘)

  

(2)

from urllib.request import urlopen
import os
cache_path=r‘F:\下載\cache.txt‘
def make_cache(func):
    def wrapper(*args,**kwargs):
        if os.path.getsize(cache_path):
            #有緩存
            print(‘\033[45m=========>有緩存\033[0m‘)
            with open(cache_path,‘rb‘) as f:
                res=f.read()
        else:
            res=func(*args,**kwargs) #下載
            with open(cache_path,‘wb‘) as f: #制作緩存
                f.write(res)
        return res
    return wrapper
@make_cache
def get(url):
    return urlopen(url).read()
get(‘https://www.baidu.com/‘)

  

十、閉包函數、函數對象、裝飾器