简体   繁体   中英

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]
>>> 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM