tags:

views:

57

answers:

4

In my program I have a package filled with various .py files each containing a class definition. I want to make a list where each entry is an instance of one of those classes. In addition, my program doesn't know how many files are in the package or what the files or classes are called, so I can't just import each file. Ideally, I should be able to modify the contents of the package (take out files, put new ones in, etc.) without having to rewrite other parts of the program. Is there a way to do this?

Originally, I had a 'if _name_ == '_main_': return foo()' line in each file and tried to append to the list using execfile(), but obviously this doesn't work. Any ideas?

Sorry if this is kinda vague. I'll try to clarify if needed. I'm using Python 2.5.4.

EDIT:

My program is a random character generator for Dungeons and Dragons. I made a package for every major data type the program needs. I have a package for Classes, Races, Items, etc. and when making a character, my program makes a list of each data type that it can sort through when making a character. For example, when equipping a character, the program can look at the Weapon list and filter out all the weapons that are unsuitable for that character and then randomly choose from the ones that remain.

I don't want to specify file names because I would like the ability to easily add to this program later. If later on down the road I wanted to add more weapon types to the program, I could just write a few new class descriptions and drop them in the Weapons package, and the program could use them without me needing to edit any other code.

+2  A: 

This sounds like a bit of a bad design. It would probably be better if you elaborate on the problem and we can help you to solve it some other way. However, what you want isn't hard:

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

EDIT: Simplified the code a bit.

Max Shawabkeh
Alright, this looks like what I want. However, dir(my_package) is only returning ['__builtins__','__doc__','__file__','__name__','__path__'], and none of the modules themselves.I'll also edit the OP and explain what I'm trying to do. I'm open to better ideas.
Max
In that case your package is not importing the modules. You can use the `modulefinder` module for this as follows: `modulefinder.ModuleFinder(path).find_all_submodules(my_package)`.
Max Shawabkeh
What do you mean, the package is not importing the modules? Sorry, this is my first time using packages, I'm not very familiar with them.
Max
Well, packages often import the modules they contain in their `__init__.py`. In your case replacing `dir()` with `modulefinder` as above should be enough for it to work. And regarding your edit, I think it would probably be simpler to define all items of a particular type in the same file then hardcode the imports. In fact, using classes to represent items may be an overkill if all you want is to generate characters - it might be more appropriate to store the items in a data file then read it in the program.
Max Shawabkeh
I see. Well, I put the modulefinder line in place of the dir(), modulefinder works, but returns a list of the module names as strings, not module objects, so my_modules comes back as an empty list.and yeah, it would be a lot simpler to hardcode the imports, and depending on how this issue turns out I may go back to that. The problem with data files is that most of my items are more than just data fields; they need to run a few lines of item-specific code at certain times. Making them classes seemed the best way to handle that.By the way, thanks for all the input. It's been a great help.
Max
A: 

To acheive this you are going to need to do the following things:

  • Have your code enumerate the source files containing your code.
  • For each source file, import the code specified in the file into a new module.
  • For each module, locate all the classes contained, instantiate each one and add it to your final list.

To take each part in turn:

  • To enumerate the source files, use os.walk and os.path to find the files and build full paths to the source.
  • To import code from a given source file dynamically, you can do execfile(my_file) in my_dict where my_file is the full path to your source file and my_dict is a dictionary to return the resulting code in (any classes declared in the source file would become members of this dict for example). Note you only need to use this method if the files you are importing are not part of a valid python module/package hierarchy (with an init.py file in the package) - if they are you can use import() instead.
  • To enumerate the classes declared in a given module you could use inspect.getmembers().
jkp
A: 

If you're willing to do a bit more work, you can use pkg_resource's entry points to advertise and discover the relevant classes. The Fedora Account System uses this to provide plugin functionality.

Ignacio Vazquez-Abrams
A: 

Assuming, first, that all your modules exist as .py files in the package's directory:

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

Further assumptions: each module should have exactly 1 class (otherwise it's skipped with a warning) instantiable without arguments (ditto ditto); you don't want to look at __init__.py (if any; actually this code does not require the path to be an actual package, any directory will do, so __init__.py may or may not be present) nor any module whose name starts with an underscore ("private" modules of the package).

Alex Martelli