がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

いわゆる__callと__getマジックメソッド

PHPで言うところのマジックメソッド、Pythonの世界では「特殊メソッド」って呼称するようですねぇ、とかいう細かいナレッジをぶっこみつつ。
ちなみにPythonは3でございます。「2とかありえない」と、複数名のPythonist(Pythonista?)に言われたので、割と素直に従っておりまする。


ちょいと扱いたいので調べてみたのですが……案外に厄介だったので、いくつか試行錯誤。
まずそもそもとして「存在しないメソッド/プロパティをcallした時に呼ばれる特殊メソッド」が、__getattr__一つなので、まずその辺で「メソッドcallなのかプロパティcallなのか」を把握しなきゃいけないという以下略。
いやまぁそもそもとして「publicなプロパティとかありえないからプロパティにアクセスしようとした愚か者に百年の呪いあれ」とか言いきってもよさそうな気がわりとビシバシとしてはいるのですがいやマジで。


ただまぁ一応技術者として「できない」よりは「使わないけどできるけど使ったものに災いあれって事で使わない」ほうがよろしいかなぁ、と思うので「どうしても無理ならあきらめる」くらいの緩い温度感で、さくり、と。


とりあえずどっかググって書いた初手。
幾分「いろいろやってる感がある」のが、若干もにょりつつ、まず「なんとかはなるんだよなぁ」というあたりを確保。

#
import inspect

#
class hoge(object):

  def dummy(self, *args):
    print('__call: {}({})'.format(self.name, args))
    return "hogehoge"

  def __getattr__(self, name):
    #print("{}".format(name))

    # ここがもうちょっと綺麗だとうれしいんだろうなぁ……
    frame = inspect.getframeinfo(inspect.currentframe().f_back)
    code = ''.join([line for line in frame.code_context])
    code = ''.join(code.split()) + ' '  # remove all spaces and append sentinel
    s = code[code.index(name) + len(name)]
    #print("::{}".format(s))
    #
    if s == '(':
      self.name = name
      return self.dummy
    else:
      print("error")

h = hoge()

#
s = h.test(1,2,3)
print("return is {}".format(s))
s = h.test2()
print("return is {}".format(s))

s = h.foo
print("return is {}".format(s))


少しだけ試行錯誤してみて、コード量を落ち着けてみた……つもりのコード

import inspect

class X(object):
  def __getattr__(self, key):
    class MethodHook:
      def __init__(self, key):
        self.key = key
      def __call__(self, *args, **kwargs):
        print('__call: "{}", {}, {}'.format(self.key, args, kwargs))

    frame = inspect.getframeinfo(inspect.currentframe().f_back)
    code = ''.join([line for line in frame.code_context])
    code = ''.join(code.split()) + ' '  # remove all spaces and append sentinel
    s = code[code.index(key) + len(key)]
    if s == '(':
      return MethodHook(key)
    else:
      print('__get: "{}"'.format(key))
      return "placeholder"

#
x = X()
x.undef_method(1, 2, 3, a1="Hello", a2="World")
x.undef_method2()

#
y = x.hoge
print("y={}".format(y))


色々調べてみるともうちょっとすっきり……なんだけど、プロパティが取れない………

class A:
    def __init__(self):
        self.testProperty = 'text of testProperty'

    def __getattr__(self, name):
        def function(*args):
            #print("You tried to call a method named: %s" % name)
            print('__call: {}({})'.format(name, args))
            print(self.testProperty)
        return function

a = A()
a.test()
a.test2(1,2,3)
a.test2([1,2,3])
val = a.hoge
print ("hoge is {}".format(val))


もうちょい試行錯誤して、概ね一段落……なんだけど、戻り値の型が固定になるなぁ……

class Magic(object):
    def __getattr__(self, name):
        print("__getattr__: {}".format(name))
        class Callable(int):
            def __call__(self, *arguments):
                print("call {}: {}".format(name, arguments))
        return Callable(100)

m = Magic()
m.test()
m.test(20)
m.test2(1,2,3,4,5)
print(m.x)


まぁ「プロパティだしなぁ」ってんで、雑な解決策を考えてみたら通ったので、まぁこの辺かなぁ一旦、的な。

class Magic(object):
    def __getattr__(self, name):
        print("__getattr__: {}".format(name))
        class CallableI(int):
            def __call__(self, *arguments):
                print("call {}: {}".format(name, arguments))
        class CallableS(str):
            pass
        if (name == 'test_s'):
            return CallableS('test')
        # else
        return CallableI(100)

m = Magic()
m.test()
m.test(20)
m.test2(1,2,3,4,5)
print(m.x)
print(m.test_s)

# ところで、存在しないプロパティでも受け取るんだねぇ……
m.hoge = 999
print(m.hoge)


とりあえず初手で欲しかった「__call」はまぁまぁな感じで実装ができたので。

__get、ぶっちゃけると一番よくやるのは「攻性防御用 http://d.hatena.ne.jp/gallu/20140119/p1 」なのでw
適当に(第一種)ホワイトリストで「許可されているメソッド名」以外は「例外ぶん投げる」とかで処理が終わりそうでもあるので、あんまり問題なさそうな気も、わりとむんむんwww
(ゲーム作成初期で、時々「実用で」__get使いますが)


まぁとりあえず当初の目的は達成したので、備忘録的に、めも。

「こうやったらもっとエレガントぢゃ!!」とかいう突っ込みがあったら歓迎いたしますので、是非。