Pythonで参照渡し
最近こういうツイートを見た。
なんか参照渡しでツイッター検索すると、Pythonに参照渡しがあるという一大宗教があるらしい
— (call me #'knjname) (@knjname) August 22, 2021
誰が教祖だ?
実際「値渡し」「参照渡し」という用語は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と出力される
この二つの手法をうまく組み合わせて汎用的な参照渡し機構が作れないか試してみたが今のところうまくいっていない。残念なことである(?)