虽然我经常会遇到需要写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里:
以“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'>简单来说,只要import命令失败,就说明找不到。
>>> import testtest Traceback (most recent call last): File "<stdin>", line 1, in <module> ModuleNotFoundError: No module named 'testtest'而为何找不到,就需要看看模块文件是否有在上一部分所说的“搜索路径”中了。
依旧是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)>较为直白的是直接将模块文件放到“搜索路径”中。 我的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中使用print(unreal),将会得到
<module 'unreal' (built-in)>此模块并非源于某个文件。 查看UE4的Python插件代码会发现,在“unreal”这个模块是由C++代码负责添加的:
PyUnrealModule = FPyObjectPtr::NewReference(PyImport_AddModule("unreal"));关于UE4如何向其中添加具体的内容,还有待研究,我想一定和UE4自己的反射机制离不开。
如果在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文件还值得研究。