Python--模塊與包

模塊

一、什麼是模塊?python

一個模塊就是一個Python文件,文件名就是模塊名字加上.py後綴。所以模塊名稱也必須符合變量名的命名規範。mysql

  1 使用python編寫的代碼(.py文件)sql

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

  3 包好一組模塊的包json

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

二、爲何要使用模塊?app

  若是你退出python解釋器而後從新進入,那麼你以前定義的函數或者變量都將丟失,所以咱們一般將程序寫到文件中以便永久保存下來,須要時就經過python test.py方式去執行,此時test.py被稱爲腳本script。ide

    隨着程序的發展,功能愈來愈多,爲了方便管理,咱們一般將程序分紅一個個的文件,這樣作程序的結構更清晰,方便管理。這時咱們不只僅能夠把這些文件當作腳本去執行,還能夠把他們當作模塊來導入到其餘的模塊中,實現了功能的重複利用,函數

三、如何使用模塊?測試

  • 方式一:import
  • 方式二:from ... import ...

import

首先,自定義一個模塊my_module.py,文件名my_module.py,模塊名my_module

name = "我是自定義模塊的內容..."


def func():
    print("my_module: ", name)

print("模塊中打印的內容...")
my_module

在import一個模塊的過程當中,發生了哪些事情?

# 用import導入my_module模塊
import my_module
>>>
模塊中打印的內容... # 怎麼回事,居然執行了my_module模塊中的print語句

import my_module
import my_module
import my_module
import my_module
import my_module
>>>
模塊中打印的內容... # 只打印一次

從上面的結果能夠看出,import一個模塊的時候至關於執行了這個模塊,並且一個模塊是不會重複被導入的,只會導入一次(python解釋器第一次就把模塊名加載到內存中,以後的import都只是在對應的內存空間中尋找。)成功導入一個模塊後,被導入模塊與文本之間的命名空間的問題,就成爲接下來要搞清楚的概念了。

被導入模塊與本文件之間命名空間的關係?

假設當前文件也有一個變量爲: name = 'local file', 也有一個同名的func方法。

# 本地文件
name = "local file"
def func():
    print(name)
    
# 本地文件有跟被導入模塊同名的變量和函數,究竟用到的是哪一個呢?
import my_module
print(my_module.name)   # 根據結果能夠看出,引用的是模塊裏面的name
my_module.func()        # 執行的是模塊裏面的func()函數
>>>
模塊中打印的內容...
我是自定義模塊的內容...
my_module:  我是自定義模塊的內容...

print(name)             # 使用的是本地的name變量
func()                  # 使用的是本地的func函數
>>>
local file
local file

在import模塊的時候發生了下面的幾步:

  一、先尋找模塊

  二、若是找到了,就在內存中開闢一塊空間,從上至下執行這個模塊

  三、把這個模塊中用到的對象都收錄到新開闢的內存空間中

  四、給這個內存空間建立一個變量指向這個空間,用來引用其內容。

  總之,模塊與文件之間的內存空間始終是隔離的

給導入的模塊取別名,用as關鍵字

若是導入的模塊名太長很差記,那麼能夠經過「import 模塊名 as  別名」的方式給模塊名取一個別名,但此時原來的模塊就再也不生效了(至關於建立了新的變量名指向模塊內存空間,斷掉原模塊名的引用)。

# 給my_module模塊取別名
import my_module as sm
print(sm.name)
>>>
我是自定義模塊的內容...
print(my_module.name)   # 取了別名後,原來的模塊名就不生效了
>>>
NameError: name 'my_module' is not defined

給模塊去別名,還可使代碼更加靈活,減小冗餘,經常使用在根據用戶輸入的不一樣,調用不一樣的模塊。

# 按照先前的作法,寫一個函數,根據用戶傳入的序列化模塊,使用對應的方法
def dump(method):
    if method == 'json':
        import json
        with open('dump.txt', 'wb') as f:
            json.dump('xxx', f)
    elif method == 'pickle':
        import pickle
        with open('dump.txt', 'wb') as f:
            pickle.dump('xxx', f)

# 上面的代碼冗餘度很高,若是簡化代碼?經過模塊取別名的方式,能夠減小冗餘
def dump(method):
    if method == 'json':
        import json as m
    elif method == 'pickle':
        import pickle as m
    with open('dump.txt', 'wb') as f:
        m.dump('dump.txt', f)

如何同時導入多個模塊?

方式一:每行導入一個模塊

