欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于Python函數(shù)的作用域規(guī)則和閉包(詳解)

 更新時(shí)間:2017年11月29日 08:55:06   作者:再見(jiàn)紫羅蘭  
下面小編就為大家分享一篇基于Python函數(shù)的作用域規(guī)則和閉包詳解,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧

作用域規(guī)則

命名空間是從名稱到對(duì)象的映射,Python中主要是通過(guò)字典實(shí)現(xiàn)的,主要有以下幾個(gè)命名空間:

內(nèi)置命名空間,包含一些內(nèi)置函數(shù)和內(nèi)置異常的名稱,在Python解釋器啟動(dòng)時(shí)創(chuàng)建,一直保存到解釋器退出。內(nèi)置命名實(shí)際上存在于一個(gè)叫__builtins__的模塊中,可以通過(guò)globals()['__builtins__'].__dict__查看其中的內(nèi)置函數(shù)和內(nèi)置異常。

全局命名空間,在讀入函數(shù)所在的模塊時(shí)創(chuàng)建,通常情況下,模塊命名空間也會(huì)一直保存到解釋器退出??梢酝ㄟ^(guò)內(nèi)置函數(shù)globals()查看。

局部命名空間,在函數(shù)調(diào)用時(shí)創(chuàng)建,其中包含函數(shù)參數(shù)的名稱和函數(shù)體內(nèi)賦值的變量名稱。在函數(shù)返回或者引發(fā)了一個(gè)函數(shù)內(nèi)部沒(méi)有處理的異常時(shí)刪除,每個(gè)遞歸調(diào)用有它們自己的局部命名空間??梢酝ㄟ^(guò)內(nèi)置函數(shù)locals()查看。

python解析變量名的時(shí)候,首先搜索局部命名空間。如果沒(méi)有找到匹配的名稱,它就會(huì)搜索全局命名空間。如果解釋器在全局命名空間中也找不到匹配值,最終會(huì)檢查內(nèi)置命名空間。如果仍然找不到,就會(huì)引發(fā)NameError異常。

不同命名空間內(nèi)的名稱絕對(duì)沒(méi)有任何關(guān)系,比如:

a = 42
def foo():
  a = 13
  print "globals: %s" % globals()
  print "locals: %s" % locals()
  return a
foo()
print "a: %d" % a

結(jié)果:

