PythonでPythonを出力する


Pythonにはクラスや関数を動的に生成したり変更したりする機能があるので、
明示的にソースコードを生成してexecする必要は必ずしも無いです。



しかし、execの方がデバッグしやすい or 効率が良い場合もあります。
namedtupleやturtleはexecを使って実装されています。



自作ソフトでコード出力を使いまくるので、専用モジュールを書いてみました。


自作ソフトで実際に必要なのは、def,if,yieldぐらいなので、
足りない機能があるかもしれません。

#encoding:shift-jis
from __future__ import division, with_statement, print_function
from myutil import *
from collections import MutableSequence

__metaclass__ = type

DEFAULT_NUMSPACES = 4

class Statements:
    def __init__(self):
        self._items = []
        self.need_pass = True
        
    def append(self, x):
        self._items.append(x)
        if not isinstance(x, _Comment):
            self.need_pass = False
        
    def __iter__(self):
        for i in self._items:
            yield i
        if self.need_pass:
            yield _Pass_()
    
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        return "\n".join(x.tostr(numspaces, indent) for x in self) + "\n"

class Sentence(object):
    pass
    
class Code(object):
    def __init__(self):
        self.stmts = Statements()
    
    def append(self, x):
        assert x is not None
        self.stmts.append(x)

    def __enter__(self):
        return self

    def __exit__(self, *a, **kw):
        return 0
    
    def __getattr__(self, name):
        klassname = "_" + name.title()
        try:
            klass = eval(klassname)
        except NameError:
            raise AttributeError("{0} object has no attribute {1}"
                    .format(repr(type(self).__name__), repr(name)))
        else:
            def substmt(*a, **kw):
                k = klass(*a, **kw)
                self.append(k)
                return k
            substmt.__name__ = name
            return substmt
    
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        return self.stmts.tostr(numspaces, indent)

def Str(s):
    return Expr(repr(s))
    
class Expr(object):
    def __init__(self, str):
        self.str = str
    def __str__(self):
        return self.str
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        return self.str
    
class ExprList(list):
    pass
    
class Symbol(object):
    def __init__(self, str):
        self.str = str
    def __str__(self):
        return self.str
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        return str(self)
    
class SymbolList(list):
    pass

for name in "expr symbol".split():
    execstr = """\
def {0}(x):
    if isinstance(x, basestring):
        return {1}(x)
    else:
        assert isinstance(x, {1}), x
        return x

def {0}_list(x):
    if isinstance(x, basestring):
        return {1}List([{0}(x)])
    elif isinstance(x, (list, tuple)):
        return {1}List({0}(i) for i in x)
    else:
        assert isinstance(x, {1}), x
        return x
""".format(name, name.title())
    
    exec execstr

def get_items(x):
    if isinstance(x, dict):
        return list(x.iteritems())
    else:
        return x

    
class assign_map(list):
    def __init__(self, items):
        list.__init__(self, [(symbol(k), expr(v)) for k, v in get_items(items)])
    
class _Compound(Code):
    def __init__(self):
        Code.__init__(self)
        self._else_ = _NullElse()
    
    def else_(self):
        assert isinstance(self._else_, _NullElse), "else has set!"
        self._else_ = _Else_()
        return self._else_
    
class _Block(Code):
    pass

class _NullElse(_Block):
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        return ""

class _Else_(_Block):
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        return " "*numspaces * indent + "else:\n" + \
            self.stmts.tostr(numspaces, indent + 1)
    
class _Elif_(_Block):
    def __init__(self, condexpr):
        _Block.__init__(self)
        self.cond = expr(condexpr)
    
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        return " "*numspaces * indent + "elif {0}:\n".format(self.cond.tostr()) + \
               self.stmts.tostr(numspaces, indent + 1)
        
class _If_(_Compound):
    def __init__(self, cond):
        _Compound.__init__(self)
        attributesFromDict(locals())
        self.cond = expr(cond)
        self._elif_ = []
    
    def elif_(self, condexpr):
        x = _Elif_(condexpr)
        self._elif_.append(x)
        return x
    
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        return " "*numspaces * indent + "if {0}:\n".format(self.cond.tostr()) + \
               self.stmts.tostr(numspaces, indent + 1) + \
               "".join(x.tostr(numspaces, indent) for x in self._elif_) + \
               self._else_.tostr(numspaces, indent)
        
class _While_(_Compound):
    def __init__(self, cond):
        _Compound.__init__(self)
        attributesFromDict(locals())
        self.cond = expr(cond)
    
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        return " "*numspaces*indent + "while {0}:\n".format(self.cond.tostr()) + \
               self.stmts.tostr(numspaces, indent + 1) + \
               self._else_.tostr(numspaces, indent)
    
class _For_(_Compound):
    def __init__(self, target, it):
        _Compound.__init__(self)
        self.expr = expr(it)
        self.target = expr(target)
    
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        return " "*numspaces*indent + "for {0} in {1}:\n".format(self.target.tostr(), self.expr.tostr()) + \
               self.stmts.tostr(numspaces, indent + 1) + \
               self._else_.tostr(numspaces, indent)
    
class _Let(Sentence):
    def __init__(self, var, value):
        Sentence.__init__(self)
        self.var = expr(var)
        self.value = expr(value)
    
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        return " "*numspaces*indent + self.var.tostr() + " = " + self.value.tostr()

class _FuncMixin(object):
    def argstr(self):
        return ", ".join(
            [a.tostr() for a in self.args] + 
            [k.tostr() + "=" + v.tostr() for k, v in self.kwargs] + 
            (["*" + self.a.tostr()] if self.a else []) + 
            (["**" + self.kw.tostr()] if self.kw else [])
        )

class _Def_(_Block, _FuncMixin):
    class _Decorator(_FuncMixin):
        def __init__(self, f, args=(), kwargs=(), a=None, kw=None):
            self.f, self.args, self.kwargs, self.a, self.kw = callargs(f, args, kwargs, a, kw)
        
        def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
            return " "*numspaces*indent + "@{0}({1})\n".format(self.f.tostr(), self.argstr())
    
    def __init__(self, name, args=(), kwargs=(), a=None, kw=None):
        _Block.__init__(self)
        
        self.name = symbol(name)
        self.args = symbol_list(args)
        self.kwargs = assign_map(kwargs)
        self.a = a and symbol(a) 
        self.kw= kw and symbol(kw)
        self.decorators = []
    
    def keyword(self):
        return "def"
    
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        lines = []
        for deco in reversed(self.decorators):
            lines.append(deco.tostr(numspaces, indent))
        lines.append(" "*numspaces*indent + self.keyword() + " {0}({1}):\n".format(self.name, self.argstr()))
        lines.append(self.stmts.tostr(numspaces, indent + 1))
        return "".join(lines)
        
    def decorator(self, *a, **kw):
        self.decorators.append(self._Decorator(*a, **kw))
        return self

class _Class_(_Def_):
    def keyword(self):
        return "class"

def callargs(f, args, kwargs, a, kw):
    return (
        expr(f),
        expr_list(args),
        assign_map(kwargs),
        (a and symbol(a)),
        (kw and symbol(kw)),
    )
    
class _Call(Sentence, _FuncMixin):
    def __init__(self, f, args=(), kwargs=(), a=None, kw=None):
        Sentence.__init__(self)
        self.f, self.args, self.kwargs, self.a, self.kw = callargs(f, args, kwargs, a, kw)
        
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        return " "*numspaces*indent + "{0}({1})".format(self.f.tostr(), self.argstr())
    
class _With_(_Block):
    def __init__(self, context, var=None):
        _Block.__init__(self)
        self.context = expr(context)
        self.var = var and symbol(var)
    
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        if self.var is None:
            return " "*numspaces*indent + "with {0}:\n".format(self.context.tostr()) + \
                   self.stmts.tostr(numspaces, indent + 1)
        else:
            return " "*numspaces*indent + "with {0} as {1}:\n".format(self.context.tostr(), self.var.tostr()) + \
                   self.stmts.tostr(numspaces, indent + 1)

class _Line(Sentence):
    def __init__(self, linestr):
        self._linestr = linestr
    
    def tostr(self, indent=0, numspaces=DEFAULT_NUMSPACES):
        return " "*numspaces*indent + self._linestr

class _Write:
    def __init__(self, str):
        self.lines = [_Line(line) for line in str.split("\n")]
    
    def tostr(self, indent=0, numspaces=DEFAULT_NUMSPACES):
        return "\n".join(line.tostr(indent, numspaces) for line in self.lines)
    
