Python中的动态类实例化
我在一个模块里有一堆类。比如说:
'''players.py'''
class Player1:
def __init__(self, name='Homer'):
self.name = name
class Player2:
def __init__(self, name='Barney'):
self.name = name
class Player3:
def __init__(self, name='Moe'):
self.name = name
...
现在,在另一个模块里,我想动态加载所有在 players.py
里的类,并实例化它们。我用的是 Python 的 inspect
模块来实现这个。
'''main.py'''
import inspect
import players
ai_players = inspect.getmembers(players, inspect.isclass)
# ai_players is now a list like: [('Player1',<class instance>),(...]
ai_players = [x[1] for x in ai_players]
# ai_players is now a list like: [<class instance>, <class...]
# now let's try to create a list of initialized objects
ai_players = [x() for x in ai_players]
我希望 ai_players
是一个对象实例的列表,像这样(假设 __str__
方法返回的是名字):['Homer', 'Barney...]
但是,我遇到了以下错误:
TypeError: __init__() takes exactly 2 arguments (1 given)
有趣的是,当我不在列表推导式或循环中实例化类对象时,一切都正常。
# this does not throw an error
ai_player1 = ai_players[0]()
print ai_player1
# >> 'Homer'
那么,为什么 Python 不允许我在列表推导式或循环中实例化这些类呢(我也试过在循环中)?
或者更好的是:你会怎么做才能动态加载一个模块里的所有类并在一个列表中实例化它们呢?
* 注意,我使用的是 Python 2.6
编辑
结果我把问题想得太简单了,上面的代码其实没问题。
不过如果我把 players.py
改成:
'''players.py'''
class Player():
"""This class is intended to be subclassed"""
def __init__(self, name):
self.name = name
class Player1(Player):
def __init__(self, name='Homer'):
Player.__init__(self, name)
class Player2(Player):
def __init__(self, name='Barney'):
Player.__init__(self, name)
class Player3(Player):
def __init__(self, name='Moe'):
Player.__init__(self, name)
并把 main.py
中的第5行改成:
ai_players = inspect.getmembers(players,
lambda x: inspect.isclass(x) and issubclass(x, players.Player))
我就会遇到之前描述的问题。此外,这样也不行(在 repl 上测试时是可以的)
ai_player1 = ai_players[0]()
这似乎和继承以及默认参数有关。如果我把基类 Player
改成:
class Player():
"""This class is intended to be subclassed"""
def __init__(self, name='Maggie'):
self.name = name
那么我就不会再出现错误,但玩家的名字总是 'Maggie'。
编辑2:
我玩了一下,发现 getmembers
函数似乎“吃掉”了默认参数。看看这个来自 repl 的日志:
>>> import players
>>> import inspect
>>> ai_players = inspect.getmembers(players,
... lambda x: inspect.isclass(x) and issubclass(x, players.Player))
>>> ai_players = [x[1] for x in ai_players]
>>> ai_players[0].__init__.func_defaults
>>> print ai_players[0].__init__.func_defaults
None
>>> players.Player1.__init__.func_defaults
('Homer',)
你知道有没有其他替代 inspect
模块中的 getmembers
的方法吗?
编辑3:
注意,如果给
issubclass()
两次相同的类,它会返回 True。(Tryptich)
这就是问题的根源。
3 个回答
下面是一种更好的方法来实现你想要的功能。
player.py:
class Player:
def __init__(self, name):
self.name = name
main.py:
import player
names = ['Homer', 'Barney', 'Moe']
players = [player.Player(name) for name in names]
注意,你需要定义一个类来保存玩家的详细信息,然后可以根据需要创建多个这个类的实例。
ai_players = inspect.getmembers(players, inspect.isclass())
这一行代码试图在没有任何参数的情况下调用 inspect.isclass
,并把这个调用的结果作为第二个参数传给 getmembers
。你其实是想把函数 inspect.isclass
传进去,所以只需要这样做(编辑:为了更清楚,你需要写成 inspect.getmembers(players, inspect.isclass)
)。你觉得这些括号有什么作用呢?
我运行了你的测试代码,结果一切正常。
那个错误提示说明你并没有在所有的类里都设置默认的“名字”参数。
我建议你再仔细检查一下。
编辑:
注意,issubclass()
如果给的是同一个类两次,它会返回 True
。
>>> class Foo: pass
>>> issubclass(Foo, Foo)
True
所以你的代码试图用没有名字参数的方式来创建 Player
,结果就出错了。
看起来你把事情搞得有点复杂了。我建议你可以更详细地解释一下你的程序,或许可以换个问题来询问,这样能得到一些反馈,看看这种做法是否真的有必要(可能并没有)。