首頁 > 軟體

Python實現建立模組的方法詳解

2022-07-20 18:01:12

楔子

匯入一個模組,我們一般都會使用 import 關鍵字,但有些場景下 import 難以滿足我們的需要。所以除了 import 之外還有很多其它匯入模組的方式,下面就來介紹一下。

__import__

這是一個內建函數,直譯器在 import 的時候,實際上就執行了這個函數。

# import os 等價於如下方式
os = __import__("os")
print(os)  # <module 'os' from 'C:\python38\lib\os.py'>

# 但是這種方式不能多級匯入
path = __import__("os.path")
print(path)  # <module 'os' from 'C:\python38\lib\os.py'>
# 可以看到,匯入的仍是 os,而不是 os.path

# 如果想匯入子模組,需要一個引數 fromlist
# 我們給它傳一個非空列表即可
path = __import__("os.path", fromlist=[""])
print(path)  # <module 'ntpath' from 'C:\python38\lib\ntpath.py'>

但是官方不建議使用這個函數,因為它是專門給直譯器用的,我們可以使用一個模組。

import importlib

os = importlib.import_module("os")
print(os)  # <module 'os' from 'C:\python38\lib\os.py'>

# 可以多級匯入
path = importlib.import_module("os.path")
print(path)  # <module 'ntpath' from 'C:\python38\lib\ntpath.py'>

所以當匯入的模組名以字串的形式存在時,或者模組名不符合規範時,就可以使用這種方式。

importlib.machinery

importlib.machinery 裡面提供了三種 Loader,可以讓我們以開啟檔案的方式匯入一個模組。

from importlib.machinery import (
    SourceFileLoader,  # 匯入原始檔
    SourcelessFileLoader,  # 匯入 pyc 檔案
    ExtensionFileLoader  # 匯入擴充套件檔案
)

# 引數一:給模組起個名字
# 引數二:檔案路徑
os = SourceFileLoader(
    "我是 os 模組",
    r"C:python38libos.py"
).load_module()
print(os)
"""
<module '我是 os 模組' from 'C:\python38\lib\os.py'>
"""
print(os.path.join("video", "overwatch", "hanzo.mp4"))
"""
videooverwatchhanzo.mp4
"""

# 我們看到結果一切正常,但有一點需要注意
# 如果是匯入包的話,那麼要匯入包裡面的 __init__.py 檔案
pd = SourceFileLoader(
    "我是 pandas 模組",
    r"C:python38libsite-packagespandas__init__.py"
).load_module()
print(pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6]}))
"""
   a  b
0  1  4
1  2  5
2  3  6
"""

# 如果只寫到 pandas,那麼會丟擲 PermissionError
# 因為我們不能把目錄當成檔案來讀取
# 至於 import 一個包,本質上也是載入包內部的 __init__.py 
# 但這裡需要我們顯式地加上 __init__.py

同理載入 pyc 和 pyd 也是類似的,但需要注意的是,載入普通檔案和 pyc 檔案時,我們可以隨便起名字,也就是第一個引數任意。但對於 pyd 檔案,第一個引數必須和 pyd 檔案的名字保持一致。

通過 module 類建立模組

Python 一切皆物件,模組自然也不例外。既然是物件,那麼必然就會有相應的類來範例化它。

import os
import hashlib
import numpy

# os.__class__ 等價於 type(os)
print(os.__class__)  # <class 'module'>
print(hashlib.__class__)  # <class 'module'>
print(numpy.__class__)  # <class 'module'>

在 Python 裡面,我們一般會把單獨的可匯入檔案稱之為模組,把包含多個模組的目錄稱之為。通過模組和包,我們可以對專案進行功能上的劃分,分門別類地進行組織。

但不管是模組、還是包,它們都是 module 這個類的範例物件,列印結果也能說明這一點。所以從直譯器的角度來看的話,模組和包區分的並沒有那麼明顯,直接把包看做是包內部的 __init__.py 即可。

