Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically create new python class files?

Tags:

python

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.

like image 499
user3517454 Avatar asked Oct 26 '25 18:10

user3517454


1 Answers

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.

like image 162
ndt Avatar answered Oct 29 '25 09:10

ndt



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!