4

I currently have a CSV file with over 200 entries, where each line needs to be made into its own class file. These classes will be inheriting from a base class with some field variables that it will inherit and set values to based on the CSV file. Additionally, the name of the python module will need to be based off an entry of the CSV file.

I really don't want to manually make over 200 individual python class files, and was wondering if there was a way to do this easily. Thanks!

edit* I'm definitely more of a java/C# coder so I'm not too familiar with python. Some more details: I'm trying to create an AI for an already existing web game, which I can extract live data from via a live stream text box. There are over 200 moves that a player can use each turn, and each move is vastly different. I could possibly create new instances of a move class as it's being used, but then I would have to loop through a database of all the moves and its effects each time the move is used, which seems very inefficient. Hence, I was thinking of creating classes of every move with the same name as it would appear in the text box so that I could create new instances of that specific move more quickly.

user3517454
  • 217
  • 2
  • 11
  • @LevLevitsky maybe the OP is confusing python with java. – timgeb Jun 15 '14 at 19:52
  • 3
    Are you sure you need 200 classes? They'll only differ by the values of certain fields? Sounds like 200 different instances to me. Can you explain more about why you need 200 classes? – Ned Batchelder Jun 15 '14 at 19:57
  • 2
    Not sure what exactly you are trying to do... but it seems you should reconsider your program design, no point in making 200 different classes when all they differ is in certain parameters - make them instances of a class instead. – tbrisker Jun 15 '14 at 19:58
  • Each of these classes will be used a lot of times, and I think it would be inefficient to go through the entire database to set 9-10 fields each time the class needs to be instantiated. – user3517454 Jun 15 '14 at 20:11
  • Another Python versus Java/C# thing: you don't choose between classes and instances based on performance. In Java/C#, classes are compile-time objects and instances are runtime objects, but in Python, they're all runtime objects. If you're worried about efficiency, think about caching pre-built instances. I don't see what classes versus instances has to do with database lookup, regardless of platform (Python, Java, or C#). If database lookup is expensive, avoid it... but in a standard architecture. – Jim Pivarski Jun 15 '14 at 20:24
  • You can create a class or a function inside a function. You can also attach a function to an existing class and do that depending on some parameters. Local functions have access to local variables within the call of that function. Note that in Python, classes are objects, too! All this should give you enough tools to do what you are asking for, but also consider the warnings given by others that this is possibly not the best of all ideas. – Ulrich Eckhardt Jun 15 '14 at 20:51

3 Answers3

3

As others have stated, you usually want to be doing runtime class generation for this kind of thing, rather than creating individual files.

But I thought: what if you had some good reason to do this, like just making class templates for a bunch of files, so that you could go in and expand them later? Say I plan on writing a lot of code, so I'd like to automate the boilerplate code parts, that way I'm not stuck doing tedious work.

Turns out writing a simple templating engine for Python classes isn't that hard. Here's my go at it, which is able to do templating from a csv file.

from os import path
from sys import argv
import csv

INIT = 'def __init__'

def csvformat(csvpath):
    """ Read a csv file containing a class name and attrs.

    Returns a list of the form [['ClassName', {'attr':'val'}]].
    """
    csv_lines = []
    with open(csvpath) as f:
        reader = csv.reader(f)
        _ = [csv_lines.append(line)
                for line in reader]
    result = []
    for line in csv_lines:
        attr_dict = {}
        attrs = line[1:]
        last_attr = attrs[0]
        for attr in attrs[1:]:
            if last_attr:
                attr_dict[last_attr] = attr
                last_attr = ''
            else:
                last_attr = attr
        result.append([line[0], attr_dict])
    return result

def attr_template(attrs):
    """ Format a list of default attribute setting code. """
    attr_list = []
    for attr, val in attrs.items():
        attr_list.append(str.format('    if {} is None:\n', attr, val))
        attr_list.append(str.format('      self.{} = {}\n', attr, val))
        attr_list.append('    else:\n')
        attr_list.append(str.format('      self.{} = {}\n', attr, attr))
    return attr_list

