轉載自:http://www.lingcc.com/2011/12/15/11902/#sec-1python
平常使用python編程時,爲了用某個代碼模塊,一般須要在代碼中先import相應的module。
那麼python的import是如何工做的呢?linux
對於大型的軟件項目,模塊化的管理很是有必要.
因而在現現在的面嚮對象語言中,都有相應的機制來應對這一問題.
如C++中的namespace, Java中的package,C#中的namespace和using.程序員
import就是Python中用於程序模塊化管理的關鍵字.
經過import語句,將模塊中聲明或定義的變量或者函數等名字在當前程序運行的時刻可見.
這樣咱們就能夠直接經過名字的方式,如變量名或者函數名複用原有代碼.編程
經過import語句,咱們就能將python代碼模塊化,方便管理和維護小程序
先看一組示例:dom
>>> path Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'path' is not defined >>> sys.path Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'sys' is not defined >>> import sys >>> path Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'path' is not defined >>> sys.path ['', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PIL', '/usr/lib/python2.7/dist-packages/gst-0.10', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/usr/lib/pymodules/python2.7'] >>> from sys import path >>> path ['', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PIL', '/usr/lib/python2.7/dist-packages/gst-0.10', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/usr/lib/pymodules/python2.7']
這段代碼中,咱們嘗試使用sys包中的path變量獲得python默認查找模塊的路徑信息,只有在import sys以後,python解釋器才能正確的找到該變量.
咱們經過一個python內部函數dir()來看看python解釋器如何找到名字的. dir()函數是python內建函數,用於查看指定做用域下可用的名字.
若沒有傳參數,則打印當前做用域下的可用名字.python2.7
>>> help(dir) Help on built-in function dir in module __builtin__: dir(...) dir([object]) -> list of strings If called without an argument, return the names in the current scope. Else, return an alphabetized list of names comprising (some of) the attributes of the given object, and of attributes reachable from it. If the object supplies a method named __dir__, it will be used; otherwise the default dir() logic is used and returns: for a module object: the module's attributes. for a class object: its attributes, and recursively the attributes of its bases. for any other object: its attributes, its class's attributes, and recursively the attributes of its class's base classes. >>> dir() ['__builtins__', '__doc__', '__name__', '__package__'] >>> import sys >>> dir() ['__builtins__', '__doc__', '__name__', '__package__', 'sys'] >>> dir(sys) [ ..., 'modules', 'path', ... , 'version', 'version_info', 'warnoptions'] >>> from sys import path >>> dir() ['__builtins__', '__doc__', '__name__', '__package__', 'path', 'sys']
執行import語句後,python解釋器會將sys模塊的名字添加到當前做用域中,這樣就能直接經過sys.path獲得python的搜索路徑了.模塊化
注意到,咱們還用了from sys import path語句.經過這條語句path就被直接提高到當前做用域中,這樣path這個名字就能被直接使用了.
之因此有from,是爲了更加精確的讓某個模塊中的某個名字在當前做用域可見.經過這種機制,程序員能夠精確控制當前做用域的名字,防止做用域被沒必要要的名字污染.
另外,這種機制也避免了使用」.」來進行子成員的引用,減少程序員的輸入.
這裏須要提一句,雖然python提供了from XXX import *支持,能將XXX模塊中的全部名字都提高到當前做用域中,可是要當心使用,由於程序員不能精確的知道到底import了哪些名字.函數
再看一組示例:優化
>>> dir() ['__builtins__', '__doc__', '__name__', '__package__'] >>> import sys >>> dir() ['__builtins__', '__doc__', '__name__', '__package__', 'sys'] >>> import sys as SYS >>> dir() ['SYS', '__builtins__', '__doc__', '__name__', '__package__', 'sys'] >>> SYS.path ['', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PIL', '/usr/lib/python2.7/dist-packages/gst-0.10', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/usr/lib/pymodules/python2.7'] >>> sys.path ['', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PIL', '/usr/lib/python2.7/dist-packages/gst-0.10', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/usr/lib/pymodules/python2.7'] >>> del(sys) >>> SYS.path ['', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PIL', '/usr/lib/python2.7/dist-packages/gst-0.10', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/usr/lib/pymodules/python2.7'] >>> sys.path Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'sys' is not defined
上面的例子展現了兩個功能:
有時咱們可能須要編寫一個完整的模塊庫,好比python對XML的處理就須要一堆的函數.這時候可能劃分紅多個文件,更加方便管理.
邏輯上也更加清晰.
所以python引入了對多文件模塊包的支持.說白了,就是import的不是一個文件的內容,而是一個文件夾的內容.
看下面的示例:
>>> dir() ['__builtins__', '__doc__', '__name__', '__package__'] >>> import xml >>> dir() ['__builtins__', '__doc__', '__name__', '__package__', 'xml'] >>> import xml.sax.xmlreader >>> dir() ['__builtins__', '__doc__', '__name__', '__package__', 'xml'] >>> dir(xml) ['_MINIMUM_XMLPLUS_VERSION', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'sax'] >>> dir(xml.sax) ['ContentHandler', 'ErrorHandler', 'InputSource', 'SAXException', 'SAXNotRecognizedException', 'SAXNotSupportedException', 'SAXParseException', 'SAXReaderNotAvailable', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', '_create_parser', '_exceptions', '_false', '_key', 'default_parser_list', 'handler', 'make_parser', 'parse', 'parseString', 'xmlreader'] >>> from xml import * >>> dir(xml) ['_MINIMUM_XMLPLUS_VERSION', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'dom', 'etree', 'parsers', 'sax']
表面上看起來,和內容在單個文件內的import機制差很少. 咱們能夠到xml對應的目錄下看看:
erlv@erlv-debian:/usr/lib/python2.7/xml$ ls * __init__.py __init__.pyc __init__.pyo dom: domreg.py expatbuilder.py __init__.py minicompat.py minidom.py NodeFilter.py pulldom.py xmlbuilder.py etree: cElementTree.py ElementInclude.py ElementPath.py ElementTree.py __init__.py parsers: expat.py __init__.py sax: _exceptions.py expatreader.py handler.py __init__.py saxutils.py xmlreader.py
咱們import的xmlreader,它的路徑是xml/sax/xmlreader.py,和import xml.sax.xmlreader相同.
這實際上也正是python解釋器實際的動做.
注意到,每一個文件夾下都有一個_init__.py文件.這個是模塊包中的必須文件,它幫助python解釋器將該目錄識別成包.
沒有此文件的文件夾,python解釋器不會把它當模塊包文件夾的.
_init__.py中通常會指定包中全部的模塊,以及import此包時,須要預先import哪些包等初始化信息.固然,你能夠往裏面添加其餘代碼.
該腳本會在import 包時執行. 默承認覺得空.
另外,還注意到有.py,.pyc和.pyo三個文件.
從上面的觀察中能夠看到,其實python的import機制完成的是名字做用域的相關操做.包括做用域的分層,提高和刪除等等.
Python中的做用域是一個樹狀的結構,經過」.」操做,程序員能夠進入做用域分支中找到想要的名字.
同時,能夠經過from XXX import YYY機制實現將某個樹枝上的名字提高到當前做用域中.
因此,python解釋器在實現這種做用域機制的時候,須要引入做用域層級的概念.
另外,爲了實現這套機制的動態支持,包括提高新名字,名字重命名和名字刪除操做.
Python解釋器採起了全局模塊池的方式.全部的模塊在加載後都添加到這個池中.
在經過鏈表的形式維護樹狀的邏輯結構.
python中靈活的做用域管理,一方面可讓程序員更加方便的對代碼進行模塊化管理,另一方面也增長了靈活性,最大可能的減少當前做用域的名字污染問題.
參考2中的<python源碼剖析>中,詳細介紹了python解釋器中如何支持import動做的.
這部分的實現主要在cpython解釋器的import.c文件中.import動做的入口函數是bltinmodule.c的builtin__import__函數.