1. 程式人生 > >Python - 函式 - 閉包、裝飾器、遞迴

Python - 函式 - 閉包、裝飾器、遞迴

目錄

一、閉包 - 內部函式包含,對外層作用域而非全域性作用域的引用

1-1 閉包的意義 - 優先使用自己外層包裹的作用域

二、裝飾器 - 閉包的一種應用方式

2-1 不使用裝飾器實現裝飾器的功能 - 使用閉包

2-2 無參裝飾器(@裝飾器名)

2-2-1 無參裝飾器 - 書寫格式

2-2-2 無參裝飾器練習 - 認證功能裝飾器

2-3 有參裝飾器(@裝飾器名(引數名))

2-3-1 有參裝飾器書寫格式

2-4 函式上疊加 多裝飾器 的執行順序

2-5 @wraps  對裝飾器內層函式的使用 -- 讓使用裝飾器的函式 呼叫__name__,__doc__的時候可以指向原有函式屬性

三、 遞迴  - 呼叫函式的過程中直接或者間接呼叫本身

3-1 二分法


一、閉包 - 內部函式包含,對外層作用域而非全域性作用域的引用

def counter():
    n = 0

    def incr():
        nonlocal n
        # 修改巢狀作用域(enclosing 作用域,外層非全域性作用域)中的變數
        x = n
        n += 1
        return x

    return incr


c = counter()
print(c()) # 第一次 n=0 x=0 n++
print(c())# 2 n=1 x=1 n++
print(c())
print(c.__closure__[0].cell_contents)  # 檢視閉包的元素

1-1 閉包的意義 - 優先使用自己外層包裹的作用域

'''
閉包的意義:
返回的函式物件,不僅僅是一個函式物件,在該函式外還包裹了一層作用域,
這使得,該函式無論在何處呼叫,優先使用自己外層包裹的作用域
'''

# 應用領域:延遲計算(原來我們是傳參,現在我們是包起來)
from urllib.request import urlopen


def index(url):
    def get():
        return urlopen(url).read()

    return get


baidu = index('http://www.baidu.com')
print(baidu().decode('utf-8'))

二、裝飾器 - 閉包的一種應用方式

概念:裝飾器他人的器具,本身可以是任意可呼叫物件,被裝飾者也可以是任意可呼叫物件。
          即:裝飾器是在不修改被裝飾物件原始碼和呼叫方式的前提下,為被裝飾物件新增新功能的工具

原則:

  1. 不修改被裝飾物件的原始碼
  2. 不修改被裝飾物件的呼叫方式

目標: 在遵循原則1和2的前提下,為被裝飾物件新增上新功能

執行原理:python直譯器,一旦執行到@裝飾器的名字,就會呼叫裝飾器,然後將被裝飾函式的記憶體地址,當做引數傳給裝飾器,最後將裝飾器呼叫的結果賦值給原函式名

函式上新增多個裝飾器的執行順序:解釋呼叫@語法的時候是自下而上執行(即從越靠近函式的執行器先先執行) 執行裝飾器內部函式的wrapper的時候是自上而下

即,func = @outter_最外層(@outter_中間(@outter_內層))

 

2-1 不使用裝飾器實現裝飾器的功能 - 使用閉包

import time


# 原函式index(),無參
def index():
    print('welcome to index page')
    time.sleep(3)


# 原函式home(name) 有參
def home(name):
    print('welcome to index page')
    time.sleep(2)
    return 123


# 閉包函式,用來接收index的地址並新增新功能之後返回
def outter(func):
    def wrapper(*args, **kwargs):
        # 讓傳入的引數 原封不動,則可以解決原函式對引數(有參無參)的要求

        start = time.time()
        res = func(*args, **kwargs)
        # 執行形參函式,並且將func的返回值賦給res
        stop = time.time()

        print('run time is %s' % (stop - start))
        return res  # 返回傳入函式的返回值

    return wrapper


index = outter(index)  # 將index地址傳入putter,返回wrapper地址
# 當前重新複製的index儲存著wrapper的地址
index()

home = outter(home)
# home=outter(最原始那個home函式的內地址) #home=wrapper函式的內地址
res = home('egon')  # res=wrapper函式的內地址('egon')
print(res)

