2017年9月12日 星期二

[ Python 文章收集 ] 深入理解 Python 中的 __builtin__ 和 __builtins__

Source From Here 
Preface 
這裡的說明主要是以 Python 2.7 為例,因為在 Python 3+ 中,__builtin__ 模塊被命名為 builtins,下面主要是探討 Python 2.x 中 __builtin__ 模塊和 __builtins__ 模塊的區別和聯繫。 

名稱空間(Namespace) 
首先不得不說 名稱空間,因為名稱空間是 Python 中非常重要的一個概念,所謂名稱空間,其實指的是名稱(標識符)到對象的映射。在一個正常的Python程序的執行過程中,至少存在兩個名稱空間: 
* 內建名稱空間 
* 全局名稱空間

如果定義了函數,則還會有 局部名稱空間,全局名稱空間一般由在程序的全局變量和它們對應的映射對象組成,而局部名稱空間則在函數內部由函數局部變量和它們對應的映射對象組成,這里關鍵的是內建名稱空間,它到底是怎麼產生的? 

內建函數 
在啟動 Python 解釋器之後,即使沒有創建任何的變量或者函數,還是會有許多函數可以使用,比如: 
>>> abs(-1) 
1 
>>> max(1, 3) 
3

我們把這些函數稱為 內建函數,是因為它們不需要我們程序員作任何定義,在啟動 Python 解釋器的時候,就已經導入到內存當中供我們使用: 
>>> abs 
 
>>> max 

內建名稱空間與 __builtins__ 
那麼內建函數也是函數,雖然我們沒有人為導入這些,但是正如前面所說,在啟動 Python 解釋器的時候,會自動幫我們導入,那麼內建函數存在於哪裡呢?其實準確地來說,是 Python 解釋器在啟動的時候會首先加載內建名稱空間,內建名稱空間有許多名字到對象之間映射,而這些名字其實就是內建函數的名稱,對象就是這些內建函數本身(注意區分函數名稱和函數對象的區別)。這些名稱空間由 __builtins__ 模塊中的名字構成: 
>>> dir() 
['__builtins__', '__doc__', '__name__', '__package__']

可以看到有一個 __builtins__ 的模塊名稱,這個模塊本身定義了一個名稱空間,即內建名稱空間,我們不妨 dir 一下: 
>>> dir(__builtins__) 
['ArithmeticError', 'AssertionError', 'AttributeError', ...'vars', 'xrange', 'zip']

會看到我們熟悉的內建函數的名稱,如 list、dict 等,當然還有一些異常和其它屬性。 

__builtins__ 與 __builtin__ 的簡單區別 
既然內建名稱空間由 __builtins__ 模塊中的名稱空間定義,那麼是不是也意味著內建名稱空間中所對應的這些函數也是在 __builtins__ 模塊中實現的呢?顯然不是的,我們可以在解釋器中直接輸入 __builtins__ 
>>> __builtins__ 
__builtin__
' (built-in)>
從結果中可以看到,__builtins__ 其實還是引用了 __builtin__ 模塊而已,這說明真正的模塊是 __builtin__,也就是說,前面提到的內建函數其實是在內建模塊 __builtin__ 中定義的,即 __builtins__ 模塊包含內建名稱空間中內建名字的集合(因為它引用或者說指向了 __builtin__ 模塊),而真正的內建函數、異常和屬性來自 __builtin__ 模塊。也就是說,在 Python 中,其實真正是只有 __builtin__ 這個模塊,並不存在 __builtins__ 這個模塊 
>>> import __builtin__ 
>>> import __builtins__ 
Traceback (most recent call last): 
File "", line 1, in  
ImportError: No module named __builtins__

可以看到,導入 __builtin__ 模塊並沒有問題,但導入 __builtins__ 模塊時就會提示不存在,這充分說明了前面的結論,現在再次總結如下: 
在 Python 中並沒有 __builtins__ 這個模塊,只有 __builtin__ 模塊,__builtins__ 模塊只是在啟動 Python 解釋器時,解釋器為我們自動創建的一個到 __builtin__ 模塊的引用.

當然,至於這種引用到底是怎麼樣,可以看下面的深入區別。 

__builtins__ 與 __builtin__ 的深入區別 
上面只是大概說了下 __builtins__  __builtin__ 兩個模塊的簡單區分而已,其實深究下去,要分成下面所提及的兩種情況。 

