1. ホーム
  2. python

[解決済み] 変数を参照渡しする方法を教えてください。

2022-03-16 14:12:18

質問

Pythonのドキュメントでは、パラメータが参照で渡されるのか値で渡されるのかについて不明確なようで、次のコードは変更されない値「オリジナル」を生成します。

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change(self.variable)
        print(self.variable)

    def change(self, var):
        var = 'Changed'

実際の参照で変数を渡すために何かできることはないでしょうか?

どのように解決するのですか?

引数は 代入で渡す . その根拠は2つあります。

  1. 渡されたパラメータは、実際には 参照 オブジェクトへの参照 (ただし、参照は値で渡される)。
  2. あるデータ型はミュータブルだが、他のデータ型はミュータブルでない

だから

  • を渡すと ミュータブル オブジェクトをメソッドに渡すと、メソッドはその同じオブジェクトへの参照を取得し、思う存分変異させることができます。しかし、メソッド内で参照を再バインドすると、外部スコープはそのことについて何も知らず、終了した後も外部参照は元のオブジェクトを指すことになります。

  • を渡すと 不変 オブジェクトをメソッドに渡したとしても、外部参照を再バインドすることはできませんし、オブジェクトを変異させることさえできません。

さらにわかりやすくするために、いくつかの例を挙げてみましょう。

リスト - ミュータブルな型

あるメソッドに渡されたリストを変更してみましょう。

def try_to_change_list_contents(the_list):
    print('got', the_list)
    the_list.append('four')
    print('changed to', the_list)

outer_list = ['one', 'two', 'three']

print('before, outer_list =', outer_list)
try_to_change_list_contents(outer_list)
print('after, outer_list =', outer_list)

出力します。

before, outer_list = ['one', 'two', 'three']
got ['one', 'two', 'three']
changed to ['one', 'two', 'three', 'four']
after, outer_list = ['one', 'two', 'three', 'four']

渡されたパラメータは outer_list そのため、Mutating List メソッドを使ってそれを変更し、その変更を外部スコープに反映させることができます。

では、パラメータとして渡された参照を変更しようとすると、どうなるか見てみましょう。

def try_to_change_list_reference(the_list):
    print('got', the_list)
    the_list = ['and', 'we', 'can', 'not', 'lie']
    print('set to', the_list)

outer_list = ['we', 'like', 'proper', 'English']

print('before, outer_list =', outer_list)
try_to_change_list_reference(outer_list)
print('after, outer_list =', outer_list)

出力します。

before, outer_list = ['we', 'like', 'proper', 'English']
got ['we', 'like', 'proper', 'English']
set to ['and', 'we', 'can', 'not', 'lie']
after, outer_list = ['we', 'like', 'proper', 'English']

というのは the_list パラメータは値で渡されるため、新しいリストを代入しても、メソッドの外のコードには何の影響もありません。そのため the_list はコピーであった。 outer_list のリファレンスを持っていました。 the_list は新しいリストを指すようになりましたが outer_list を指します。

文字列 - 不変の型

イミュータブルなので、文字列の中身を変更することはできない

では、参照先を変更してみましょう。

def try_to_change_string_reference(the_string):
    print('got', the_string)
    the_string = 'In a kingdom by the sea'
    print('set to', the_string)

outer_string = 'It was many and many a year ago'

print('before, outer_string =', outer_string)
try_to_change_string_reference(outer_string)
print('after, outer_string =', outer_string)

出力します。

before, outer_string = It was many and many a year ago
got It was many and many a year ago
set to In a kingdom by the sea
after, outer_string = It was many and many a year ago

ここでも the_string パラメータは値で渡されるため、新しい文字列を代入してもメソッドの外のコードからは何も見えません。また the_string はコピーであった。 outer_string のリファレンスを持っていました。 the_string が新しい文字列を指していることを確認する必要があります。 outer_string を指します。

これで少しはスッキリしたかな。

EDITです。 これは、@David が最初に尋ねた質問、"実際の参照で変数を渡すためにできることはあるか、" に対する答えになっていないことが指摘されています。それを解決しましょう。

どうすれば回避できるのか?

Andreaの回答が示すように、新しい値を返すことができます。この方法では、渡されたものを変更することはできませんが、必要な情報を取り出すことができます。

def return_a_whole_new_string(the_string):
    new_string = something_to_do_with_the_old_string(the_string)
    return new_string

# then you could call it like
my_string = return_a_whole_new_string(my_string)

どうしても戻り値を使いたくない場合は、値を保持するクラスを作成して関数に渡すか、リストのような既存のクラスを使用することができます。

def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change):
    new_string = something_to_do_with_the_old_string(stuff_to_change[0])
    stuff_to_change[0] = new_string

# then you could call it like
wrapper = [my_string]
use_a_wrapper_to_simulate_pass_by_reference(wrapper)

do_something_with(wrapper[0])

少し面倒な気もしますが。