PythonでSingletonパターン―インスタンスが高々1つしか存在しないクラス―を実現する、
ひとつの簡単な方法は、クラスの代わりにモジュールを使う事だ。
モジュールは一つしか存在しえないからだ。
しかしクラスをSingleton化する度に、一々selfを消したりglobalを書いて回るのは面倒くさい。
もっと楽な方法が無いかと、標準ライブラリのturtleモジュールを覗いてみた。
turtleはSingletonではないが、
import turtle turtle.fd(100) t = turtle.Turtle() t.fd()
のように、Turtleクラスのメソッドをモジュールから直接呼び出せる。
この仕組みを応用すれば、任意のクラスをモジュール化できるはずだ。
#turtle.py から抜粋 _tg_turtle_functions = ['back', 'backward' (中略) 'ycor'] class Turtle(RawTurtle): (略) def _getpen(): """Create the 'anonymous' turtle if not already present.""" if Turtle._pen is None: Turtle._pen = Turtle() return Turtle._pen def getmethparlist(ob): "Get strings describing the arguments for the given object" argText1 = argText2 = "" # bit of a hack for methods - turn it into a function # but we drop the "self" param. if type(ob)==types.MethodType: fob = ob.im_func argOffset = 1 else: fob = ob argOffset = 0 # Try and build one for Python defined functions if type(fob) in [types.FunctionType, types.LambdaType]: try: counter = fob.func_code.co_argcount items2 = list(fob.func_code.co_varnames[argOffset:counter]) realArgs = fob.func_code.co_varnames[argOffset:counter] defaults = fob.func_defaults or [] defaults = list(map(lambda name: "=%s" % repr(name), defaults)) defaults = [""] * (len(realArgs)-len(defaults)) + defaults items1 = map(lambda arg, dflt: arg+dflt, realArgs, defaults) if fob.func_code.co_flags & 0x4: items1.append("*"+fob.func_code.co_varnames[counter]) items2.append("*"+fob.func_code.co_varnames[counter]) counter += 1 if fob.func_code.co_flags & 0x8: items1.append("**"+fob.func_code.co_varnames[counter]) items2.append("**"+fob.func_code.co_varnames[counter]) argText1 = ", ".join(items1) argText1 = "(%s)" % argText1 argText2 = ", ".join(items2) argText2 = "(%s)" % argText2 except: pass return argText1, argText2 for methodname in _tg_turtle_functions: pl1, pl2 = getmethparlist(eval('Turtle.' + methodname)) if pl1 == "": print ">>>>>>", pl1, pl2 continue defstr = ("def %(key)s%(pl1)s: return _getpen().%(key)s%(pl2)s" % {'key':methodname, 'pl1':pl1, 'pl2':pl2}) exec defstr eval(methodname).__doc__ = _turtle_docrevise(eval('Turtle.'+methodname).__doc__)
getmethparlistが長いが、getmethparlistがやっていることは簡単で、
あるメソッドや関数の定義の引数部分の文字列と、呼び出しの引数部分の文字列を返すだけだ。
その後のfor methodname in _tg_turtle_functions: のループの中で、
まず、
defstr = ("def %(key)s%(pl1)s: return _getpen().%(key)s%(pl2)s" % {'key':methodname, 'pl1':pl1, 'pl2':pl2})
で、Turtleのメソッドを呼び出す、同名の関数のコードを作り、
exec defstr
で、関数を実体化させている。
標準ライブラリなので、
eval(methodname).__doc__ = _turtle_docrevise(eval('Turtle.'+methodname).__doc__)
で、docstringもしっかり定義している。
あとは、Turtleクラスを非公開にしてしまえば、Singletonパターンの例になるだろう。
まさか、標準ライブラリの中で黒魔術のexecが使われているとは思わなかった。