Python实现创建模块的方法详解

楔子

导入一个模块,我们一般都会使用 import 关键字,但有些场景下 import 难以满足我们的需要。所以除了 import 之外还有很多其它导入模块的方式,下面就来介绍一下。

__import__

这是一个内置函数,解释器在 import 的时候,实际上就执行了这个函数。

# import os 等价于如下方式
os = __import__("os")
print(os)  # 

# 但是这种方式不能多级导入
path = __import__("os.path")
print(path)  # 
# 可以看到,导入的仍是 os,而不是 os.path

# 如果想导入子模块,需要一个参数 fromlist
# 我们给它传一个非空列表即可
path = __import__("os.path", fromlist=[""])
print(path)  # 

但是官方不建议使用这个函数,因为它是专门给解释器用的,我们可以使用一个模块。

import importlib

os = importlib.import_module("os")
print(os)  # 

# 可以多级导入
path = importlib.import_module("os.path")
print(path)  # 

所以当导入的模块名以字符串的形式存在时,或者模块名不符合规范时,就可以使用这种方式。

importlib.machinery

importlib.machinery 里面提供了三种 Loader,可以让我们以打开文件的方式导入一个模块。

from importlib.machinery import (
    SourceFileLoader,  # 导入源文件
    SourcelessFileLoader,  # 导入 pyc 文件
    ExtensionFileLoader  # 导入扩展文件
)

# 参数一:给模块起个名字
# 参数二:文件路径
os = SourceFileLoader(
    "我是 os 模块",
    r"C:\python38\lib\os.py"
).load_module()
print(os)
"""

"""
print(os.path.join("video", "overwatch", "hanzo.mp4"))
"""
video\overwatch\hanzo.mp4
"""

# 我们看到结果一切正常,但有一点需要注意
# 如果是导入包的话,那么要导入包里面的 __init__.py 文件
pd = SourceFileLoader(
    "我是 pandas 模块",
    r"C:\python38\lib\site-packages\pandas\__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__)  # 
print(hashlib.__class__)  # 
print(numpy.__class__)  # 

在 Python 里面,我们一般会把单独的可导入文件称之为模块,把包含多个模块的目录称之为。通过模块和包,我们可以对项目进行功能上的划分,分门别类地进行组织。

但不管是模块、还是包,它们都是 module 这个类的实例对象,打印结果也能说明这一点。所以从解释器的角度来看的话,模块和包区分的并没有那么明显,直接把包看做是包内部的 __init__.py 即可。

既然模块的类型是 class module,那么我们是不是也可以通过调用类型对象的方式创建呢?显然是可以的,但是 module 这个类解释器没有暴露给我们,直接用的话会提示变量 module 未定义。所以只能先随便导入一个模块,然后通过 type 函数或者 __class__ 属性获取。

# 不过 types 模块内部已经帮我们做好了
# ModuleType = type(sys)
from types import ModuleType

print(ModuleType)  # 

# 类对象有了,下面就可以创建了
# module 类接收两个参数
# 参数一:模块的名字,必须传递
# 参数二:模块的 doc,不传默认为 None
satori = ModuleType("古明地觉", "模块的名字是一个女孩,她来自地灵殿")
print(satori)  # 
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""


a = A("我是 A")
print(a)  # 
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创建模块内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

你可能感兴趣的:(Python实现创建模块的方法详解)