pythonでSingletonパターン


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が使われているとは思わなかった。