【问题标题】:Python generating PythonPython 生成 Python
【发布时间】:2010-11-24 18:41:12
【问题描述】:

我有一组对象,我正在为其创建一个类,我想将每个对象存储为它自己的文本文件。我真的很想将它存储为 Python 类定义,它是我正在创建的主类的子类。所以,我在 effbot.org 上搜索了一下,发现了一个 Python 代码生成器。我做了一些实验,这就是我想出的:

#
# a Python code generator backend
#
# fredrik lundh, march 1998
#
# fredrik@pythonware.com
# http://www.pythonware.com
#
# Code taken from http://effbot.org/zone/python-code-generator.htm

import sys, string

class CodeGeneratorBackend:

    def begin(self, tab="\t"):
        self.code = []
        self.tab = tab
        self.level = 0

    def end(self):
        return string.join(self.code, "")

    def write(self, string):
        self.code.append(self.tab * self.level + string)

    def indent(self):
        self.level = self.level + 1

    def dedent(self):
        if self.level == 0:
            raise SyntaxError, "internal error in code generator"
        self.level = self.level - 1

class Point():
    """Defines a Point. Has x and y."""
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def dump_self(self, filename):
        self.c = CodeGeneratorBackend()
        self.c.begin(tab="    ")
        self.c.write("class {0}{1}Point()\n".format(self.x,self.y))
        self.c.indent()
        self.c.write('"""Defines a Point. Has x and y"""\n')
        self.c.write('def __init__(self, x={0}, y={1}):\n'.format(self.x, self.y))
        self.c.indent()
        self.c.write('self.x = {0}\n'.format(self.x))
        self.c.write('self.y = {0}\n'.format(self.y))
        self.c.dedent()
        self.c.dedent()
        f = open(filename,'w')
        f.write(self.c.end())
        f.close()

if __name__ == "__main__":
    p = Point(3,4)
    p.dump_self('demo.py')

感觉真的很难看,有没有更清洁/更好/更蟒蛇的方式来做到这一点?请注意,这不是我真正打算使用的课程,这是一个我可以轻松模拟的小课程,不需要太多行。另外,子类中不需要有生成函数,如果我再次需要,我可以从超类中调用代码生成器。

【问题讨论】:

  • 不要使用制表符来缩进python,使用空格:)
  • 是的,它设置为默认值,但我用 4 个空格覆盖了该变量。
  • 如何破解对象上的__str__ 方法,以便在打印单个对象和列表时它们都返回有效的python 代码?

标签: code-generation python


【解决方案1】:

我们使用 Jinja2 来填写模板。这要简单得多。

模板看起来很像 Python 代码,其中有几个 {{something}} 替换。

【讨论】:

  • 根据我对 Anon 的评论,我将只使用模板系统。我会密切关注 Jinja,感谢您的提示!
  • 有这样的例子吗?
  • 我使用 str.format 做了同样的事情(好吧,在这种情况下从 python 生成 java)。 - 虽然在某种程度上感觉有点不对劲,但比使用 AST 更容易看到发生了什么。
  • 我最近为 Jinja2 开发了一个命令行工具,特别是考虑到代码生成。 github.com/kblomqvist/yasha
【解决方案2】:

这几乎是生成 Python 源代码 代码的最佳方式。但是,您也可以使用 ast 库在运行时生成 Python 可执行代码。您可以使用抽象语法树构建代码,然后将其传递给compile() 以将其编译为可执行代码。然后就可以使用eval()运行代码了。

我不确定是否有一种方便的方法可以保存已编译的代码以供以后使用(即在.pyc 文件中)。

【讨论】:

  • 好吧,我真的想要一些人类可读的东西。可执行代码往往不是。
  • 很公平,我会把这个答案留在这里,以防它对有类似问题的其他人有用。
  • 在 Python 3.9+ 中有 ast.unparse()
  • 有什么简单的例子使用ast生成可执行代码?
【解决方案3】:

只需阅读您对 Wintermute 的评论 - 即:

我拥有的是一堆行星 我想将每个存储为自己的文本 文件。我不是特别执着 将它们存储为 python 源代码, 但我很喜欢制作它们 人类可读。

如果是这样,那么您似乎不需要子类,但应该能够使用相同的类并仅通过数据区分行星。在这种情况下,为什么不直接将数据写入文件,当您在程序中需要行星对象时,读取数据以初始化对象?

如果您需要执行诸如覆盖方法之类的操作,我可以看到编写代码 - 但您不应该对所有行星使用相同的方法,只需使用不同的变量吗?

只写出数据(它可以包括标签类型信息以提高可读性,当您读入时您会跳过)的好处是非 Python 程序员在阅读它们时不会分心,您可以使用如有必要,使用其他语言的相同文件等。

【讨论】:

  • 是的,我对这个问题想得太多了,不是吗。我可能会这样做。谢谢!
【解决方案4】:

我不确定这是否特别 Pythonic,但您可以使用运算符重载:

class CodeGenerator:
    def __init__(self, indentation='\t'):
        self.indentation = indentation
        self.level = 0
        self.code = ''

    def indent(self):
        self.level += 1

    def dedent(self):
        if self.level > 0:
            self.level -= 1

    def __add__(self, value):
        temp = CodeGenerator(indentation=self.indentation)
        temp.level = self.level
        temp.code = str(self) + ''.join([self.indentation for i in range(0, self.level)]) + str(value)
        return temp

    def __str__(self):
        return str(self.code)

a = CodeGenerator()
a += 'for a in range(1, 3):\n'
a.indent()
a += 'for b in range(4, 6):\n'
a.indent()
a += 'print(a * b)\n'
a.dedent()
a += '# pointless comment\n'
print(a)

当然,这比您的示例实施起来要昂贵得多,而且我会警惕过多的元编程,但这是一个有趣的练习。您可以根据需要扩展或使用它;怎么样:

  • 添加 write 方法并将 stdout 重定向到 this 的对象以直接打印到脚本文件
  • 从它继承以自定义输出
  • 添加属性获取器和设置器

很高兴听到你的选择:)

【讨论】:

    【解决方案5】:

    据我了解您正在尝试做的事情,我会考虑使用反射在运行时动态检查一个类并基于它生成输出。 http://diveintopython3.ep.io/ 有一个很好的关于反射(A.K.A. introspection)的教程。

    您可以使用dir() 函数获取给定对象的属性名称列表。可以通过__doc__ 属性访问对象的文档字符串。也就是说,如果要查看函数或类的文档字符串,可以执行以下操作:

    >>> def foo():
    ...    """A doc string comment."""
    ...    pass
    ...
    >>> print foo.__doc__
    A doc string comment.
    

    【讨论】:

    • 我有一堆行星,我想将它们存储为各自的文本文件。我并不特别喜欢将它们存储为 python 源代码,但我很喜欢使它们易于阅读。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-02
    • 2021-09-17
    • 2011-04-17
    • 1970-01-01
    • 2010-12-05
    • 2021-03-29
    • 2012-08-25
    相关资源
    最近更新 更多