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) <= 2: return " "*numspaces*indent + self.name.tostr() + " = {" + \ ", ".join(k.tostr()+":"+v.tostr() for k, v in self.items) + \ "}" else: lines = [] lines.append(" "*numspaces*indent + self.name.tostr() + " = {") 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 = Code() code1.comment("encoding:shift-jis") code1.import_("math") code1.import_("os.path", "basename, splitext") code1.import_("os.path", "*") with code1.if_("0 == 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 = Code() with code2.while_("x != 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=2)) if __name__ == "__main__": import doctest doctest.testmod() main()