py11 模塊

一 模塊介紹

一個模塊就是一個包含了一組功能的python文件,好比spam.py,模塊名爲spam,能夠經過import spam使用。python

在python中,模塊的使用方式都是同樣的,但其實細說的話,模塊能夠分爲四個通用類別: 
1 使用python編寫的.py文件   算法

2 已被編譯爲共享庫或DLL的C或C++擴展   api

3 把一系列模塊組織到一塊兒的文件夾(注:文件夾下有一個__init__.py文件,該文件夾稱之爲包)   app

4 使用C編寫並連接到python解釋器的內置模塊函數

示例文件學習

#spam.py
print('from the spam.py')

money=1000

def read1():
    print('spam模塊:',money)

def read2():
    print('spam模塊')
    read1()

def change():
    global money
    money=0

py文件區分兩種用途:模塊與腳本

#編寫好的一個python文件能夠有兩種用途:
一:腳本,一個文件就是整個程序,用來被執行 優化

二:模塊,文件中存放着一堆功能,用來被導入使用 #python爲咱們內置了全局變量__name__,ui

當文件被當作腳本執行時:__name__ 等於'__main__' 當文件被當作模塊導入時:__name__等於模塊名spa

#做用:用來控制.py文件在不一樣的應用場景下執行不一樣的邏輯.net

    if __name__ == '__main__':

import

import的本質:

import spam:至關於把spam文件下全部代碼解釋一遍(相似於print的代碼直接運行了),而後賦值給spam,這樣一來就能spam.money,spam.read2()調用了

模塊導入時到底發生了是麼?

  • 當模塊第一次被導入時,先爲源文件(spam模塊)建立新的名稱空間,而後在該命名空間解釋代碼(至關於執行源文件),最後建立名字spam來引用該命名空間,這樣就能夠經過spam.xx訪問。
  • 當再次導入相同文件時,因爲第一次導入後就將模塊名加載到內存了,後續的import語句僅是對已經加載到內存中的模塊對象增長了一次引用,不會從新執行模塊內的語句。
  • ps:咱們能夠從sys.module中找到當前已經加載的模塊,sys.module是一個字典,內部包含模塊名與模塊對象的映射,該字典決定了導入模塊時是否須要從新導入。
#test.py
import spam #只在第一次導入時才執行spam.py內代碼,此處的顯式效果是隻打印一次'from the spam.py',固然其餘的頂級代碼也都被執行了,只不過沒有顯示效果.
import spam
import spam
import spam

'''
執行結果:
from the spam.py
'''

導入模塊還有別的方式,例如:

import spam

import spam,spam1

from spam import *(把spam中全部的不是如下劃線(_)開頭的名字都導入到當前位置)(不建議使用)

from spam import money,read1

from spam import money,read1 as spam_read1

  • from spam import * 由於會吧spam文件全部的變量,函數都導入,可能會與自己文件變量,函數有衝突,不建議使用
  • 經過from導入的模塊,使用時,不能再用spam.xx來使用,只能直接xx或者xxx()來使用。
  • 當用from導入的變量或函數與本文件產生衝突(名字相同)時,以本文件的爲優先

導入模塊時:

首先,會根據sys.path進行尋找,在sys.path對應的路徑中找是否有須要導入的模塊,沒找到就報錯,能夠經過sys.path.append()和sys.path.insert()來添加須要的路徑。

文件的__file__屬性表明當前文件的文件名,os.path.abspath(__file__)表明獲取當前文件的絕對路徑,os.path.dirname()獲取目錄名(至關於上一級)。eg:

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

sys.path 輸出是一個列表,其中第一項是空串'',表明當前目錄(如果從一個腳本中打印出來的話,能夠更清楚地看出是哪一個目錄),亦即咱們執行python解釋器的目錄(對於腳本的話就是運行的腳本所在的目錄)。

包是一種管理 Python 模塊命名空間的形式,採用"點模塊名稱"。

好比一個模塊的名稱是 A.B, 那麼他表示一個包 A中的子模塊 B 。

就好像使用模塊的時候,你不用擔憂不一樣模塊之間的全局變量相互影響同樣,採用點模塊名稱這種形式也不用擔憂不一樣庫之間的模塊重名的狀況。

