Python閉包詳解


首先給出閉包函數的必要條件:

閉包函數必須返回一個函數對象

閉包函數返回的那個函數必須引用外部變量(一般不能是全局變量),而返回的那個函數內部不一定要return

幾個典型的閉包例子:

# ENV>>> Python 3.6
    # NO.1
    def line_conf(a, b):
        def line(x):
            return a * x + b
        return line
    
    # NO.2
    def line_conf():
        a = 1
        b = 2
 
        def line(x):
            print(a * x + b)
        return line
 
    # NO.3
    def _line_(a,b):
        def line_c(c):
            def line(x):
                return a*(x**2)+b*x+c
            return line
        return line_c

           不包括print語句的代碼是4行,閉包寫法是6行,看起來有點不對勁啊?怎么閉包實現需要的代碼量還多呢?別急,我現在有個需求:

            再定義100條直線!

            那么現在誰的代碼量更少呢?很明顯這個是可以簡單計算出來的,采用閉包的方式添加一條直線只需要加一行代碼,而普通做法需要添兩行代碼,定義100條直線兩種做法的代碼量差為:100+6 -(100*2+4) = -98。需要注意的是,實際環境中定義的單個函數的代碼量多達幾十上百行,這時候閉包的作用就顯現出來了,沒錯,大大提高了代碼的可復用性!

            注意:閉包函數引用的外部變量不一定就是其父函數的參數,也可以是父函數作用域內的任意變量,如“熱身”中的第二段代碼:

 

     二、如何顯式地查看“閉包”

接上面的代碼塊:

    L = line_conf()
    print(line_conf().__closure__) #(<cell at 0x05BE3530: int object at 0x1DA2D1D0>,
    # <cell at 0x05C4DDD0: int object at 0x1DA2D1E0>)
    for i in line_conf().__closure__: #打印引用的外部變量值
        print(i.cell_contents) #1  ; #2
            __closure__屬性返回的是一個元組對象,包含了閉包引用的外部變量。

若主函數內的閉包不引用外部變量,就不存在閉包,主函數的_closure__屬性永遠為None:

    def line_conf():
        a = 1
        b = 2
        def line(x):
            print(x+1)  #<<<------
        return line
    L = line_conf()
    print(line_conf().__closure__) # None
    for i in line_conf().__closure__: #拋出異常
        print(i.cell_contents)

 三、為何叫閉包?

 

             如你所見,line_A對象作為line_conf返回的閉包對象,它引用了line_conf下的變量b=1,在print時,全局作用域下定義了新的b變量指向20,最終結果仍然引用的line_conf內的b。這是因為,閉包作為對象被返回時,它的引用變量就已經確定(已經保存在它的__closure__屬性中),不會再被修改。

               是的,閉包在被返回時,它的所有變量就已經固定,形成了一個封閉的對象,這個對象包含了其引用的所有外部、內部變量和表達式。當然,閉包的參數例外。

    四、閉包可以保存運行環境

               思考下面的代碼會輸出什么?

 關於這個問題的深入探討(python新手理解起來可能需要點時間),我們先看下面的代碼(2019/5/19增):

_list = []
for i in range(3):
    def func():
        return i+1
    func.__doc__ = i
    func.__hash__ = i
    func.__repr__ = i
    func.__defaults__ = tuple([i]) #這個屬性必須是tuple類型
    func.__name__ = f'{i}'
    func.hello = i  #自定義一個屬性並賦值
    # 不能再玩了
    _list.append(func)
 
for f in _list:
    print(f.__doc__,
          f.__hash__,
          f.__repr__,
          f.__defaults__,
          f.__name__,
          f.hello,
          f(),
          )
# 輸出
# 0 0 0 (0,) 0 0 3
# 1 1 1 (1,) 1 1 3
# 2 2 2 (2,) 2 2 3

  代碼中我在保存函數時,修改了函數的一些屬性(前幾個叫做magic method,是函數對象默認擁有的),使它們等於循環內的變量i,hello屬性顯然是我自定義的一個屬性,也讓它等於了i。

    然后,我們循環打印每個函數的這些屬性,可以發現,咦~  這些屬性居然可以保存這個變量i     :)

    嗯,是的,函數的一些基本屬性在定義時就會有一個初始的確定值(不論這個值是由可變或不可變對象構成,都是一個完整拷貝,不受源變量變動影響); 閉包保存這個變量的原理是一樣的,它用的是函數的__closure__屬性,這個屬性還有一點特殊,它是只讀的,不能由人為修改。(function還有一個__code__屬性,這個對象很牛)

    這部分內容是對閉包和函數對象的更深一層的探討,理解后更上一層樓;

    不過當你不知道這些屬性時是做什么用時,最好不要修改它們。

五、閉包的實際應用

                現在你已經逐漸領悟“閉包”了,趁熱打鐵,再來一個小例子:

              看到這里,你也可以試着自己寫出一個簡單的閉包函數。

                OK,現在來看一個真正在實際環境中會用到的案例:

                1、【閉包實現快速給不同項目記錄日志】

 

  import logging
    def log_header(logger_name):
        logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(name)s] %(levelname)s  %(message)s',
                            datefmt='%Y-%m-%d %H:%M:%S')
        logger = logging.getLogger(logger_name)
 
        def _logging(something,level):
            if level == 'debug':
                logger.debug(something)
            elif level == 'warning':
                logger.warning(something)
            elif level == 'error':
                logger.error(something)
            else:
                raise Exception("I dont know what you want to do?" )
        return _logging
 
    project_1_logging = log_header('project_1')
 
    project_2_logging = log_header('project_2')
 
    def project_1():
 
        #do something
        project_1_logging('this is a debug info','debug')
        #do something
        project_1_logging('this is a warning info','warning')
        # do something
        project_1_logging('this is a error info','error')
 
    def project_2():
 
        # do something
        project_2_logging('this is a debug info','debug')
        # do something
        project_2_logging('this is a warning info','warning')
        # do something
        project_2_logging('this is a critical info','error')
 
    project_1()
    project_2()

 

 原文地址https://blog.csdn.net/sc_lilei/article/details/80464645

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM