Published On: 2014-07-03|Last Updated: 2014-07-03|Categories: Python|Tags: |

Python でスタックフレームが積まれるタイミングは私の知っている限りでは以下の 3 つです。(eval と exec 等もありますがここでは無視します。)

  1. モジュールが読み込まれたとき
  2. 関数が呼び出されたとき (import も含む)
  3. class が定義されるとき (class の内側)

1 と 2 はスタックフレームが生成されるのは自然ですし、もしスタックフレームを直接触る羽目になってもさほど困ることはありません。

しかし 3 を扱うのはかなり困難です。まず定義される class オブジェクトはこの時点では存在しません (このスタックフレームの名前空間を用いて class が定義されるため)。黒魔術を使うためにはこのスタックフレームの locals にアクセスする必要があるのですが、スタックフレームだけでは 1、2 との区別ができません。

以上がこれまでの認識だったのですが、スタックフレームのコードオブジェクトを片っ端から dis してみたら固有のプリフィックスというかシグネチャと言える準備コードがあるようです。

Python 3.4.1 (v3.4.1:c0e311e010fc, May 18 2014, 10:38:22) [MSC v.1600 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import sys
>>> import dis
>>> class Foo:
        dis.dis(sys._getframe().f_code)


  1           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)
              6 LOAD_CONST               0 ('Foo')
              9 STORE_NAME               2 (__qualname__)

  2          12 LOAD_NAME                3 (dis)
             15 LOAD_ATTR                3 (dis)
             18 LOAD_NAME                4 (sys)
             21 LOAD_ATTR                5 (_getframe)
             24 CALL_FUNCTION            0 (0 positional, 0 keyword pair)
             27 LOAD_ATTR                6 (f_code)
             30 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             33 POP_TOP
             34 LOAD_CONST               1 (None)
             37 RETURN_VALUE
>>> class Foo: print('%r' % sys._getframe().f_code.co_code[:12])

b'ex00x00Zx01x00dx00x00Zx02x00'

1 行目の class Foo: でバイトコードが発生していることがわかります。この b’ex00x00Zx01x00dx00x00Zx02x00′ がシグネチャとなります。3 番目の LOAD_CONST の引数は必ず 0 になるようです。

ちなみにバイトコードの意味は次のような感じです。

global __name__
__module__ = __name__
__qualname__ = 'Foo'

試しに次のようなタチの悪いコードを作ってみましたが、こちらにもあるようにユーザーコードでは STORE_NAME は生成されないようです。

>>> def foo(): global __name__; __module__ = __name__; __qualname__ = 'harassment'

>>> dis.dis(foo)
  1           0 LOAD_GLOBAL              0 (__name__)
              3 STORE_FAST               0 (__module__)
              6 LOAD_CONST               1 ('harassment')
              9 STORE_FAST               1 (__qualname__)
             12 LOAD_CONST               0 (None)
             15 RETURN_VALUE

これが何のためかというとデコレーターの引数が function か class か method かの判別をするためです。要するに黒魔術のためで、普通の人は frame object だの code object だのに触ってはいけません。locals()、globals() に触るのも避けましょう。おもむろにこんなコードを考え付くようになってしまいます。

>>> (lambda **kw: kw)(**{'dotted.name': 'hello', 'separated name': 'world'})
{'separated name': 'world', 'dotted.name': 'hello'}

関連