這樣不一樣的做者均可以提供 NumPy 模塊,或者是 Python 圖形庫。

不妨假設你想設計一套統一處理聲音文件和數據的模塊(或者稱之爲一個"包")。

這裏給出了一種可能的包結構(在分層的文件系統中):

sound/                          頂層包
      __init__.py               初始化 sound 包
      formats/                  文件格式轉換子包
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  聲音效果子包
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  filters 子包
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

在導入一個包的時候,Python 會根據 sys.path 中的目錄來尋找這個包中包含的子目錄。

目錄只有包含一個叫作 __init__.py 的文件纔會被認做是一個包,主要是爲了不一些濫俗的名字(好比叫作 string)不當心的影響搜索路徑中的有效模塊。

最簡單的狀況,放一個空的 :file:__init__.py就能夠了。固然這個文件中也能夠包含一些初始化代碼或者爲(將在後面介紹的) __all__變量賦值。

用戶能夠每次只導入一個包裏面的特定模塊,好比:

import sound.effects.echo

這將會導入子模塊:sound.effects.echo。 他必須使用全名去訪問:

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

還有一種導入子模塊的方法是:

from sound.effects import echo

這一樣會導入子模塊: echo,而且他不須要那些冗長的前綴,因此他能夠這樣使用:

echo.echofilter(input, output, delay=0.7, atten=4)

還有一種變化就是直接導入一個函數或者變量:

from sound.effects.echo import echofilter

一樣的,這種方法會導入子模塊: echo,而且能夠直接使用他的 echofilter() 函數:

echofilter(input, output, delay=0.7, atten=4)

注意當使用from package import item這種形式的時候,對應的item既能夠是包裏面的子模塊(子包),或者包裏面定義的其餘名稱,好比函數,類或者變量。

import語法會首先把item看成一個包定義的名稱,若是沒找到,再試圖按照一個模塊去導入。若是還沒找到,恭喜,一個:exc:ImportError 異常被拋出了。

反之,若是使用形如import item.subitem.subsubitem這種導入形式,除了最後一項,都必須是包,而最後一項則能夠是模塊或者是包,可是不能夠是類,函數或者變量的名字。

導入包的實質就是執行包的__init__.py文件

使用import 包名     # 至關於執行__init__.py,在__init__.py文件裏也能夠import導入模塊,未在__init__.py中導入的模塊儘管導入了包名也沒法使用

使用import 包名.模塊名  # 如此只能導入包中的一個模塊,調用時也須要包名.模塊名.方法名這樣調用(用as優化),帶路徑的導入也是如此:(模塊包名.[路徑(子包).]模塊名)

使用from 包名 import *  # 這樣會先找__init__.py中叫__all__ 的列表變量,把這個列表中的全部名字做爲包內容導入。

  • 當__all__未定義時,from 包名 import *這種方式會執行__init__.py,若__init__.py文件中導入了模塊,則會把模塊導入
  • 當__all__定義了可是爲空時,from 包名 import *這種方式也會執行__init__.py,若__init__.py文件中導入了模塊,會對模塊進行解釋(至關於運行),可是沒法使用該模塊。由於__all__定義了,則只會導入__all__裏的模塊,可是其爲空,因此值會執行__init__.py,對__init__.py文件導入的模塊進行解釋,卻不能使用這些模塊。
  • 當__all__定義了且不爲空時,from 包名 import *這種方式執行__init__.py,對__init__.py文件中導入的模塊進行解釋,可是隻能使用__all__中的模塊

包以及包所包含的模塊都是用來被導入的,而不是被直接執行的。而環境變量都是以執行文件爲準的

好比咱們想在glance/api/versions.py中導入glance/api/policy.py,有的同窗一抽這倆模塊是在同一個目錄下,十分開心的就去作了,它直接這麼作

在version.py中

import policy
policy.get()

沒錯,咱們單獨運行version.py是一點問題沒有的,運行version.py的路徑搜索就是從當前路徑開始的,因而在導入policy時能在當前目錄下找到

可是你想啊,你子包中的模塊version.py極有多是被一個glance包同一級別的其餘文件導入,好比咱們在於glance同級下的一個test.py文件中導入version.py,以下