class _Pass_(_Line):
    def __init__(self):
        _Line.__init__(self, "pass")
    
class _Return_(_Line):
    def __init__(self, value=None):
        if value is None:
            _Line.__init__(self, "return")
        else:
            _Line.__init__(self, "return " + expr(value).tostr())

class _Yield_(_Line):
    def __init__(self, value=None):
        if value is None:
            _Line.__init__(self, "yield")
        else:
            _Line.__init__(self, "yield " + expr(value).tostr())
    
class _Import_(_Line):
    def __init__(self, moduleexpr, var=None):
        self.var = var and symbol(var)
        self.moduleexpr = expr(moduleexpr)
        if self.var is None:
            _Line.__init__(self, "import " + self.moduleexpr.tostr())
        else:
            _Line.__init__(self, "import " + self.moduleexpr.tostr() + " as " + self.var.tostr())
    
class _From_Import(_Line):
    def __init__(self, moduleexpr, targets="*"):
        self.targets = symbol_list(targets)
        self.moduleexpr = expr(moduleexpr)
        _Line.__init__(self, "from " + self.moduleexpr.tostr() + " import " + ", ".join(t.tostr() for t in self.targets))
    
    
class _Comment(_Line):
    def __init__(self, string):
        self.string = string
        _Line.__init__(self, "#" + self.string)

class _Dictdef(Sentence):
    def __init__(self, name, items):
        Sentence.__init__(self)
        self.items = [(expr(k), expr(v)) for k, v in get_items(items)]
        self.name = expr(name)
    
    def tostr(self, numspaces=DEFAULT_NUMSPACES, indent=0):
        if len(self.items) <&#061; 2:
            return " "*numspaces*indent + self.name.tostr() + " &#061; {" + \
                    ", ".join(k.tostr()+":"+v.tostr() for k, v in self.items) + \
                    "}"
        else:
            lines &#061; []
            lines.append(" "*numspaces*indent + self.name.tostr() + " &#061; {")
            for k, v in self.items:
                lines.append(" "*numspaces*(1 + indent) + k.tostr() + ":" + v.tostr() + ",") 
            lines.append(" "*numspaces*indent + "}")
            return "\n".join(lines)
            
def dictexpr(adict):
    return Expr("{" + ",".join(expr(k).tostr() +":" + expr(v).tostr() for k, v in get_items(adict)) + "}")

def main():
    code1 &#061; Code()
    
    code1.comment("encoding:shift-jis")
    code1.import_("math")
    code1.import_("os.path", "basename, splitext")
    code1.import_("os.path", "*")
    
    with code1.if_("0 &#061;&#061; 1") as if_:
        if_.let("x", "1")
        if_.call("int", ("x",))
        with if_.else_() as s:
            s.let("y", "1")
            s.call("spam", ("x", "y"), (("a", "A"), ("b", "B")), "args", "kwargs")
    
    code1.let("x", "0")
    code1.let("adict", dictexpr({repr("spam"): "1 + 2 * 4", repr("egg"): repr("hello!")}))
    code1.while_("1")
    
    code2 &#061; Code()
    with code2.while_("x !&#061; 1") as w:
        w.let("x", "1")
        with w.with_("open('spam.txt')", "fp") as s:
            s.let("x", "1")
            s.pass_()
        with w.with_("open('spam.txt')") as s:
            s.let("x", "1")
            s.pass_()
        with w.else_() as s:
            s.let("x", "0")
    
    with code2.for_("i, j, k", "product([1, 2], [3, 4], [5, 6])") as f:
        f.pass_()
    
    with code2.class_("Greeter") as class_:
        with class_.def_("hello", ["x"], [("huga", "12")]) as s:
            s.call("print", ["'hello'*x"])
    
    with code2.def_("hello", ["x"], [("huga", "12")]) as s:
        s.call("print", ["'hello'*x"])
    
    with code2.def_("hello", ["x"], [("huga", "12")], "a", "kw") as s:
        s.call("print", ["'hello'*x"])
    
    code2.dictdef("python", [("egg", "1"), ("ham", "2")])
    code2.dictdef("python", [("egg", "1"), ("ham", "2"), ("span", "3")])
    
    code1.append(code2)
    
    print(code1.tostr(numspaces&#061;2))
    
if __name__ &#061;&#061; "__main__":
    import doctest
    doctest.testmod()
    main()