2-2 無參裝飾器(@裝飾器名)

import time


def timmer(func):
    def wrapper(*args, **kwargs):
        # 讓傳入的引數 原封不動,則可以解決原函式對引數(有參無參)的要求

        start_time = time.time()
        # 開始時間
        res = func(*args, **kwargs)
        # 執行形參函式,並且將func的返回值賦給res
        stop_time = time.time()
        # 結束時間

        print('run time is %s' % (stop_time - start_time))
        print(res)
        return res  # 返回傳入函式的返回值

    return wrapper


@timmer  # 呼叫timmer(foo)並賦值給foo,即foo = timmer(foo)
def foo():
    time.sleep(3)
    print('from foo')


# 在foo()函式上套上timmer的無參裝飾器

foo()  # foo = timmer(foo)

2-2-1 無參裝飾器 - 書寫格式

'''
def outter(func):
    def wrapper(*args,**kwargs)

        … 邏輯程式碼塊 …
        res = func(*args,**kwargs)
        … 邏輯程式碼塊 …

        return res
    return wrapper

@outter
def index():
    pass

index()

'''

2-2-2 無參裝飾器練習 - 認證功能裝飾器

def login_out(func):
    def login_wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res

    return login_wrapper


def users_list():
    # 返回值為users_list
    users_list = []
    with open('user_and_pwd', 'rt', encoding='utf-8') as users_pwd_f:
        users_date = users_pwd_f.read()
        user_list = users_date.split('/')
        user_list.remove('')

        for i in range(len(user_list)):
            users_name = user_list[i].split('|')
            users_list.append(users_name)
    print(users_list)
    return (users_list)


def login_out(func):
    def login(*args, **kwargs):
        # 登陸判斷 三次嘗試機會 返回值: '登陸使用者名稱'
        res = func(*args, **kwargs)
        count = 0
        uname_dic = {}
        for n in users_list():
            uname_dic[n[0]] = n[1]
        # print(uname_dic)

        while count < 3:
            name = input('請輸入使用者名稱:').strip()
            if name in uname_dic:
                pwd = input('請輸入密碼:').strip()
                if pwd == uname_dic[name]:
                    print('登陸成功!\n')
                    uname = name
                    login = True
                    break
            else:
                print('使用者名稱不存在!')
                count += 1

        # return (name)
        print(res)
        return res  # 接受到原函式的返回值

    return login


@login_out
def index():
    print('認證功能裝飾器')
    res = 123
    return res


index()

2-3 有參裝飾器(@裝飾器名(引數名))

def auth(driver='file'):
    def auth2(func):
        def wrapper(*args, **kwargs):

            name = input("user: ")
            pwd = input("pwd: ")
            if driver == 'file':
                if name == 'egon' and pwd == '123':
                    print('login successful')
                    res = func(*args, **kwargs)
                    return res
            elif driver == 'ldap':
                print('ldap')

        return wrapper

    return auth2


@auth(driver='file')  # 相等於@auth2 但在其內部可以使用dirver的值
def foo(name):
    print(name)


foo('egon')

2-3-1 有參裝飾器書寫格式

# 在無參裝飾器的外層再包一層函式,最外層函式用來進行往內層傳值
'''
有參裝飾器(書寫格式,做多三層)

def out(d='123',a=2,)
    def outter(func):
        def wrapper(*args,**kwargs)
            
            … 邏輯程式碼塊 …

            res = func(*args,**kwargs)
                
            … 邏輯程式碼塊 …

            return res
        return wrapper


@out(d='1111')
def index():
    pass

index()
'''

2-4 函式上疊加 多裝飾器 的執行順序

'''
解釋呼叫@語法的時候是自下而上執行(即從越靠近函式的執行器先先執行) 
執行裝飾器內部函式的wrapper的時候是自上而下 

即,func = @outter_最外層(@outter_中間(@outter_內層))
'''

import time


def outter1(func1):  # func1=wrapper2
    print('outter1')

    def wrapper1(*args, **kwargs):
        print('wrapper1')
        res1 = func1(*args, **kwargs)  # res1=wrapper2(*args,**kwargs)
        return res1

    return wrapper1