globals: {'a': 42, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'C:\\Users\\h\\Desktop\\test4.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x0000000002C17AC8>, '__doc__': None}
locals: {'a': 13}
a: 42

可見(jiàn)在函數(shù)中對(duì)變量a賦值會(huì)在局部作用域中創(chuàng)建一個(gè)新的局部變量a,外部具有相同命名的那個(gè)全局變量a不會(huì)改變。

在Python中賦值操作總是在最里層的作用域,賦值不會(huì)復(fù)制數(shù)據(jù),只是將命名綁定到對(duì)象。刪除也是如此,比如在函數(shù)中運(yùn)行del a,也只是從局部命名空間中刪除局部變量a,全局變量a不會(huì)發(fā)生任何改變。

如果使用局部變量時(shí)還沒(méi)有給它賦值,就會(huì)引發(fā)UnboundLocalError異常:

a = 42
def foo():
  a += 1
  return a
foo()

上述函數(shù)中定義了一個(gè)局部變量a,賦值語(yǔ)句a += 1會(huì)嘗試在a賦值之前讀取它的值,但全局變量a是不會(huì)給局部變量a賦值的。

要想在局部命名空間中對(duì)全局變量進(jìn)行操作,可以使用global語(yǔ)句,global語(yǔ)句明確地將變量聲明為屬于全局命名空間:

a = 42
def foo():
  global a
  a = 13
  print "globals: %s" % globals()
  print "locals: %s" % locals()
  return a
foo()
print "a: %d" % a

輸出:

globals: {'a': 13, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'C:\\Users\\h\\Desktop\\test4.py', '__package__': None, '__name__': '__main__', 'foo': <function foo at 0x0000000002B87AC8>, '__doc__': None}
locals: {}
a: 13

可見(jiàn)全局變量a發(fā)生了改變。

Python支持嵌套函數(shù)(閉包),但python 2只支持在最里層的作用域和全局命名空間中給變量重新賦值,內(nèi)部函數(shù)是不可以對(duì)外部函數(shù)中的局部變量重新賦值的,比如:

def countdown(start):
  n = start
  def display():
    print n
  def decrement():
    n -= 1
  while n > 0:
    display()
    decrement()
countdown(10)

運(yùn)行會(huì)報(bào)UnboundLocalError異常,python 2中,解決這個(gè)問(wèn)題的方法是把變量放到列表或字典中:

def countdown(start):
  alist = []
  alist.append(start)
  def display():
    print alist[0]
  def decrement():
    alist[0] -= 1
  while alist[0] > 0:
    display()
    decrement()
countdown(10)

在python 3中可以使用nonlocal語(yǔ)句解決這個(gè)問(wèn)題,nonlocal語(yǔ)句會(huì)搜索當(dāng)前調(diào)用棧中的下一層函數(shù)的定義。:

def countdown(start):
  n = start
  def display():
    print n
  def decrement():
    nonlocal n
    n -= 1
  while n > 0:
    display()
    decrement()
countdown(10)

閉包

閉包(closure)是函數(shù)式編程的重要的語(yǔ)法結(jié)構(gòu),Python也支持這一特性,舉例一個(gè)嵌套函數(shù):

def foo():
  x = 12
  def bar():
    print x
  return bar
foo()()

輸出:12

可以看到內(nèi)嵌函數(shù)可以訪問(wèn)外部函數(shù)定義的作用域中的變量,事實(shí)上內(nèi)嵌函數(shù)解析名稱時(shí)首先檢查局部作用域,然后從最內(nèi)層調(diào)用函數(shù)的作用域開(kāi)始,搜索所有調(diào)用函數(shù)的作用域,它們包含非局部但也非全局的命名。

組成函數(shù)的語(yǔ)句和語(yǔ)句的執(zhí)行環(huán)境打包在一起,得到的對(duì)象就稱為閉包。在嵌套函數(shù)中,閉包將捕捉內(nèi)部函數(shù)執(zhí)行所需要的整個(gè)環(huán)境。

python函數(shù)的code對(duì)象,或者說(shuō)字節(jié)碼中有兩個(gè)和閉包有關(guān)的對(duì)象:

co_cellvars: 是一個(gè)元組,包含嵌套的函數(shù)所引用的局部變量的名字
co_freevars: 是一個(gè)元組,保存使用了的外層作用域中的變量名

再看下上面的嵌套函數(shù):

>>> def foo():
    x = 12
    def bar():
      return x
    return bar
 
>>> foo.func_code.co_cellvars
('x',)
>>> bar = foo()
>>> bar.func_code.co_freevars
('x',)

可以看出外層函數(shù)的code對(duì)象的co_cellvars保存了內(nèi)部嵌套函數(shù)需要引用的變量的名字,而內(nèi)層嵌套函數(shù)的code對(duì)象的co_freevars保存了需要引用外部函數(shù)作用域中的變量名字。

在函數(shù)編譯過(guò)程中內(nèi)部函數(shù)會(huì)有一個(gè)閉包的特殊屬性__closure__(func_closure)。__closure__屬性是一個(gè)由cell對(duì)象組成的元組,包含了由多個(gè)作用域引用的變量:

>>> bar.func_closure
(<cell at 0x0000000003512C78: int object at 0x0000000000645D80>,)

若要查看閉包中變量的內(nèi)容:

>>> bar.func_closure[0].cell_contents
12

如果內(nèi)部函數(shù)中不包含對(duì)外部函數(shù)變量的引用時(shí),__closure__屬性是不存在的:

>>> def foo():
    x = 12
    def bar():
      pass
    return bar
 
>>> bar = foo()
>>> print bar.func_closure
None

當(dāng)把函數(shù)當(dāng)作對(duì)象傳遞給另外一個(gè)函數(shù)做參數(shù)時(shí),再結(jié)合閉包和嵌套函數(shù),然后返回一個(gè)函數(shù)當(dāng)做返回結(jié)果,就是python裝飾器的應(yīng)用啦。

延遲綁定

需要注意的一點(diǎn)是,python函數(shù)的作用域是由代碼決定的,也就是靜態(tài)的,但它們的使用是動(dòng)態(tài)的,是在執(zhí)行時(shí)確定的。

>>> def foo(n):
    return n * i
 
>>> fs = [foo for i in range(4)]
>>> print fs[0](1)

當(dāng)你期待結(jié)果是0的時(shí)候,結(jié)果卻是3。

這是因?yàn)橹挥性诤瘮?shù)foo被執(zhí)行的時(shí)候才會(huì)搜索變量i的值, 由于循環(huán)已結(jié)束, i指向最終值3, 所以都會(huì)得到相同的結(jié)果。

在閉包中也存在相同的問(wèn)題:

def foo():
  fs = []
  for i in range(4):
    fs.append(lambda x: x*i)
  return fs
for f in foo():
  print f(1)

返回:

解決方法,一個(gè)是為函數(shù)參數(shù)設(shè)置默認(rèn)值:

>>> fs = [lambda x, i=i: x * i for i in range(4)]
>>> for f in fs:
    print f(1)

另外就是使用閉包了:

>>> def foo(i):
    return lambda x: x * i
 
>>> fs = [foo(i) for i in range(4)]
>>> for f in fs:
    print f(1)

或者:

>>> for f in map(lambda i: lambda x: i*x, range(4)):
    print f(1)

