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の実装にこれを盛り込まないといけないので憂鬱です。。