def import_template(imports):
    """ Import superclasses.

    Assumes the .py files are named based on the lowercased class name.
    """
    imp_lines = []
    for imp in imports:
        imp_lines.append(str.format('from {} import {}\n',
            imp.lower(), imp))
    return imp_lines

def init_template(attrs):
    """ Template a series of optional arguments based on a dict of attrs.
    """
    init_string = 'self'
    for key in attrs:
        init_string += str.format(', {}=None', key)
    return init_string

def gen_code(foldername, superclass, name, attrs):
    """ Generate python code in foldername.

    Uses superclass for the superclass, name for the class name,
    and attrs as a dict of {attr:val} for the generated class.

    Writes to a file with lowercased name as the name of the class.
    """
    imports = [superclass]
    pathname = path.join(foldername, name.lower() + '.py')
    with open(pathname, 'w') as pyfile:
        _ = [pyfile.write(imp) 
                for imp
                in import_template(imports)]
        pyfile.write('\n')
        pyfile.write((str.format('class {}({}):\n', name, superclass)))
        pyfile.write((str.format('  {}({}):\n', 
            INIT, init_template(attrs))))
        _ = [pyfile.write(attribute) 
                for attribute
                in attr_template(attrs)]
        pyfile.write('    super().__init__()')

def read_and_generate(csvpath, foldername, superclass):
    class_info = csvformat(csvpath)
    for line in class_info:
        gen_code(foldername, superclass, *line)

def main():
    read_and_generate(argv[1], argv[2], argv[3])

if __name__ == "__main__":
    main()

The above takes a csvfile formatted like this as its first argument (here, saved as a.csv):

Magistrate,foo,42,fizz,'baz'
King,fizz,'baz'

Where the first field is the class name, followed by the attribute name and its default value. The second argument is the path to the output folder.

If I make a folder called classes and create a classes/mysuper.py in it with a basic class structure:

class MySuper():
    def __init__(*args, **kwargs):
        pass

And then run the code like this:

$ python3 codegen.py a.csv classes MySuper

I get the files classes/magistrate.py with the following contents:

from mysuper import MySuper

class Magistrate(MySuper):
  def __init__(self, fizz=None, foo=None):
    if fizz is None:
      self.fizz = 'baz'
    else:
      self.fizz = fizz
    if foo is None:
      self.foo = 42
    else:
      self.foo = foo
    super().__init__()

And classes/king.py:

from mysuper import MySuper

class King(MySuper):
  def __init__(self, fizz=None):
    if fizz is None:
      self.fizz = 'baz'
    else:
      self.fizz = fizz
    super().__init__()

You can actually load them and use them, too!

$ cd classes
classes$ python3 -i magistrate.py
>>> m = Magistrate()
>>> m.foo
42
>>> m.fizz
'baz'
>>>

The above generates Python 3 code, which is what I'm used to, so you will need to make some small changes for it to work in Python 2.

ndt
  • 376
  • 2
  • 8
1

First of all, you don't have to seperate python classes by files - it is more common to group them by functionality to modules and packages (ref to What's the difference between a Python module and a Python package?). Furthermore, 200 similar classes sound like a quite unusual design - are they really needed or could you e.g. use a dict to store some properties?

And of course you can just write a small python script, read in the csv, and generate one ore more .py files containing the classes (lines of text written to the file).

Should be just a few lines of code depending on the level of customization.

If this list changes, you even don't have to write the classes to a file: You can just generate them on the fly.

If you tell us how far you got or more details about the problem, we could help in completing the code...

Community
  • 1
  • 1
OBu
  • 4,977
  • 3
  • 29
  • 45
  • I could use a dictionary, but the thing is each class has around 10 field variables, and the "move" class might even contain functions that might affect another completely different class that has an instance of that "move" class as a field. Thanks for letting me know that not every class has to be in its own file. More used to java and C#, so didn't know. – user3517454 Jun 15 '14 at 20:13
0

Instead of generating .py files, read in the csv and do dynamic type creation. This way, if the csv changes, you can be sure that your types are dynamically regenerated.

Neil G
  • 32,138
  • 39
  • 156
  • 257