问题
虽然我经常会遇到需要写python脚本的情况,但是大多是在某个环境中,比如Houdini或者UE4等,所写的大多是一小段逻辑。因此,对于那些牵扯到多个python文件的较大型的python项目,我还没有经验。这导致目前我对python里“模块”这个概念有很多不了解之处。
目前,我所最关注的问题是——当我在Houdini或者UE4中运行Python时:
- 我可以使用哪些模块?
- 模块都来自于何处?
- 我怎样补充新的模块?
我想这些问题并不复杂,不过我还是准备结合文档和实践尝试搞明白这些问题。
基础概念
我从《5. 导入系统 — Python 3.9.0 文档》中获得了关于“模块”的一些概念:
- 模块(module):是 Python 代码的一种组织单位。各模块具有独立的命名空间,可包含任意 Python 对象。模块可通过导入操作被加载。
import
语句是发起调用导入机制的最常用方式,但不是唯一的方式- Python 只有一种模块对象类型,所有模块都属于该类型,无论模块是用 Python、C 还是别的语言实现。 为了帮助组织模块并提供名称层次结构,Python 还引入了包的概念。
- 包(package):一种可包含子模块或递归地包含子包的模块。从技术上说,包是带有
__path__
属性的模块。 import
语句结合了两个操作:它先搜索指定名称的模块,然后将搜索结果绑定到当前作用域中的名称。- 你可以把包看成是文件系统中的目录,并把模块看成是目录中的文件,但请不要对这个类似做过于字面的理解,因为包和模块不是必须来自于文件系统。
模块的搜索路径
官方文档里有对模块的搜索机制进行描述,但感觉描述比较复杂,我没能完全理解。。。
在《Python 模块 | 菜鸟教程》中,有一个对搜索路径的描述:
使用sys.path
可以看到所有会搜索的路径,并且其中有一定的优先级:
- 当前目录
- shell 变量 PYTHONPATH 下的每个目录。
- 默认路径。UNIX下,默认路径一般为/usr/local/lib/python/。
我尝试在我所感兴趣的三个环境中使用sys.path
看结果是什么:
直接启动下载的官方的python:
Houdini里:
UE4里:
how to
1. 如何确定一个模块是否加载了?
以“xml”这个模块为例,尝试将这个模块print
:
print(xml)
如何没有加载,则会显示错误:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'xml' is not defined
如果已加载,则会显示它是个模块,并显示来源:
<module 'xml' from 'C:\\Users\\admin\\AppData\\Local\\Programs\\Python\\Python38\\lib\\xml\\__init__.py'>
2. 如何判断一个模块是否能找到
简单来说,只要import
命令失败,就说明找不到。
>>> import testtest
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'testtest'
而为何找不到,就需要看看模块文件是否有在上一部分所说的“搜索路径”中了。
3. 如何知道模块的来源
依旧是print
模块的做法。
“xml”模块看来是来自于一个包:
>>> print(xml)
<module 'xml' from 'C:\\Users\\admin\\AppData\\Local\\Programs\\Python\\Python38\\lib\\xml\\__init__.py'>
“inlinecpp”模块看来是来自于一个.pyc
文件:
>>> print(inlinecpp)
<module 'inlinecpp' from 'C:/PROGRA~1/SIDEEF~1/HOUDIN~1.348/houdini/python2.7libs\in
linecpp.pyc'>
“math”模块看来并非来自于文件:
>>> print(math)
<module 'math' (built-in)>
4. 如何添加新的模块
较为直白的是直接将模块文件放到“搜索路径”中。
我的yaksue.py
:
def yaksuefunc():
print("hello yaksue")
在之前已经知道了在Houdini中运行python时会搜索到的路径,因此我将yaksue.py
放入其中一个目录。
便可以成功在Houdini的Python环境中使用了:
如果想使用自己指定的路径,也可以修改sys.path
的值,它是可写的。
例如,将文件放入“D:/Temp”文件夹中,然后将此路径加入sys.path
:
sys.path.append("D:/Temp")
即可在使用自己路径中的模块了
(不用担心会污染“sys.path”,经测试这个修改sys.path的操作是一次性的)
UE4的“unreal”模块
如果在UE4中使用print(unreal)
,将会得到
<module 'unreal' (built-in)>
此模块并非源于某个文件。
查看UE4的Python插件代码会发现,在“unreal”这个模块是由C++代码负责添加的:
PyUnrealModule = FPyObjectPtr::NewReference(PyImport_AddModule("unreal"));
关于UE4如何向其中添加具体的内容,还有待研究,我想一定和UE4自己的反射机制离不开。
Houdini的“hou”模块
如果在Houdini中使用print(hou)
,将会得到:
<module 'hou' from 'C:/PROGRA~1/SIDEEF~1/HOUDIN~1.348/houdini/python2.7libs\hou.pyc'>
会发现它是由一个编译好的python文件提供。它是编译好的二进制,没法阅读,但是在同级目录可以找到一个hou.py
,我想应该是由它编译的。
但在hou.py
中并没有实际函数的实现(或者说实际实现被注释掉了),最后实际是调用了_hou
模块的函数。
在同级目录发现另一个文件_hou.pyd
。
查资料发现.pyd
是非python语言编译好的模块。
关于如何生成.pyd
文件还值得研究。