既然模組的型別是 class module,那麼我們是不是也可以通過呼叫型別物件的方式建立呢?顯然是可以的,但是 module 這個類直譯器沒有暴露給我們,直接用的話會提示變數 module 未定義。所以只能先隨便匯入一個模組,然後通過 type 函數或者 __class__ 屬性獲取。

# 不過 types 模組內部已經幫我們做好了
# ModuleType = type(sys)
from types import ModuleType

print(ModuleType)  # <class 'module'>

# 類物件有了,下面就可以建立了
# module 類接收兩個引數
# 引數一:模組的名字,必須傳遞
# 引數二:模組的 doc,不傳預設為 None
satori = ModuleType("古明地覺", "模組的名字是一個女孩,她來自地靈殿")
print(satori)  # <module '古明地覺'>
print(satori.__doc__)  # 模組的名字是一個女孩,她來自地靈殿


# 但此時模組裡面是沒啥東西的,我們加一些屬性吧
# 操作模組本質上是在操作它的屬性字典
code = """
age = 16

def foo():
    return "^_^"
"""
# 執行 code,結果會體現在 satori 的屬性字典中
exec(code, satori.__dict__)
print(satori.age)  # 16
print(satori.foo())  # ^_^

需要注意的是裡面 exec 函數,它會把字串當成程式碼來執行,所以這就要求字串的來源必須是可靠的,我們能夠確保不會出現惡意內容。而如果是使用者傳遞的字串,那麼絕不能用 exec 來執行,當然 eval 也是同理。

然後是 exec 的第二個引數,表示執行時的名稱空間,預設是全域性名稱空間。所以當不指定第二個引數時,exec(code) 相當於建立了兩個全域性變數:age 和 foo。

code = """
age = 16

def foo():
    return "^_^"
"""

exec(code)
print(age)  # 16
print(foo())  # ^_^

但是我們在執行的時候,將它換成了 satori.__dict__,所以結果相當於給模組新增了兩個變數,或者說屬性。

將一個類的範例變成一個模組

如果想將一個類的範例變成模組,那麼這個類應該繼承 ModuleType。

import sys
from types import ModuleType


class A(ModuleType):

    def __init__(self, module_name):
        super().__init__(module_name)

    def __getattr__(self, item):
        return f"不存在的屬性: {item}"

    def __setattr__(self, key, value):
        self.__dict__[key] = value

    def __str__(self):
        return f"<module '{self.__name__}' from '我來自於虛無'>"


a = A("我是 A")
print(a)  # <module '我是 A' from '我來自於虛無'>
print(a.__name__)  # 我是 A
print(a.xx)  # 不存在的屬性: xx
a.xx = "xx"
print(a.xx)  # xx

# 加入到 sys.modules 中
sys.modules["嘿嘿"] = a
import 嘿嘿
print(嘿嘿.xx)  # xx
print(嘿嘿.yy)  # 不存在的屬性: yy

是不是很好玩呢?

小結

以上就是載入模組的幾種方式,主要用途如下:

  • 匯入一個在 sys.path 中的模組,並且模組名已知,那麼直接使用 import 關鍵字即可;
  • 匯入一個在 sys.path 中的模組,但模組名是執行時的一個字串,那麼使用 importlib 模組的 import_module 函數;
  • 匯入一個不在 sys.path 中的模組,使用 importlib.machinery 的各種 Loader,只要把模組的路徑傳進去即可。當然啦,位於 sys.path 中的模組也可以使用該方法,但顯然此時使用前兩種更為方便;
  • 直接建立一個模組,通過繼承 module 類來實現,並且還可以加入到 sys.modules 中。Python 有一個第三方模組叫 sh,顧名思義是用來執行 Linux Shell 命令的,它內部就使用了繼承 module 類來建立模組的這種方式。但是要知道 module 這個類直譯器沒有暴露給我們,我們需要通過 type(模組) 或者 模組.__class__ 的方式獲取;

到此這篇關於Python實現建立模組的方法詳解的文章就介紹到這了,更多相關Python建立模組內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com