from glance.api import versions
'''
執行結果:
ImportError: No module named 'policy'
'''

'''
分析:
此時咱們導入versions在versions.py中執行
import policy須要找從sys.path也就是從當前目錄找policy.py,
這必然是找不到的
'''

dir() 函數

內置的函數 dir() 能夠找到模塊內定義的全部名稱。以一個字符串列表的形式返回:
>>> import spam, sys
>>> dir(spam)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'change', 'money', 'read1', 'read2']
>>> dir(sys)  
['__displayhook__', '__doc__', '__excepthook__', '__loader__', '__name__',
 '__package__', '__stderr__', '__stdin__', '__stdout__',
 '_clear_type_cache', '_current_frames', '_debugmallocstats', '_getframe',
 '_home', '_mercurial', '_xoptions', 'abiflags', 'api_version', 'argv',
 'base_exec_prefix', 'base_prefix', 'builtin_module_names', 'byteorder',
 'call_tracing', 'callstats', 'copyright', 'displayhook',
 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix',
 'executable', 'exit', 'flags', 'float_info', 'float_repr_style',
 'getcheckinterval', 'getdefaultencoding', 'getdlopenflags',
 'getfilesystemencoding', 'getobjects', 'getprofile', 'getrecursionlimit',
 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettotalrefcount',
 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info',
 'intern', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path',
 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1',
 'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit',
 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout',
 'thread_info', 'version', 'version_info', 'warnoptions']

若是沒有給定參數,那麼 dir() 函數會羅列出當前定義的全部名稱:

>>> a = [1, 2, 3, 4, 5]
>>> import fibo
>>> fib = fibo.fib
>>> dir() # 獲得一個當前模塊中定義的屬性列表
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys']
>>> a = 5 # 創建一個新的變量 'a'
>>> dir()
['__builtins__', '__doc__', '__name__', 'a', 'sys']
>>>
>>> del a # 刪除變量名a
>>>
>>> dir()
['__builtins__', '__doc__', '__name__', 'sys']
>>>

絕對導入

關於這句from __future__ import absolute_import的做用:
直觀地看就是說」加入絕對引入這個新特性」。說到絕對引入,固然就會想到相對引入。那麼什麼是相對引入呢?好比說,你的包結構是這樣的:
pkg/
pkg/init.py
pkg/main.py
pkg/string.py

若是你在main.py中寫import string,那麼在Python 2.4或以前, Python會先查找當前目錄下有沒有string.py, 若找到了,則引入該模塊,而後你在main.py中能夠直接用string了。若是你是真的想用同目錄下的string.py那就好,可是若是你是想用系統自帶的標準string.py呢?那其實沒有什麼好的簡潔的方式能夠忽略掉同目錄的string.py而引入系統自帶的標準string.py。這時候你就須要from __future__ import absolute_import了。這樣,你就能夠用import string來引入系統的標準string.py, 而用from pkg import string來引入當前目錄下的string.py了

相對導入(各類坑)

相對導入

若是在結構中包是一個子包(好比這個例子中對於包sound來講),而你又想導入兄弟包(同級別的包)你就得使用導入絕對的路徑來導入。好比,若是模塊sound.filters.vocoder 要使用包sound.effects中的模塊echo,你就要寫成 from sound.effects import echo。from後面一個點表明同級目錄,兩個點表明上級目錄。

from . import echo
from .. import formats
from ..filters import equalizer

下面是相對導入須要注意的幾點

1.若是要將一個文件夾目錄當作package的話,必需要在該目錄下加一個__init__.py的文件(注意是兩個下劃線連在一塊兒__),不然將沒法做爲一個package;