import os
import sys
import time

方式二:一行導入多個模塊,模塊之間經過逗號「,」來分隔

import os, sys, my_module

可是,根據PEP8規範規定使用第一種方式,而且三種模塊有前後順序(內置>第三方>自定義)

# 根據PEP8規範
import os
import django
import my_module

模塊搜索路徑

經過sys內置模塊,咱們知道sys.path存儲了全部模塊的路徑,可是正常的sys.path的路徑中除了內置模塊,第三方模塊所在的路徑以外,只有一個路徑是永遠正確的,就是當前執行的文件所在目錄。一個模塊是否可以被導入,就取決於這個模塊所在的目錄是否在sys.path中。

python解釋器在啓動時會自動加載一些模塊,可使用sys.modules查看

在第一次導入某個模塊時(好比my_module),會先檢查該模塊是否已經被加載到內存中(當前執行文件的名稱空間對應的內存),若是有則直接引用

若是沒有,解釋器則會查找同名的內建模塊,若是尚未找到就從sys.path給出的目錄列表中依次尋找my_module.py文件。

因此總結模塊的查找順序是:內存中已經加載的模塊->內置模塊->sys.path路徑中包含的模塊

須要特別注意的是:咱們自定義的模塊名不該該與系統內置模塊重名。

模塊和腳本

運行一個py文件有兩種方式,可是這兩種執行方式之間有一個明顯的差異,就是__name__。

  一、已腳本的方式執行:cmd中「python xxx.py」 或者pycharm等IDE中執行

    __name__ = '__main__'

  二、導入模塊時執行:import模塊,會執行該模塊。

    __name__ = 模塊名

然而,當你有一個py文件既能夠做爲腳本執行,又能夠做爲模塊提供給其餘模塊引用時,這時做爲模塊須要導入時而不顯示多餘的打印邏輯/函數調用,因此這些邏輯能夠放在「if __name__ = '__main__': xxx」 代碼塊中。

這樣py文件做爲腳本執行的時候就可以打印出來,以模塊被導入時,便不會打印出來。

from ... import ...

from...import是另外一種導入模塊的形式,若是你不想每次調用模塊的對象都加上模塊名,就可使用這種方式。

在from ... import ... 的過程當中發生了什麼事兒?

from my_module import name, func
print(name)     # 此時引用模塊中的對象時,就不要再加上模塊名了。
func()

  一、尋找模塊

  二、若是找到模塊,在內存中開闢一塊內存空間,從上至下執行模塊

  三、把模塊中的對應關係所有都保存到新開闢的內存空間中

  四、創建一個變量xxx引用改模塊空間中對應的xxx, 若是沒有import進來的時候,就使用不了。

from ... import ... 方式取別名

與import方式一模一樣,經過"from 模塊名 import  對象名  as  別名"。

from my_module import name as n, func as f

from ... import *

import * 至關於把這個模塊中的全部名字都引入到當前文件中,可是若是你本身的py文件若是有重名的變量,那麼就會產生很差的影響,所以使用from...import *時須要謹慎,不建議使用。

* 與 __all__

__all__是與*配合使用的,在被導入模塊中增長一行__all__=['xxx','yyy'],就規定了使用import *是隻能導入在__all__中規定的屬性。

# 在my_module模塊中定義__all__
__all__ = ['name']
name = 'My module...'

def func():
    print("my_module: ", name)

# 在其餘文件中經過import *導入全部屬性
from my_module import *
print(name)
>>>
My module...

func()
>>>
NameError: name 'func' is not defined

 拓展知識點:

  (1)pyc文件與pyi文件 *

  pyi文件:跟.py同樣,僅僅做爲一個python文件的後綴名。

  pyc文件: python解釋器爲了提升加載模塊的速度,會在__pycache__目錄中生成模塊編譯好的字節碼文件,而且對比修改時間,只有模塊改變了,纔會再次編譯。pyc文件僅僅用於節省了啓動時間,可是並不能提升程序的執行效率。

  (2)模塊的導入和修改 *

  1.導入模塊後,模塊就已經被加載到內存中,此後計算對模塊進行改動,讀取的內容仍是內存中原來的結果

  2.若是想讓改動生效,能夠經過「from importlib import reload」, 須要'reload 模塊名'從新加載模塊,改動才生效。

  (3)模塊的循環使用 ****

  謹記模塊的導入必須是單鏈的,不能有循環引用,若是存在循環,那麼就是程序設計存在問題。

  (4)dir(模塊名) ***

  能夠得到該模塊中全部的名字,並且是字符串類型的,就能夠經過反射去執行它。

