Python 如何在运行时将模块中的函数添加到类中,同时保留包层次结构?

Python 如何在运行时将模块中的函数添加到类中,同时保留包层次结构?,python,python-packaging,Python,Python Packaging,假设我有一个Python 3包,其结构如下: 。 └── MyFun软件包/ ├── __初始值 ├── helloworld.py └── 世界/ ├── __初始值 ├── 世界1.py └── 世界2.py helloworld.py定义了以下类: class World(object): def __init__(self, name): self.name = name worlds子包中的每个模块定义了不同的功能。例如,world1.py可能包含: def

假设我有一个Python 3包,其结构如下:

。
└── MyFun软件包/
├── __初始值
├── helloworld.py
└── 世界/
├── __初始值
├── 世界1.py
└── 世界2.py
helloworld.py
定义了以下类:

class World(object):
    def __init__(self, name):
        self.name = name
worlds
子包中的每个模块定义了不同的功能。例如,
world1.py
可能包含:

def frobulate(self):
   return f'{self.name} has been frobulated' 
我的最终目标是在运行时将
worlds
子包中包含的每个模块中的每个函数都添加到
World
类中,这样当我将另一个模块添加到
worlds/
(例如
world3.py
)时,就不需要手动更改任何内容。但是,我还希望保留包层次结构,以便包外部的脚本可以执行以下操作:

from MyFunPackage.helloworld import World
aWorld = World('a')
print(aWorld.world1.frobulate()) # 'a has been frobulated'
稍后,如果我将
world3.py
添加到
World
子包中,我应该能够在不修改
World
类的情况下将以下内容添加到外部脚本中:

print(aWorld.world3.wormhole(2)) # 'a has transited wormhole #2 to world3'
我想我已经从这些问题中找到了一些我需要的东西:


然而,我在将这些部分装配在一起时遇到了很多困难,尤其是在“保存包层次结构”方面。我想在这里实现的目标是可能的吗?如果是,我将如何实现它?

因此,这可能不是Python设计用来解决的问题,但我们可以让它工作

这个困境有两个独立的部分:第一,“如何在不事先知道的情况下导入所有这些包?”,第二“如何将这些包绑定到一个世界对象,使我能够使用
self
作为第一个参数对它们调用方法?”我将按顺序处理这些问题


如何导入目录中的所有包?
\uuuu init\uuuuu.py
是一个文件,其中包含在尝试加载模块时运行的代码。通常,它负责收集模块中的所有重要资源,并构建其他人可以使用的本地名称空间。我们将稍微滥用这种行为:

>>> x = helloworld.World('hello')
>>> x.world1.frobulate()
TypeError: frobulate() missing 1 required positional argument: 'self'
>>> import helloworld
>>> x = helloworld.World('Tim')
>>> print(x.world1.frobulate())
'Tim has been frobulated'
worlds/\uuuuu init\uuuuuuuuu.py

然后我们得到了错误的行为:

>>> x = helloworld.World('hello')
>>> x.world1.frobulate()
TypeError: frobulate() missing 1 required positional argument: 'self'
>>> import helloworld
>>> x = helloworld.World('Tim')
>>> print(x.world1.frobulate())
'Tim has been frobulated'
这个问题的解决方案是在包装器in之间放置某种类型的中间包,每当您试图调用它时,它都会添加
World()
的实例作为第一个参数。为此,我创建了一个新的内部类,
SubWorld
,该类在初始化时有效地重新绑定模块中的每个方法

因此,此完整代码:

helloworld.py

这给了我们预期的行为:

>>> x = helloworld.World('hello')
>>> x.world1.frobulate()
TypeError: frobulate() missing 1 required positional argument: 'self'
>>> import helloworld
>>> x = helloworld.World('Tim')
>>> print(x.world1.frobulate())
'Tim has been frobulated'


根据每个
worldn
对象的工作方式,您可以相应地修改
SubWorld
(例如,如果需要维护对变量的引用以及对函数的引用)。动态处理此问题的一个好方法可能是使用
property()
s,并将任何特定变量
v
的getter指定为类似lambda的
lambda v:getattr(module,v)

这种层次结构定义在python项目中有点不寻常,这就是为什么你很难用日常语法来实现它。您应该退后一步,想想自己到底是如何投入到这个体系结构中的,如果以更接近于常见python习惯用法的方式重写它还不算太晚,也许您应该这样做(“特别想到显式优于隐式”)

这就是说,如果日常python不削减它,您可以使用奇怪的python编写您想要的内容,而不需要太多麻烦。如果您想了解函数如何被详细地转换成方法,请考虑阅读。
MyFunPackage/worlds/\uuuuuu init\uuuuuuuuuuuuuuuuuuuupy

来自。进口世界1,世界2
需要为您创建的任何新
world\n.py
文件更新此行。虽然它可以自动动态导入,但它会破坏任何IDE的成员暗示,并且需要更灵活的代码。您确实写过,在添加模块时不想更改任何其他内容,但希望将文件名添加到此行是可以的

此文件不应包含任何其他代码

MyFunPackage/worlds/world*.py

def frobulate(自身):
返回f'{self.name}已被冻结'
无需向
world1.py
world2.py
worlds
文件夹中的任何新文件添加任何特殊代码。只要在它们中写下你认为合适的函数就行了

MyFunPackage/helloworlds.py

从类型导入MethodType、FunctionType、SimpleNamespace
从…起进口世界
_基本属性={
“\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu,
“加载程序”、“名称”、“包”、“路径”、“规范”
}
阶级世界:
定义初始化(self,name):
self.name=名称
#对于“worlds”包中的所有模块
对于目录中的世界名称(世界):
如果“世界名称”在“基础”属性中:
继续#跳过非包和
世界=getattr(世界,世界名称)
函数_map={}
#通过以下方式收集其中的所有函数:
对于目录中的func(世界):
如果不是isinstance(getattr(world,func),FunctionType):
继续#忽略非函数,以及
如果getattr(世界,函数)。\uuuuu模块\uuuuu!=世界。\名称\名称:
继续#忽略仅导入的名称
#将它们转换为当前worlds实例的方法
函数映射[func]=MethodType(getattr(world,func),self)
#并将它们添加到以th命名的新命名空间中