2.執行模塊是程序入口模塊時(__name__ == '__main__'),不能使用相對導入。由於在程序入口模塊執行時,__name__這個變量值是」__main__」。而相對引用符號」.」的就是對應__name__這個變量。當這個模塊是在別的模塊中被導入使用,此時的」.」就是原模塊的文件名。在main函數中執行時,此時」.」變成了」__main__」。(會出現No module named '__main__.xxxx'; '__main__' is not a package

3.雖然第二點說了,程序入口模塊不能使用相對導入,那麼在程序入口模塊中隨意調用別的模塊進行相對導入不就好了?emmmm太天真了,與程序入口模塊在同一目錄下的模塊也沒法使用相對導入,不然會出現(ImportError: attempted relative import with no known parent package)解決模塊的算法是基於__name____package__變量的值。大部分時候,這些變量不包含任何包信息 —- 好比:__name__ = __main____package__ = None(第三點屬於None) 時,python解釋器不知道模塊所屬的包。在這種狀況下,相對引用會認爲這個模塊就是頂級模塊,而無論模塊在文件系統上的實際位置。

4.執行腳本時,會把當前項目目錄和執行文件的目錄加入到sys.path中

下面給出報錯示例:首先請看目錄結構(請自行忽略8級漢語拼音)

在abc三個文件中互相import a這種導入時一點問題都沒有,但一旦換成相對導入

1.例如在a.py中寫

from . import b
from .b import funb
from .daorubao import d

這三種寫法都會報錯,想一想屬於哪一種錯?答案是相對導入注意的第二點,執行模塊是程序入口模塊時不能使用相對導入

修改方法是把後兩行的.去掉便可

2.在c.py中寫

from .a import funa
from .daorubao import d

而後在b.py中寫

import c

而後運行b.py,這時,c.py中的兩行語句不管哪一行都會報錯,錯誤參考相對導入注意第三點

解決方法依然是吧c.py中的兩行語句的.去掉便可

3.在e.py中寫

from . import f
from .f import *

而後在a.py中寫

from daorubao import e

這個時候會出現。。emmm不會出現錯誤,由於這時e中的相對導入可使用,不是入口程序,也不和入口程序在同一目錄下

 __all__屬性在模塊與包中的應用

 在包中

 在包中__all__的使用在上文有提到,總結來講就是使用from 包名 import *時,會先找__init__.py中叫__all__ 的列表變量

  • 當__all__未定義時,from 包名 import *這種方式會執行__init__.py,若__init__.py文件中導入了模塊,則會把模塊導入
  • 當__all__定義了可是爲空時,from 包名 import *這種方式也會執行__init__.py,若__init__.py文件中導入了模塊,會對模塊進行解釋(至關於運行),可是沒法使用該模塊。由於__all__定義了,則只會導入__all__裏的模塊,可是其爲空,因此值會執行__init__.py,對__init__.py文件導入的模塊進行解釋,卻不能使用這些模塊。
  • 當__all__定義了且不爲空時,from 包名 import *這種方式執行__init__.py,對__init__.py文件中導入的模塊進行解釋,可是隻能使用__all__中的模塊

在模塊中

用於模塊導入時限制,也是隻有使用
from 模塊名 import *時,被導入模塊若定義了__all__屬性,則只有__all__內指定的屬性、方法、類可被導入。若沒定義,則導入模塊內的全部公有屬性,方法和類 。

詳見https://blog.csdn.net/sxingming/article/details/52903377

特別注意一下這種

def func():  # 模塊中的public方法
    print('func() is called!')

def _func():  # 模塊中的protected方法
    print('_func() is called!')

def __func():  # 模塊中的private方法
    print('__func() is called!')
from kk import * #這種方式只能導入公有的屬性,方法或類【沒法導入以單下劃線開頭(protected)或以雙下劃線開頭(private)的屬性,方法或類】
可是若是帶下劃線的方法屬性被寫進__all__或者直接import a._func也是能夠用的
from kk import func,_func,__func #能夠經過這種方式導入public,protected,private
import kk #也能夠經過這種方式導入public,protected,private

importlib

Python將importlib做爲標準庫提供。它旨在提供Pythonimport語法和(__import__()函數)的實現。另外,importlib提供了開發者能夠建立本身的對象(即importer)來處理導入過程。

那麼imp呢?還有一個imp模塊提供了import語句接口,不過這個模塊在Python3.4已經deprecated了。建議使用importlib來處理。

下面是簡單使用:

import importlib

# 根據字符串導入模塊
# 一般用來導入包下面的模塊
o = importlib.import_module("xx.oo")
s2 = "Person"

# 由字符串找函數、方法、類  利用 反射
the_class = getattr(o, "Person")
p2 = the_class("小黑")
p2.dream()

動態導入

importlib模塊支持傳遞字符串來導入模塊。咱們先來建立一些簡單模塊一遍演示。咱們在模塊裏提供了相同接口,經過打印它們自身名字來區分。咱們分別建立了foo.pybar.py,代碼以下:

def main():
    print(__name__)

如今咱們盡須要使用importlib導入它們。咱們來看看代碼是如何實現的,確保該代碼在剛纔建立的兩個文件的相同目錄下。

#importer
import importlib def dynamic_import(module): return importlib.import_module(module) if __name__ == "__main__": module = dynamic_import('foo') module.main() module2 = dynamic_import('bar') module2.main() 

這裏咱們導入importlib模塊,並建立了一個很是簡單的函數dynamic_import。這個函數直接就調用了importlib的import_module方法,並將要導入的模塊字符串傳遞做爲參數,最後返回其結果。而後在主入口中咱們分別調用了各自的main方法,將打印出各自的name.

$ python3 importer.py 
foo
bar

也許你不多會代碼這麼作,不過在你須要試用字符串做爲導入路徑的話,那麼importlib就有用途了。

模塊導入檢查

Python有個衆所周知的代碼風格EAFP: Easier to ask forgiveness than permission.它所表明的意思就是老是先確保事物存在(例如字典中的鍵)以及在犯錯時捕獲。若是咱們在導入前想檢查是否這個模塊存在而不是靠猜。 使用mportlib就能實現。

import importlib.util def check_module(module_name): """  Checks if module can be imported without actually  importing it  """ module_spec = importlib.util.find_spec(module_name) if module_spec is None: print("Module: {} not found".format(module_name)) return None else: print("Module: {} can be imported".format(module_name)) return module_spec def import_module_from_spec(module_spec): """  Import the module via the passed in module specification  Returns the newly imported module  """ module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) return module if __name__ == '__main__': module_spec = check_module('fake_module') module_spec = check_module('collections') if module_spec: module = import_module_from_spec(module_spec) print(dir(module)) 

