将Python文件连接在一起以简化导入



我有一个代码目录,如下所示:

root_dir
├── root.py
├── same_step_0.py
├── same_step_1.py
├── same_step_2.py
└── utils
├── __init__.py
├── file_one.py
└── file_two.py

root.py文件引用same_step文件中的函数。例如:

# In root.py
same_step_0.generated_function()

";步骤";文件引用了"utils"目录中的函数,只需简单的导入和调用即可,例如

from utils.file_one import function_one
function_one(val="somelongstring")

好的,现在我们来解决这个问题——我正在研究一个API,它有效地要求每个";步骤";成为一个单独的python文件。因此,我需要做的是,以某种方式,将每个步骤文件引入的所有函数附加到一个文件中,然后提交给服务。

理想情况下,我希望在对底层代码进行少量更改的情况下完成这项工作。因此,如果可能的话,在same_step_0中,我想将其保留为:

from utils.file_one import function_one
function_one(val="somelongstring")

并且不必将其更改为

# Note removing the 'utils' prefix
from file_one import function_one
function_one(val="somelongstring")

所以,问题是,有没有一种方法可以将所有依赖的文件或函数一起附加到一个文件中,使其模仿导入行为?或者至少函数调用行为?我思考过的想法:

  • 将所有子文件pickle成序列化格式,并在代码末尾插入
  • 所有python文件的原始附加-可能在内联模块内
  • 为函数删除所有文件并手动附加它们(但是,这不会保留名称空间(

我还没有深入研究这些-我很好奇这是否可能是

我可以进行大量的代码生成和包装,但我不希望触及内部代码(例如function_one调用(。

目标

创建一组最小的脚本,以及可以存储在Python可执行文件中的交换格式,以重新创建模块结构。

来源

好的,这里有一个简单的例子,它滥用types.ModuleTypesysglobals()来做到这一点:

import os
import sys
import types
class Module:
def __init__(self, name, source=None, path=None, modules=None):
self.name = name
self.source = source
self.path = path
self.modules = modules
def from_file(name, path):
return Module(name, open(path).read(), path)
def from_dir(name, path):
return Module(name, None, path, [])
def __repr__(self):
return f'Module(name={repr(self.name)}, source={repr(self.source)}, path={repr(self.path)}, modules={self.modules})'
def to_dict(self):
data = { 'name': self.name }
if self.source is not None:
data['source'] = self.source
if self.path is not None:
data['path'] = self.path
if self.modules is not None:
data['modules'] = [i.to_dict() for i in self.modules]
return data
@staticmethod
def from_dict(data):
modules = None
if 'modules' in data:
modules = [Module.from_dict(i) for i in data.pop('modules')]
return Module(**data, modules=modules)
def get_modname(parent, module):
if parent is None:
return module
if module == '__init__':
return parent
return f'{parent}.{module}'
def compile_module(module, parent=None):
modname = get_modname(parent, module.name)
mod = types.ModuleType(modname)
exec(module.source, mod.__dict__)
path = os.path.realpath(module.path)
mod.__path__ = os.path.dirname(path)
mod.__file__ = path
sys.modules[modname] = mod
globals()[modname] = mod
return mod
def compile_module_recursive(package, parent=None):
# Need to do this recursively.
mod = compile_module(package, parent)
if not package.modules:
return mod
for submodule in package.modules:
submod = compile_module_recursive(submodule, parent=get_modname(parent, package.name))
if not hasattr(mod, submodule.name):
setattr(mod, submodule.name, submod)
return mod
def read_module_recursive(directory, parent=None):
# The module order is first the:
#   1. All submodules
#   2. Then definitions inside
# Then, need to define `__name__`, `__path__` and `__file__`.
cwd = os.getcwd()
realpath = os.path.realpath(directory)
parent_dir = os.path.dirname(realpath)
base_dir = os.path.basename(realpath)
if parent is None:
parent = Module.from_dir(base_dir, realpath)
os.chdir(realpath)
for entry in os.listdir(realpath):
path = os.path.join(realpath, entry)
if os.path.isfile(path):
name, ext = os.path.splitext(entry)
if not ext == '.py':
continue
if name == '__init__':
parent.path = path
parent.source = open(path).read()
else:
parent.modules.append(Module.from_file(name, path))
elif os.path.isdir(path):
if entry == '__pycache__':
continue
path = os.path.join(realpath, entry)
# Must have processed __init__.py
if not os.path.isfile(f'{path}/__init__.py'):
continue
module = Module.from_dir(entry, path)
parent.modules.append(module)
read_module_recursive(entry, module)
os.chdir(cwd)
return parent

# SAMPLE USE
# ----------
#   module = read_module_recursive('mylib')
#   data = module.to_dict()  # can store as a Python dict.
#   module = Module.from_dict(data)
#   compile_module_recursive(module)

它的工作原理

基本上,它从所有模块中递归地读取所有.py文件。它目前不支持扩展类型,只支持纯Python文件。然后,它为整个模块树创建一个中间的树状类型。它提供了与dict之间的简单序列化,因此将其复制并粘贴到可执行文件中要容易得多。

示例使用

假设我有以下目录结构:

mylib/
__init__.py
a.py
b.py
c/
__init__.py
d.py

它正确地尊重导入顺序,以及变量赋值如何覆盖导入或子模块。请注意,它一次递归地导入所有内容,这与传统的导入结构不同。

mylib/init.py

a = 'e'

mylib/a.py

def afunc(a):
return str(a)
class AClass:
def x(self):
return 1

mylib/b.py

def bfunc(b):
return repr(b)
class BClass:
def x(self):
return 1

mylib/c/初始化.py

mylib/c/d.py

def dfunc(d):
return str(d)
class DClass:
def x(self):
return 1

示例

>>> module = read_module_recursive('mylib')
>>> data = module.to_dict()  # can store as a Python dict.
>>> module = Module.from_dict(data)
>>> compile_module_recursive(module)
>>> mylib
<module 'mylib' from 'C:\Users\user\OneDrive\Desktop\lib\mylib\__init__.py'>
>>> mylib.a
'e'
>>> from mylib.a import afunc # still works
>>> afunc(54)
'54'
>>> import mylib # works because `'mylib'` is in `sys.modules`.

恭喜:这是一个破解,但它很有效,而且效果很好,而且它使用Python自己的打包系统来做所有事情,所以它比复制和粘贴更有弹性。编译后,它还尊重导入(由于将它们添加到sys.modules中(。它还尊重__doc__和其他属性。

许可证

公共域或未授权。使用您认为合适的。归因是很好的,但不是必须的。

最新更新