包是一種經過‘.模塊名’來組織python模塊名稱空間的方式。

(1)不管是import形式仍是from ... import 形式,凡是在導入語句中(而不是在使用時)遇到帶點的,都要第一時間提升警覺:這是關於包纔有的導入語法

(2)包是目錄級的(文件夾級),文件夾是用來組成py文件(包的本質就是一個包含__init__.py文件的目錄)

(3)import導入文件時,產生名稱空間中的名字來源與文件,import包,產生的名稱空間的名字一樣來源與文件,即包下的__init__.py,導入包本質就是在導入文件

  注意:

    一、在python3中,即便包下沒有__init__.py文件,import包仍然不會報錯,而在python2中,包下必定要有該文件,不然import包會報錯

    二、建立包的目的不是爲了運行,而是被導入使用,記住,包只有模塊的一種形式而已,包即模塊

包A和包B下有同名模塊也不會衝突,如A.a與B.a來自兩個命令空間

示例環境以下:

import os
os.makedirs('glance/api')
os.makedirs('glance/cmd')
os.makedirs('glance/db')
l = []
l.append(open('glance/__init__.py','w'))
l.append(open('glance/api/__init__.py','w'))
l.append(open('glance/api/policy.py','w'))
l.append(open('glance/api/versions.py','w'))
l.append(open('glance/cmd/__init__.py','w'))
l.append(open('glance/cmd/manage.py','w'))
l.append(open('glance/db/models.py','w'))
map(lambda f:f.close() ,l)
建立目錄代碼
glance/                   #Top-level package

├── __init__.py      #Initialize the glance package

├── api                  #Subpackage for api
│   ├── __init__.py
│   ├── policy.py
│   └── versions.py

├── cmd                #Subpackage for cmd
│   ├── __init__.py
│   └── manage.py

└── db                  #Subpackage for db
│   ├── __init__.py
│   └── models.py
目錄結構
#文件內容

#policy.py
def get():
    print('from policy.py')

#versions.py
def create_resource(conf):
    print('from version.py: ',conf)

#manage.py
def main():
    print('from manage.py')

#models.py
def register_models(engine):
    print('from models.py: ',engine)
文件內容

從包中導入模塊

(1)從包中導入模塊有兩種方式,可是不管哪一種,不管在什麼位置,都必須遵循一個原則:(凡是在導入時帶點的,點的左邊都必須是一個包),不然非法。

(2)對於導入後,在使用就沒有這種限制,點的左邊能夠是包,模塊,函數,類(它們均可以用點的方式調用本身的屬性)

(3)對比import item 和from item import name的應用場景:若是咱們想直接使用name那麼必須使用後者。

方式一:import

  例如: 包名1.包名2.包名3.模塊名

# 在與包glance同級別的文件中測試
import glance.db.models
glance.db.models.register_models('mysql') 
"""執行結果:from models.py mysql"""

方式二:from ... import ...

  例如:from 包名1.包名2 import 模塊名

       from 包名1.包名2.模塊名 import 變量名/函數名/變量名

  注意:須要注意的是from後import導入的模塊,必須是明確的一個不能帶點,不然會有語法錯誤,如:from a import b.c是錯誤語法

# 在與包glance同級別的文件中測試
from glance.db import models
models.register_models('mysql')
"""執行結果:from models.py mysql"""
from glance.cmd import manage
manage.main()
"""執行結果:from manage.py"""

直接導入包

若是是直接導入一個包,那麼至關於執行了這個包中的__init__文件

並不會幫你把這個包下面的其餘包以及py文件自動的導入到內存

若是你但願直接導入包以後,全部的這個包下面的其餘包以及py文件都能直接經過包來調用,那麼須要你本身處理__init__文件。

__init__.py文件

無論是哪一種方式,只要是第一次導入包或者是包的任何其餘部分,都會依次執行包下的__init__.py文件;這個文件能夠爲空,可是也能夠存放一些初始化包的代碼。

絕對導入和相對導入

咱們的最頂級包glance是寫給別人用的,而後在glance包內部也會有彼此之間互相導入的需求,這時候就有絕對導入和相對導入兩種方式:

絕對導入:以glance做爲起始

相對導入:用. 或者.. 的方式做爲起始(只能在一個包中使用,不能用於不一樣目錄內)

