locals 和 globals(python學習14)

localsglobals

我們先偏離一下 HTML 處理的主題, 討論一下Python 如何處理變量。 Python 有兩個內置的函數, localsglobals, 它們提供了基於dictionary 的訪問局部和全局變量的方式。

還記得 locals 嗎? 您第一次是在這裏看到的:

    def unknown_starttag(self, tag, attrs):
        

    strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs]    

    self.pieces.append("<%(tag)s%(strattrs)s>" % locals())

不, 等等, 此時您還不能理解 locals 。首先,您需要學習關於命名空間的知識。這很枯燥, 但是很重要, 因此要要耐心些。

Python使用叫做名字空間的東西來記錄變量的軌跡。名字空間只是一個 dictionary,它的鍵字就是變量名,它的值就是那些變量的值。實際上,名字空間可以象 Python 的 dictionary 一樣進行訪問,一會我們就會看到。

在一個 Python程序中的任何一個地方,都存在幾個可用的名字空間。每個函數都有着自已的名字空間,叫做局部名字空間,它記錄了函數的變量,包括函數的參數和局部定義的變量。每個模塊擁有它自已的名字空間,叫做全局名字空間,它記錄了模塊的變量,包括函數、類、其它導入的模塊、模塊級的變量和常量。還有就是內置名字空間,任何模塊均可訪問它,它存放着內置的函數和異常。

當一行代碼要使用變量 x 的值時,Python 會到所有可用的名字空間去查找變量,按照如下順序:

  1. 局部名字空間 - 特指當前函數或類的方法。如果函數定義了一個局部變量 x,或一個參數 xPython 將使用它,然後停止搜索。
  2. 全局名字空間 - 特指當前的模塊。如果模塊定義了一個名爲 x的變量,函數或類,Python 將使用它然後停止搜索。
  3. 內置名字空間 - 對每個模塊都是全局的。作爲最後的嘗試,Python 將假設 x是內置函數或變量。

如果 Python 在這些名字空間找不到 x,它將放棄查找並引發一個 NameError異常,同時傳 遞 There is no variable named 'x'這樣一條信息,回到 例 3.18“引用未賦值的變量”,您會看到一路上都有這樣的信息。但是您並沒有體會到 Python 在給出這樣的錯誤之前做了多少的努力。

Python 2.2 引入了一種略有不同但重要的改變,它會影響名字空間的搜索順序:嵌套的作用域。 在 Python 2.2版本之前,當您在一個嵌套函數lambda 函數 中引用一個變量時,Python 會在當前 (嵌套的或 lambda) 函數的名字空間中搜索,然後在模塊的名字空間。Python 2.2 將只在當前 (嵌套的或 lambda) 函數的名字空間中搜索,然後是在父函數的名字空間中搜索,接着是模塊的名字空間中搜索。Python2.1 可 以兩種方式工作,缺省地,按 Python 2.0的方式工作。但是您可以把下面一行代碼增加到您的模塊頭部,使您的模塊工作起來象 Python 2.2 的方式:

from __future__ import nested_scopes

您是否爲此而感到困惑? 不要絕望! 我敢說這一點非常酷。象 Python 中的許多事情一樣,名字空間 在運行時直接可以訪問。怎麼樣? 不錯吧,局部名字空間可以通過內置的locals 函數來訪問。全局 (模塊級別) 名字空間可以通過內置的globals 函數來訪問。

例 8.10. locals 介紹

>>> def foo(arg): 

...     x = 1


...     print locals()


...     


>>> foo(7)        {'arg': 7, 'x': 1}

>>> foo('bar')    

{'arg': 'bar', 'x': 1}
函數 foo在它的局部名字空間中有兩個變量: arg,它的值是被傳入函數的,和x, 它是在函數裏定義的。
locals返回一個名字/值對的 dictionary。這個 dictionary 的鍵字是字符串形式的變量名字,dictionary的值是變量的實際值。所以用 7 來調用 foo,會打印出包含函數兩個局部變量的 dictionary: arg (7) 和 x (1)。
回想一下,Python 有動態數據類型,所以您可以非常容易地傳遞給 arg 一個字符串,這個函數 (和對 locals的調用) 將仍然很好的工作。locals 可以用於所有類型的變量。

