views:

101

answers:

3

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

+1  A: 
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?

delnan
oh sorry yes indeed you are right. But in the real code the mistake doesn't exist. I wrote this simplified example to underline the main issues and didn't test it. It's fixed now
Eugen
+1  A: 

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.

PreludeAndFugue
The example I described in my question is simplified. Of course the Players provide distinct characteristics besides their names (the logic how they play...). So your proposal is not exactly what I want.Also, the names (and in a more complex example other attributes, too) should be loaded dynamically.What if I add a player class in `players.py`? I'd have to add a name in `main.py`. This is not what I want.
Eugen
@Eugen: You said you subclass to change to logic how they play. It might be much easier to use the strategy pattern: http://en.wikipedia.org/wiki/Strategy_pattern
THC4k
+1  A: 

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

Triptych
I updated the question with a better example to illustrate what happens to be the "real" problem
Eugen
That's right, you need to add a condition into your `lambda` to make sure you don't try to instatiate the superclass `Player`. On a separate note, personally I'd manually create a list of the defined player classes at the top of `players.py`, to avoid too much `inspect`ing.
Sanjay Manohar
Yes, all the time the problem's been the fact, that the base class `Player` was inside the list, duh. And sanjay's proposal solved the problem!Frankly, for my particular problem I would not need introspection (scale is small). But imagine you would provide a game with user provided ai players (in the sense of google ai challenge). Wouldn't it be practical if user's could just put their ai's in a folder all ai's are automatically played against each other (of course not considerung sandboxing etc.)? That's the reason for the design decision.
Eugen