Arantium Maestum

プログラミング、囲碁、読書の話題

Pythonで参照渡し

最近こういうツイートを見た。

実際「値渡し」「参照渡し」という用語はCやC++などの「変数が多くの場合1対1で特定のメモリ箇所と対応している言語」に関しては自然な概念だが、Pythonのように変数がメモリ箇所と対応しない(=変数への再代入がメモリの書き換えではなく、指し示すメモリ箇所のすり替わりで行われる)言語だとあまりしっくりこない。

Pythonでは参照渡しはできない」というのは「Pythonの関数は呼び出し元の変数の指すオブジェクトを入れ替えることはできない」という意味で言われる:

x = 1
some_func(x)
print(x) // ここでx = 2になったりはしない

ただし引数として渡されるオブジェクトを操作して変化させた場合、その変化は関数呼び出し元からも観測できる:

x = [1]
some_func(x)
print(x) // ここでx = [2]になることはあり得る

この定義からすると「Pythonには参照渡しは存在しない」は概ね正しい。実用上はほぼ100%正しいといっていい。

のだが・・・

なんとかならんかな?というのがこのネタ記事の主旨である。

結論から言うと、特定の条件下では可能だ。

以下のコードがその一例:

def add1(x):
    x.cell_contents = x.cell_contents + 1

def f():
    x = 0
    def g():
        nonlocal x
    add1(g.__closure__[0])
    print(x) // 1と出力される!

何をしているかというと、f関数内で定義されている変数xを関数gのclosureに(nonlocal xという文で)捕捉させることで変数をcell objectにしている。あとは直接g.__closure__[0]とそのcell objectにアクセスして関数add1を適用すれば、呼び出し元のf関数内での変数xが書き換わる。add1は引数としてcell objectを期待しているけどf関数の外部で定義されていても問題ない。

残念ながらこの方法はモジュールのトップレベルでは使えない。f関数の中で定義された変数x、のようにglobalではない変数のみがこのように関数のclosureに捕捉され得る。

モジュールのトップレベルの変数を関数内から書き換えたいならglobals()を使えばいい:

def modify_var(varname):
    globals()[varname] += 1

x = 0
modify_var('x')
print(x) // 1と出力される

この二つの手法をうまく組み合わせて汎用的な参照渡し機構が作れないか試してみたが今のところうまくいっていない。残念なことである(?)