這裏我導入了importlib的子模塊util。check_module裏面調用find_spec方法, 傳遞該模塊字符串做爲參數。當咱們分別傳入了一個不存在和存在的Python模塊。你能夠看到當你傳入不存在的模塊時,find_spec函數將返回 None,在咱們代碼裏就會打印提示。若是存在咱們將返回模塊的specification。

咱們能夠經過該模塊的specification來實際導入該模塊。或者你直接將字符串做爲參數調用import_module函數。不過我這裏也學習如何試用模塊specification方式導入。看看import_module_from_spec函數。它接受check_module提供的模塊specification做爲參數。而後咱們將它傳遞給了module_from_spec函數,它將返回導入模塊。Python文檔推薦導入後而後執行模塊,因此接下來咱們試用exec_module函數執行。最後咱們使用dir來確保獲得預期模塊。

從源代碼導入

importlib的子模塊有個很好用的技巧我想提提。你可使用util經過模塊的名字和路徑來導入模塊。

import importlib.util def import_source(module_name): module_file_path = module_name.__file__ module_name = module_name.__name__ module_spec = importlib.util.spec_from_file_location( module_name, module_file_path ) module = importlib.util.module_from_spec(module_spec) module_spec.loader.exec_module(module) print(dir((module))) msg = 'The {module_name} module has the following methods {methods}' print(msg.format(module_name=module_name, methods=dir(module))) if __name__ == "__main__": import logging import_source(logging) 

在上面的代碼中,咱們實際導入logging模塊,並將模塊傳遞給了import_source函數。這樣咱們就能夠經過導入的模塊獲取到實際的路 徑和名字。而後咱們將信息傳遞給sec_from_file_location函數,它將返回模塊的specification。也了這個咱們就能夠在前 面那樣直接經過importlib導入了。

總結

目前,你知道如何在代碼中使用importlib和import鉤子。這個模塊內容很是多,若是你想自定義importer或者loader,那麼你能夠經過官方文檔或者源代碼瞭解更多。

相關文章
相關標籤/搜索