如何动态创建新的Python类文件?

4 投票
3 回答
3943 浏览
提问于 2025-04-18 09:52

我现在有一个包含超过200条记录的CSV文件,每一行都需要变成一个独立的类文件。这些类会从一个基础类继承一些字段变量,并根据CSV文件中的内容设置这些变量的值。此外,Python模块的名称也需要根据CSV文件中的某一条记录来命名。

我真的不想手动创建超过200个单独的Python类文件,所以我在想有没有什么简单的方法可以做到这一点。谢谢!

补充一下* 我其实更擅长Java和C#,对Python不太熟悉。 更多细节是:我正在尝试为一个已经存在的网页游戏创建一个AI,我可以通过一个实时文本框提取实时数据。 玩家每回合可以使用超过200种不同的动作,每个动作都非常不同。我可能可以在使用动作时创建一个新的动作类实例,但那样的话,每次使用动作时我都得遍历一个包含所有动作及其效果的数据库,这样效率似乎很低。因此,我在考虑为每个动作创建一个类,名称与文本框中显示的名称相同,这样我就可以更快地创建该特定动作的新实例。

3 个回答

0

与其生成 .py 文件,不如直接读取 csv 文件,然后进行 动态类型创建。这样,如果 csv 文件有变化,你可以确保你的类型会自动重新生成。

1

首先,你不需要把 Python 类分开放在不同的文件里。通常我们会根据功能把它们放在模块和包里(可以参考Python模块和包有什么区别?)。而且,200个相似的类听起来设计得有点奇怪——它们真的都需要吗?或者说,你能不能用字典来存储一些属性呢?

当然,你可以写一个简单的 Python 脚本,读取 CSV 文件,然后生成一个或多个包含类的 .py 文件(就是把文本写到文件里)。

这应该只需要几行代码,具体取决于你想要的定制程度。

如果这个列表有变化,你甚至不需要把类写到文件里:你可以直接动态生成它们

如果你告诉我们你已经做到了什么,或者提供更多关于问题的细节,我们可以帮你完成代码……

3

正如其他人所说的,通常情况下,你会想要在运行时生成类,而不是创建单独的文件。

但我在想:如果你有很好的理由这样做,比如说只是为了给一堆文件制作类模板,这样你可以以后再去扩展它们呢?假设我打算写很多代码,所以我想自动化一些重复的代码部分,这样我就不需要做那些繁琐的工作了。

结果发现,为Python类写一个简单的模板引擎并不是那么难。这是我尝试的一个方法,它可以从csv文件中进行模板化。

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

上面的代码将一个格式如下的csv文件作为第一个参数(这里保存为a.csv):

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

其中第一个字段是类名,接下来是属性名及其默认值。第二个参数是输出文件夹的路径。

如果我创建一个名为classes的文件夹,并在里面创建一个classes/mysuper.py,里面有一个基本的类结构:

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

然后像这样运行代码:

$ python3 codegen.py a.csv classes MySuper

我会得到文件classes/magistrate.py,内容如下:

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

还有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__()

你实际上可以加载它们并使用它们哦!

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

上面的代码生成的是Python 3的代码,这是我习惯的,所以如果你想在Python 2中使用,需要做一些小的修改。

撰写回答