在主模塊 __main__ 中 
其實我們在使用 Python 交互器的時候就是在主模塊中進行操作,可以做如下驗證: 
>>> print __name__ 
__main__

在這種情況,__builtins__  __builtin__ 是完全一樣的,它們指向的都是 __builtin__ 這個內建模塊: 
>>> import __builtin__ 
>>> __builtin__ 
 
>>> __builtins__ 
 
>>> __builtin__.__name__ 
'__builtin__' 
>>> __builtins__.__name__ 
'__builtin__' 
>>> __builtins__ == __builtin__ 
True 
>>> __builtins__ is __builtin__ 
True 
>>> id(__builtins__) 
140295127423752 
>>> id(__builtin__) 
140295127423752

可以看到,這時候 __builtins__  __builtin__ 是完全一樣的,它們都指向了同一個模塊對象,其實這也是Python中引用傳遞的概念。其實這種情況跟我們創建一個變量並對它做一次引用傳遞時的情況是一樣的,可以做如下測試: 
>>> def func(): 
... return 'test' 
... 
>>> func 
 
>>> funcs = func 
>>> funcs 
 
>>> func.__name__ 
'func' 
>>> funcs.__name__ 
'func' 
>>> funcs == func 
True 
>>> funcs is func 
True 
>>> id(funcs) 
140077021549944 
>>> id(func) 
140077021549944

顯然,這完全驗證了我們上面的結論。 

不是在主模塊中 
如果不是在主模塊中使用 __builtins__,這時候,__builtins__ 只是對 __builtin__.__dict__ 的一個簡單引用而已,可以通過下面的測試來驗證說明。先創建一個 test.py 模塊,後面我們需要在 Python 交互器中導入它,那麼這時候對於 test 模塊來說,它就不是主模塊了。如下: 
- test.py 
  1. #!/usr/bin/env python  
  2.   
  3. import __builtin__  
  4.   
  5.   
  6. print 'Module name:', __name__  
  7.   
  8.   
  9. print '*==test __builtin__ and __builtins__==*'  
  10. print '__builtin__ == __builtins__', __builtin__ == __builtins__  
  11. print '__builtin__ is __builtins__', __builtin__ is __builtins__  
  12. print 'id(__builtin__)', id(__builtin__)  
  13. print 'id(__builtins__)', id(__builtins__)  
  14.   
  15. print '='*50  
  16.   
  17. print '*==test __builtin__.__dict__ and __builtins__==*'  
  18. print '__builtin__.__dict__ == __builtins__', __builtin__.__dict__ == __builtins__  
  19. print '__builtin__.__dict__ is __builtins__', __builtin__.__dict__ is __builtins__  
  20. print 'id(__builtin__.__dict__)', id(__builtin__.__dict__)  
  21. print 'id(__builtins__)', id(__builtins__)  
在 Python 交互器中導入上面這個 test 模塊,如下: 
>>> import test 
Module name: test 
*==test __builtin__ and __builtins__==* 
__builtin__ == __builtins__ False 
__builtin__ is __builtins__ False 
id(__builtin__) 140592847690504 
id(__builtins__) 140592847925608 
================================================== 
*==test __builtin__.__dict__ and __builtins__==* 
__builtin__.__dict__ == __builtins__ True 
__builtin__.__dict__ is __builtins__ True 
id(__builtin__.__dict__) 140592847925608 
id(__builtins__) 140592847925608

可以看到輸出的結果跟我們想的是完全一樣的,即這時候 __builtins__ 其實是對 __builtin__.__dict__ 模塊的引用。 

總結 
不管怎麼說,在啟動 Python 解釋器或運行一個 Python 程序時,內建名稱空間都是從 __builtins__ 模塊中加載的,只是 __builtins__ 本身是對 Python 內建模塊 __builtin__ 的引用,而這種引用又分下面兩種情況: 
* 如果是在主模塊 __main__ 中,__builtins__ 直接引用 __builtin__ 模塊,此時模塊名 __builtins__ 與模塊名 __builtin__ 指向的都是同一個模塊,即 內建模塊(這裡要注意變量名和對象本身的區別 
* 如果不是在主模塊中,那麼 __builtins__ 只是引用了 __builtin__.__dict__


沒有留言:

張貼留言

[ Python 常見問題 ] How to shift a datetime object by 12 hours in python

Source From   Here   Question   Datetime   objects hurt my head for some reason. I am writing to figure out   how to shift a date time obje...