locals 對局部 (函數) 名字空間做了些什麼,globals 就對全局 (模塊) 名字空間做了什麼。然而 globals 更令人興奮,因爲一個模塊的名字空間是更令人興奮的。[3]不僅僅是模塊的名字空間包含了模塊級的變量和常量,它還包括了所有在模塊中定義的函數和類。再加上,它包括了任何被導入到模塊中的東西。

回想一下 from module importimport module 之間的不同。使用 import module,模塊自身被導入,但是它保持着自已的名字空間,這就是爲什麼您需要使用模塊名來訪問它的函數或屬性:module.function 的原因。但是使用 frommoduleimport,實際上是從另一個模塊中將指定的函數和屬性導入到您自己的名字空間,這就是爲什麼您可以直接訪問它們卻不需要引用它們所來源的模塊的原因。使用globals 函數,您會真切地看到這一切的發生。

例 8.11. globals 介紹

看看下面列出的在文件 BaseHTMLProcessor.py尾部的代碼塊:


if __name__ == "__main__":
    for k, v in globals().items():             print k, "=", v
不要被嚇壞了,想想以前您已經全部都看到過了。globals 函數返回一個 dictionary,我們使用 items 方法和多變量賦值遍歷dictionary。在這裏唯一的新東西就是 globals函數。

現在從命令行運行這個腳本會得到下面的輸出 (注意您的輸出可能有略微的不同, 這依賴於您的系統平臺和所安裝的Python 版本):

c:\docbook\dip\py> python BaseHTMLProcessor.py
SGMLParser = sgmllib.SGMLParser                


htmlentitydefs = <module 'htmlentitydefs' from 'C:\Python23\lib\htmlentitydefs.py'> 
BaseHTMLProcessor = __main__.BaseHTMLProcessor 


__name__ = __main__                            


...略...
SGMLParser使用了 from moduleimportsgmllib中被導入。也就是說它被直接導入到我們的模塊名字空間了,就是這樣。
對比這個和 htmlentitydefs, 它是用 import被導入的。 也就是說 htmlentitydefs 模塊本身也在名字空間中, 但是entitydefs 變量定義在 htmlentitydefs 之外。
這個模塊只定義一個類, BaseHTMLProcessor, 不錯。 注意這兒的值就是類本身,不是一個特別的類實例。
記得 if__name__ 技巧 嗎?當運行一個模塊時(對從另外一個模塊中導入而言) ,內置的 __name__ 是一個特殊值__main__。因爲我們是把這個模塊當作腳本從命令來運行的,故__name__ 值爲 __main__,這就是爲什麼我們這段簡單地打印 globals 的代碼可以執行的原因。
使用 localsglobals函數,通過提供變量的字符串名字您可以動態地得到任何變量的值。這種方法提供了這樣的功能: getattr 函數允許您通過提供函數的字符串名來動態地訪問任意的函數。

localsglobals之間有另外一個重要的區別,您應該在它困擾您之前就瞭解它。它無論如何都會困擾您的,但至少您還記得了解過它。

例 8.12. locals 是隻讀的, globals不是


def foo(arg):
    

    x = 1
    

        print locals()    
    

    locals()["x"] = 2 


   print "x=",x      



z = 7


print "z=",z


foo(3)


globals()["z"] = 8    


print "z=",z          
因爲使用 3 來調用foo,會打印出 {'arg': 3,'x': 1}。這個應該沒什麼奇怪的。
locals是一個返回 dictionary 的函數, 並且在 dictionary 中設置一個值。您可能認爲這樣會改變局部變量x 的值爲 2,但並不會。locals實際上沒有返回局部名字空間,它返回的是一個拷貝。所以對它進行改變對局部名字空間中的變量值並無影響。
這樣會打印出 x=1,而不是 x= 2
在有了對 locals的經驗之後,您可能認爲這樣 不會 改變z 的值,但是可以。由於 Python 在實現過程中內部有所區別(關於這些區別我寧可不去研究,因爲我自已還沒有完全理解) ,globals返回實際的全局名字空間,而不是一個拷貝: 與 locals 的行爲完全相反。所以對 globals 所返回的 dictionary的任何的改動都會直接影響到全局變量。
這樣會打印出 z=8,而不是 z= 7
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章