def outter2(func2):  # func2=最原始的那個index的記憶體地址
    print('outter2')

    def wrapper2(*args, **kwargs):
        print('wrapper2')
        res2 = func2(*args, **kwargs)
        return res2

    return wrapper2


@outter1  # index=outter1(wrapper2) #index=wrapper1
@outter2  # outter2(最原始的那個index的記憶體地址) ===> wrapper2
def index():
    print('welcome to index page')
    time.sleep(3)


index()  # wrapper1()

'''
outter2
outter1
wrapper1
wrapper2

'''

 

2-5 @wraps  對裝飾器內層函式的使用 -- 讓使用裝飾器的函式 呼叫__name__,__doc__的時候可以指向原有函式屬性

# ------ 不對內層函式使用 @ wraps() - ---
import time


def outter1(func1):
    # print('outter1')
    def wrapper1(*args, **kwargs):
        # print('wrapper1')
        res1 = func1(*args, **kwargs)  # res1=wrapper2(*args,**kwargs)
        return res1

    return wrapper1


@outter1
def index():
    '''
    index 功能
    '''
    print('index page')
    time.sleep(2)


print(index.__name__)  # 顯示函式名字
print(index.__doc__)  # 顯示函式的註釋內容

# 使用裝飾器之後 會顯示 裝飾器重新複製的函式物件資料


import time
from functools import wraps


# ------ 對內層函式使用 @ wraps() - ---
def outter1(func1):
  
    # 讓使用裝飾器的函式 呼叫__name__,__doc__的時候可以指向原有函式屬性
    # 若有多層函式巢狀,只需要在最內層的裝飾器新增 @wraps

    @wraps(func1)
    def wrapper1(*args, **kwargs):
        # print('wrapper1')
        res1 = func1(*args, **kwargs)  # res1=wrapper2(*args,**kwargs)
        return res1

    return wrapper1


@outter1
def index():
    '''
    index 功能
    '''
    print('index page')
    time.sleep(2)


print(index.__name__)  # 顯示函式名字
print(index.__doc__)  # 顯示函式的註釋內容

三、 遞迴  - 呼叫函式的過程中直接或者間接呼叫本身

'''
函式的遞迴 : 函式巢狀呼叫的一種特殊形式,在呼叫函式的過程中直接或者間接呼叫本身

遞迴呼叫必須有兩個明確的階段:
  1.回溯:一次次遞迴呼叫下去,即一重複的過程
          但每次重複,問題的規模減少,直到逼近最終結果
          即回溯結果一定有一個明確的結束條件
  2.遞推:一層一層的往回傳值
'''

l = [1, [2, [3, [4, [5, [6, ]]]]]]


def s(l):
    for i in l:
        if type(i) is not list:
            print(i)
        else:
            s(i)


s(l)

3-1 二分法

nums = [13, 15, 17, 23, 31, 54, 87, 99, 104, 230]


def func(num, search):
    print(num)

    if len(num) == 0:
        print('can not find it')
        return
    middle = len(num) // 2
    if num[middle] > search:
        func(num[:middle], search)

    elif num[middle] < search:
        func(num[middle:], search)

    else:
        print('find it')
        return


func(nums, 3)

'''
[13, 15, 17, 23, 31, 54, 87, 99, 104, 230]
[13, 15, 17, 23, 31]
[13, 15]
[13]
[]
can not find it
'''



l = [1, 2, 10, 30, 33, 99, 101, 200, 301, 402]


def search(num, l, start=0, stop=len(l) - 1):
    if start <= stop:
        mid = start + (stop - start) // 2
        print('start:[%s] stop:[%s] mid:[%s] mid_val:[%s]' % (start, stop, mid, l[mid]))
        if num > l[mid]:
            start = mid + 1
        elif num < l[mid]:
            stop = mid - 1
        else:
            print('find it', mid)
            return
        search(num, l, start, stop)
    else:  # 如果stop > start則意味著列表實際上已經全部切完,即切為空
        print('not exists')
        return


search(301, l)

'''
start:[0] stop:[9] mid:[4] mid_val:[33]
start:[5] stop:[9] mid:[7] mid_val:[200]
start:[8] stop:[9] mid:[8] mid_val:[301]
find it 8
'''