Dynamic Class Instantiation in Python
I have a bunch of classes in a module. Let's say:
'''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
...
Now, in another module, I want to dynamically load all classes in players.py
and instantiate them. I use the python inspect
module for this.
'''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]
I would ai_players
expect to be a list of object instances like so (let's suppose __str__
returns the name): ['Homer', 'Barney...]
However, I get the following error
TypeError: __init__() takes exactly 2 arguments (1 given)
The funny thing is that when I don't instantiate the class objects in a list comprehension or a for loop everything works fine.
# this does not throw an error
ai_player1 = ai_players[0]()
print ai_player1
# >> 'Homer'
So why doesn't Python allow me to instantiate the classes in a list comprehensions/for loop (I tried it in a for loop, too)?
Or better: How would you go about dynamically loading all classes in a module and instantiating them in a list?
* Note, I'm using Python 2.6
EDIT
Turns out I oversimplified my problem and the above code is just fine.
However if I change players.py
to
'''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)
and change the 5th line in main.py
to
ai_players = inspect.getmembers(players,
lambda x: inspect.isclass(x) and issubclass(x, players.Player))
I encounter the described problem. Also that doesn't work either (it worked when I tested it on the repl)
ai_player1 = ai_players[0]()
It seems to have something to do with inheritance and default arguments. If I change the base class Player
to
class Player():
"""This class is intended to be subclassed"""
def __init__(self, name='Maggie'):
self.name = name
Then I don't get the error but the players' names are always 'Maggie'.
EDIT2:
I played around a bit and it turns out the getmembers function somehow "eats" the default parameters. Have a look at this log from the 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]开发者_Python百科
>>> ai_players[0].__init__.func_defaults
>>> print ai_players[0].__init__.func_defaults
None
>>> players.Player1.__init__.func_defaults
('Homer',)
Do you know of any alternative to getmembers from the inspect module?
EDIT3:
Note that issubclass() returns True if given the SAME class twice. (Tryptich)
That's been the evildoer
I ran your test code and it worked fine for me.
That error is indicating that you are not actually setting the default "name" parameter on ALL of your classes.
I'd double check.
Edit:
Note that issubclass()
returns True
if given the SAME class twice.
>>> class Foo: pass
>>> issubclass(Foo, Foo)
True
So your code tries to instantiate Player
with no name argument and dies.
Seems like you're over-complicating things a bit. I'd suggest explaining your program in a bit more detail, perhaps in a different question, to get some feedback on whether this approach is even necessary (probably not).
ai_players = inspect.getmembers(players, inspect.isclass())
This line tries to calls inspect.isclass with no arguments and gives the result of the call to getmembers as second argument. You want to pass the function inspect.isclass
, so just do that (edit: to be clearer, you want inspect.getmembers(players, inspect.isclass)
). What do you think these parentheses do?
The following is a better way of doing what you want.
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]
Note that you want to define a single class to hold the details of a player, then you can create as many instances of this class as required.
精彩评论