srcgenを止めてprestringというライブラリを作り始めました。

srcgenを止めてprestringというライブラリを作り始めました。

ここ最近srcgenを使って遊んでたんですが、色々と思う所があったので自分でライブラリを作りました。

srcgenの素晴らしいところ

インデントを含んだ構造をすごい手軽に記述できるのが良いです。 具体的にはpythonコードの文字列を生成してexec()を呼ぶというのがすごくやりやすくなりました。

from srcgen import PythonModule

def as_python_code(fn):
    def wrapper(name, *args, **kwargs):
        m = PythonModule()
        fn(m, name, *args, **kwargs)
        # activate python code
        env = {}
        exec(str(m), env)
        return env[name]
    return wrapper


@as_python_code
def namedobject(m, name, fields):
    args = fields.replace(",", " ").split(" ")
    with m.class_(name):
        with m.def_("__init__", "self", *args):
            for x in args:
                m.stmt("self.{x} = {x}".format(x=x))

        m.stmt("__slots__ = {!r}".format(args))

        with m.def_("__repr__", "self"):
            m.return_("self.__class__.__name__ + '({})'.format(self=self)".format(
                ", ".join("{x}={{self.{x}}}".format(x=x) for x in args)
            ))

Point = namedobject("Point", "x y z")

# 以下のようなコードが生成

"""
class Point(object):
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    __slots__ = ['x', 'y', 'z']
    def __repr__(self):
        return self.__class__.__name__ + '(x={self.x}, y={self.y}, z={self.z})'.format(self=self)
"""

任意回数のインデントを含んだ構造を表現できないのではと思いましたが再帰でどうにかなりました。

srcgenの良くない所

使っていて幾つか不満というか良くないと思った所もあります。大きく分けると以下の2つです。

  • コード生成の途中に既に通った箇所について後付で別のコードを注入することができない
  • コード生成の指定時に一行で書いたものを複数行に分けて出力するということができない

コード生成の途中に既に通った箇所について後付で別のコードを注入することができない

これは以下のようなことをしたい場合に遭遇する例です。何らかのプラグイン的なコードを生成したいと思っています。 ここであるプラグインaはあるモジュールAに依存しており、また別のプラグインbはモジュールBに依存しているとします。 ここで生成したいコード中にどのようなプラグインが必要かは後に決まります。前もって指定する事はできません。

ここで from A import plugin_a というようなimport文をを後付で埋め込みたいと思った時にsrcgenは対応していませんでした。

def use_plugin_a(m):
    m.from_("A", "plugin_a")
    m.stmt("so_something(plugin_a)")

def use_plugin_b(m):
    m.from_("B", "plugin_b")
    m.stmt("so_something(plugin_b)")

use_plugin_a(m)
use_plugin_b(m)

# 以下の様になってしまう。
"""
from A import plugin_a
so_something(plugin_a)
from B import plugin_b
so_something(plugin_b)
"""

本来求めていたのはこのような出力でした。

from A import plugin_a
from B import plugin_b

so_something(plugin_a)
so_something(plugin_b)

コード生成の指定時に一行で書いたものを複数行に分けて出力するということができない。

これは、pep8対応するためと言ったほうが良いかもしれません。 具体的にはコード中の行数が長過ぎるようなコードに対して複数行に分けたいと思ったのですが。 withのcontextで保持されているインデントのセマンティクスを考慮しての改行ができませんでした。

# 生成後のコード
def f(x):
    return SomeLongNameClass(logn_argument_word1="long long argument 0", logn_argument_word2="long long argument 1", logn_argument_word3="long long argument 2", logn_argument_word4="long long argument 0")

# 以下の様に改行したい
def f(x):
    return SomeLongNameClass(
        long_argument_word1="long long argument 0",
        long_argument_word2="long long argument 1",
        long_argument_word3="long long argument 2",
        long_argument_word4="long long argument 0")

これから

prestring というライブラリを作り始めました。