使用閉包就很類似于偏函數(shù)了,也可以使用偏函數(shù):

>>> fs = [functools.partial(lambda x, i: x * i, i) for i in range(4)]
>>> for f in fs:
    print f(1)

這樣自由變量i都會(huì)優(yōu)先綁定到閉包函數(shù)上。

以上這篇基于Python函數(shù)的作用域規(guī)則和閉包(詳解)就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 淺談算法之最小生成樹(shù)Kruskal的Python實(shí)現(xiàn)

    淺談算法之最小生成樹(shù)Kruskal的Python實(shí)現(xiàn)

    最小生成樹(shù)Kruskal算法可以稱為“加邊法”,初始最小生成樹(shù)邊數(shù)為0,每迭代一次就選擇一條滿足條件的最小代價(jià)邊,加入到最小生成樹(shù)的邊集合里。本文將介紹它的原理,并用Python進(jìn)行實(shí)現(xiàn)
    2021-06-06
  • PyCharm2020.1.1與Python3.7.7的安裝教程圖文詳解

    PyCharm2020.1.1與Python3.7.7的安裝教程圖文詳解

    這篇文章主要介紹了PyCharm2020.1.1與Python3.7.7的安裝教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-08-08
  • Python讀取TIF影像的多種方法

    Python讀取TIF影像的多種方法

    Python提供了豐富的庫(kù)來(lái)讀取和處理TIFF文件,其中PIL庫(kù)是最常用的,本文給大家介紹Python讀取TIF影像的幾種方法,需要的朋友可以參考下
    2023-07-07
  • Python調(diào)整數(shù)組形狀如何實(shí)現(xiàn)

    Python調(diào)整數(shù)組形狀如何實(shí)現(xiàn)

    這篇文章主要介紹了Python調(diào)整數(shù)組形狀如何實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧
    2022-12-12
  • Python7個(gè)爬蟲(chóng)小案例詳解(附源碼)下篇

    Python7個(gè)爬蟲(chóng)小案例詳解(附源碼)下篇

    這篇文章主要介紹了Python7個(gè)爬蟲(chóng)小案例詳解(附源碼)上篇,本文章內(nèi)容詳細(xì),通過(guò)案例可以更好的理解爬蟲(chóng)的相關(guān)知識(shí),七個(gè)例子分為了三部分,本次為下篇,共有三道題,需要的朋友可以參考下
    2023-01-01
  • matlab中二維插值函數(shù)interp2的使用詳解

    matlab中二維插值函數(shù)interp2的使用詳解

    這篇文章主要介紹了matlab中二維插值函數(shù)interp2的使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-04-04
  • Python調(diào)用Pandas實(shí)現(xiàn)Excel讀取

    Python調(diào)用Pandas實(shí)現(xiàn)Excel讀取

    這篇文章主要為大家介紹了在Python中如何調(diào)用Pandas實(shí)現(xiàn)Excel文件的讀取,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2022-04-04
  • Python?"手繪風(fēng)格"數(shù)據(jù)可視化方法實(shí)例匯總

    Python?"手繪風(fēng)格"數(shù)據(jù)可視化方法實(shí)例匯總

    這篇文章主要給大家介紹了關(guān)于Python?"手繪風(fēng)格"數(shù)據(jù)可視化方法實(shí)現(xiàn)的相關(guān)資料,本文分別給大家?guī)?lái)了Python-matplotlib手繪風(fēng)格圖表繪制、Python-cutecharts手繪風(fēng)格圖表繪制以及Python-py-roughviz手繪風(fēng)格圖表繪制,需要的朋友可以參考下
    2022-02-02
  • 利用python計(jì)算均值、方差和標(biāo)準(zhǔn)差(Numpy和Pandas)

    利用python計(jì)算均值、方差和標(biāo)準(zhǔn)差(Numpy和Pandas)

    這篇文章主要給大家介紹了關(guān)于利用python計(jì)算均值、方差和標(biāo)準(zhǔn)差的相關(guān)資料,Numpy在Python中是一個(gè)通用的數(shù)組處理包,它提供了一個(gè)高性能的多維數(shù)組對(duì)象和用于處理這些數(shù)組的工具,它是使用Python進(jìn)行科學(xué)計(jì)算的基礎(chǔ)包,需要的朋友可以參考下
    2023-11-11
  • Python檢測(cè)生僻字的實(shí)現(xiàn)方法

    Python檢測(cè)生僻字的實(shí)現(xiàn)方法

    最近在工作中碰到一個(gè)需求,要求檢測(cè)字段是否包含生僻字以及一些非法字符如 ~!@#$%^&*。通過(guò)網(wǎng)上的查找資料解決了,現(xiàn)在將解決的過(guò)程和示例代碼分享給大家,有需要的可以參考借鑒。下面來(lái)一起看看吧。
    2016-10-10

最新評(píng)論