Python--模塊與包


模塊

1、什么是模塊?

一個模塊就是一個Python文件,文件名就是模塊名字加上.py后綴。因此模塊名稱也必須符合變量名的命名規范。

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

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

  3 包好一組模塊的包

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

2、為什么要使用模塊?

  如果你退出python解釋器然后重新進入,那么你之前定義的函數或者變量都將丟失,因此我們通常將程序寫到文件中以便永久保存下來,需要時就通過python test.py方式去執行,此時test.py被稱為腳本script。

    隨着程序的發展,功能越來越多,為了方便管理,我們通常將程序分成一個個的文件,這樣做程序的結構更清晰,方便管理。這時我們不僅僅可以把這些文件當做腳本去執行,還可以把他們當做模塊來導入到其他的模塊中,實現了功能的重復利用,

3、如何使用模塊?

  • 方式一: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模塊的時候發生了下面的幾步:

  1、先尋找模塊

  2、如果找到了,就在內存中開辟一塊空間,從上至下執行這個模塊

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

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

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

給導入的模塊取別名,用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__。

  1、已腳本的方式執行:cmd中“python xxx.py” 或者pycharm等IDE中執行

    __name__ = '__main__'

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

    __name__ = 模塊名

然而,當你有一個py文件既可以作為腳本執行,又可以作為模塊提供給其他模塊引用時,這時作為模塊需要導入時而不顯示多余的打印邏輯/函數調用,所以這些邏輯可以放在“if __name__ = '__main__': xxx” 代碼塊中。

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

from ... import ...

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

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

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

  1、尋找模塊

  2、如果找到模塊,在內存中開辟一塊內存空間,從上至下執行模塊

  3、把模塊中的對應關系全部都保存到新開辟的內存空間中

  4、建立一個變量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,導入包本質就是在導入文件

  注意:

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

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

包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
 1、__file__ 可以得到當前文件的絕對路徑,E:/練習/project/bin/start.py
 2、__file__.split('/') 將當前文件的絕對路徑進行處理,按照'/'分隔得到:['E:', '練習', 'project', 'bin', 'start.py']
 3、'/'.join(ret[:-2]) 因為我們只需要拿到project項目的動態路徑,所以進行切割,在jojn得到: E:/練習/project
 4、sys.path.append(base_path) 再將得到的路徑添加到sys.path中
 5、from core import main 因為我們拿到的是project目錄,所以導入是從當前路徑的core包導入main模塊
 6、main.func() 最后再是模塊名.方法。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM