Pythonのマングリングについて

Pythonのマングリングとは?

Pythonではクラス内に __で始まり、 __ で終わらない名前をマングリングします。
マングリングとは例えば

class T:
   def foo(self):
     self.__bar = "bar"
     print(self.__bar)
     print(self._T__bar)

T().foo()

としたときに、barが2回出力されます。Pythonではクラス内で定義された __barを特別扱いして self._T__bar とすることでprivateメンバ変数に見せかける仕組みのようです。もちろんクラス内からは self.__bar でアクセス出来ます。

ルールは簡単。__ がついて __ で終わらない変数を _クラス名__変数名 にするだけです。

マングリングの闇

import編

さて、上の例だとselfの変数にマングリングが行われましたが、実はマングリングはクラス内で定義されたもの 全て に対して行われます。

$ ls | grep test
__test2
_T__test1

というディレクトリがあった場合、

class T:
   import __test1
   import __test2

を実行すると

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in T
ImportError: No module named '_T__test2'

こうなるわけです。
import __test1 はマングリングされて _T__test1 名前空間パッケージを見に行くため正常にimportされます。
import __test2 はマングリングされて _T__test2 を見に行きますが、__test2 は見ないため ImportError となります。
これバグではとちょっと思ったり…

import __test1 を逆アセンブルします。

  3           8 LOAD_CONST               1 (0)
             10 LOAD_CONST               2 (None)
             12 IMPORT_NAME              3 (_T__test1)
             14 STORE_NAME               3 (_T__test1)
             16 LOAD_CONST               2 (None)
             18 RETURN_VALUE

つまり、 import __test1 はコンパイル時に import _T__test1 にされるのです。

exec編

class Foo:
    def __init__(self):
        exec("self.__foo = 2")
    def bar(self):
        print(self.__foo)

Foo().bar()

を実行すると

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in bar
AttributeError: 'Foo' object has no attribute '_Foo__foo'

となるわけです。つまり exec はマングリングされないんですね。マングリングはスクリプトのコンパイル時に行われるので exec だとマングリングされません。

クラススコープについて

import ですでにやりましたが、クラススコープの識別子も変換しないと、メソッドから参照できなくなるのでとにかく変換します。

class A:
  import sys as __sys
  print(__sys)
  print(_A__sys)
  def f(self):
    print(self.__sys)
     print(self._A__sys)

A().f()

<module 'sys' (built-in)>
<module 'sys' (built-in)>
<module 'sys' (built-in)>
<module 'sys' (built-in)>

となります。

何でもかんでも変換する

マングリングに例外はありません。識別子は全部変換されます。

class A:
  def x(self):
    __y = 100
    print(dir())

A().x()

['_A__y', 'self']

となります。

class A:
   def f(self):
      import sys
      sys.__omg = 100

A().f()

import sys

sys._A__omg
100

もはや意味がわかりませんね。

global編

当然globalもです。

class A:
  global __x
  __x = 100

_A__x

100

oh…

引数編

class A:
  def f(self):
    def a(__arg):
      print(dir())
      a(1)

A().f()

['_A__arg']

(‘A`)

最後に

マングリングの仕様について、如何だったでしょうか?こんな知識嫌がらせにしか使えないですが、もし遭遇した場合には役に立つことを願います。
私はcafebabepyの実装にこれを盛り込まないといけないので憂鬱です。。