在Python中从文件返回类的实例
在我的程序里,有一个包里面装了很多个.py文件,每个文件里都有一个类的定义。我想做一个列表,列表里的每一项都是这些类的一个实例。而且,我的程序并不知道包里有多少个文件,也不知道这些文件或类的名字,所以我不能直接导入每个文件。理想情况下,我希望能够修改这个包的内容(比如删除文件、添加新文件等),而不需要重写程序的其他部分。有没有办法做到这一点呢?
最开始,我在每个文件里都有一行'if __name__ == '__main__': return foo()',然后试着用execfile()来添加到列表里,但显然这样不行。有没有什么想法呢?
抱歉,如果这个描述有点模糊。如果需要的话,我会尽量解释得更清楚。我使用的是Python 2.5.4。
补充一下:
我的程序是一个随机角色生成器,用于《龙与地下城》。我为程序需要的每种主要数据类型都做了一个包。我有一个包专门放角色类、种族、物品等等。在创建角色时,我的程序会生成一个每种数据类型的列表,这样在制作角色时可以进行筛选。例如,在给角色装备时,程序可以查看武器列表,过滤掉所有不适合这个角色的武器,然后从剩下的武器中随机选择。
我不想指定文件名,因为我希望以后能方便地对这个程序进行扩展。如果将来我想添加更多的武器类型,我只需要写几个新的类描述,然后把它们放进武器包里,程序就能使用它们,而我不需要修改其他任何代码。
4 个回答
首先假设,你的所有模块都以 .py
文件的形式存在于包的目录里:
import inspect, glob, os, sys
def thelistyouwant(pathtothepackage):
sys.path.insert(0, pathtothepackage)
result = []
for fn in glob.glob(os.path.join(pathtothepackage, '*.py')):
if fn.startswith('_'): continue # no __init__ or other private modules
m = __import__(fn[:-3])
classes = inspect.getmembers(m, inspect.isclass)
if len(classes) != 1:
print>>sys.stderr, "Skipping %s (%d != 1 classes!)" % (fn, len(classes))
continue
n, c = classes[0]
try:
result.append(c())
except TypeError:
print>>sys.stderr, "Skipping %s, can't build a %s()" % (fn, n)
del sys.path[0]
return result
进一步假设:每个模块应该恰好有一个类(如果有多个类,就会跳过并发出警告),而且这个类可以不带参数地实例化(同样的道理);你不想去看 __init__.py
文件(如果有的话;实际上,这段代码并不要求路径一定是一个真正的包,任何目录都可以,所以 __init__.py
文件可能存在也可能不存在),也不想看任何名字以下划线开头的模块(这些是包的“私有”模块)。
要实现这个目标,你需要做以下几件事:
- 让你的代码列出包含你代码的源文件。
- 对于每个源文件,将文件中指定的代码导入到一个新的模块中。
- 对于每个模块,找到里面所有的类,实例化每一个类,并把它们添加到你的最终列表中。
下面逐一解释每个步骤:
- 要列出源文件,可以使用
os.walk
和os.path
来查找文件,并构建源文件的完整路径。 - 要动态导入某个源文件中的代码,可以使用
execfile(my_file) in my_dict
,其中my_file
是你的源文件的完整路径,my_dict
是一个字典,用来存放导入的代码(比如,源文件中声明的任何类都会成为这个字典的成员)。注意,只有当你导入的文件不属于有效的 Python 模块/包结构(即包中没有 init.py 文件)时,才需要使用这种方法。如果它们属于有效的模块/包结构,你可以直接使用 import()。 - 要列出某个模块中声明的类,可以使用 inspect.getmembers()。
这听起来有点设计不太好。如果你能详细说明一下问题,我们可以帮你想其他解决办法。不过,你想要的其实并不难:
import types
import my_package
my_package_members = [getattr(my_package, i) for i in dir(my_package)]
my_modules = [i for i in my_package_members if type(i) == types.ModuleType]
instances = []
for my_module in my_modules:
my_module_members = [getattr(my_module, i) for i in dir(my_module)]
my_classes = [i for i in my_module_members
if type(i) in (types.TypeType, types.ClassType)]
for my_class in my_classes:
instances.append(my_class())
编辑:把代码简化了一下。