絕對導入和絕對導入示例:

絕對導入:
    既然導入包就是執行包下的__init__.py文件,那麼嘗試在啊glance的__init__.py文件中"import api",執行一下,貌似沒有報錯,在嘗試下在包外導入,狀況如何?
    在包外建立一個test.py文件,在裏面操做以下:
    import glance
    glance.api
    ModuleNotFoundError: No module named 'api'
    
緣由:爲何還會報錯?由於一個模塊能不能被導入就看在sys.path中有沒有路徑,在哪裏執行文件,sys.path永遠記錄該文件的目錄。
    (1)在glance的__init__.py文件中,sys.path的路徑是:
    'E:\\Python練習\\包\\glance'
    因此可以找到同級的api
    (2)可是在test文件中導入,此時sys.path的路徑是:
    'E:\\李彥傑\\Python練習\\包'
    因此找不到不一樣層級的api,因此就會報No module name 'api'
    
解決辦法一:
    使用絕對路徑(絕對路徑爲當前執行文件的目錄)
    (1)在glance包中的__init__.py中經過絕對路徑導入:
    "from glance import api"2)這樣在test文件中執行,就能找到同層級的glance,再去裏面找api
    (3)同理,若是想使用api包中的模塊,也要在api包中的__init__.py文件中導入"from glance.api import policy, veersions",
    (4)如今在test文件中調用glance下的api下的policy模塊就不會報錯:
        import glance
        glance.api.policy.get()
        glance.api.versions.create_resource('測試')
        執行結果:
            from policy.py
            from versions.py 測試
絕對導入的缺點:
若是之後包的路徑發生了轉移,包內的全部__init__.py文件中的絕對路徑都須要改變


解決辦法二:
    使用相對導入
        . 表示當前目錄
        .. 表示上一級目錄
    (1)在glance包中的__init__.py中經過相對路徑的形式導入:
     「from . import api」
    (2)同理在api包中的__init__.py中經過相對路徑的形式導入:
     「from . import policy,version」
    (3)一樣在test文件中調用glance下的api下的policy模塊就不會報錯:
        import glance
        glance.api.policy.get()
        glance.api.versions.create_resource('測試')
        執行結果:
            from policy.py
            from versions.py 測試

相對導入的優勢:
    包發生路徑轉移,其中的相對路徑都沒有改變,因此不用逐個逐個修改。

相對導入的缺點:
    但凡帶着相對導入的文件,只能當作模塊導入,不能做爲一個腳本單獨執行!!!

擴展知識:

  同級目錄下的包導入

  需求:如今須要在bin下面的start文件中導入core目錄下的main模塊;怎麼破?

project

├── bin                 #Subpackage for bin
    ├── __init__.py
    └── start.py

├── core                #Subpackage for core
    ├── __init__.py
    └── main.py
# main.py文件中的內容:
def func():
    print("In main")
(1)、在start中直接導入,由於路徑不對,因此直接報錯:
import main # 執行,報錯ModuleNotFoundError: No module named 'main'
(2)、由上面報錯咱們知道確定路徑不對,那麼咱們想到直接將core路徑加進去不就行了嗎?是的,這樣是可行的
import sys
path = 'E:\練習\包\core'   # 複製獲得core的絕對路徑
sys.path.append(path)     # 將core路徑添加
import main         # 再次導入便不會報錯
main.func()         # 執行結果:In main
(3)、上面的方法看似可行,可是仍是有一個問題,若是我將project打包發給別人,或者我換個環境運行呢?   那麼又得更改對應的path。不怎麼合理,那麼咱們看下面的方法:
import sys
print(__file__)
ret = __file__.split('/')
base_path = '/'.join(ret[:-2])

sys.path.append(base_path)

from core import main
main.func()     # In main
 一、__file__ 能夠獲得當前文件的絕對路徑,E:/練習/project/bin/start.py
 二、__file__.split('/') 將當前文件的絕對路徑進行處理,按照'/'分隔獲得:['E:', '練習', 'project', 'bin', 'start.py']
 三、'/'.join(ret[:-2]) 由於咱們只須要拿到project項目的動態路徑,因此進行切割,在jojn獲得: E:/練習/project
 四、sys.path.append(base_path) 再將獲得的路徑添加到sys.path中
 五、from core import main 由於咱們拿到的是project目錄,因此導入是從當前路徑的core包導入main模塊
 六、main.func() 最後再是模塊名.方法。
相關文章
相關標籤/搜索