0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Python でも変数の参照渡しなどは理解しないといけないね、っておはなし

Posted at

背景

同僚が、Python でなんかバグに遭遇していた。

  • 波形データの合成処理で、髭が付加する

という現象で、再現性もあるってことだったので、デバッグしたらすぐじゃない?と傍観してたが、意外に苦戦してる様子だったので、コードを一式貰って解析してみた際の記録

結論

ミュータブル(mutable)、イミュータブル(immutable) の変数をよく理解せず使ってしまっていた。

具体的には・・

  • list の値を保存していこうとしているにも関わらず、参照渡しで保存したつもりになっていた。

通常であれば問題になることは少ないが、今回問題になったのは、

  • 引数を 参照渡しとして、処理側で変更して戻していた為

であった。

引数自体を毎回変えているにもかかわらず、それを利用する側でその認識が落ちていた為、初回保存データが消えており、二回目データが二重に記録されることで、波形に髭が出たように見えていた。
※最初のデータが記録されず、二回目のデータが二重に記録されていた

  • mutable, immutable という単語について

mute 音が出ている状態を定常とし、静かにする=変える、と捉えると分かりやすい。

im-: 否定の接頭語
mute: 音を消す、静かにする。変える(mutare)。語源はラテン語 mutus
able: 可能を示す接尾語

  • mutable: 変更可能な
  • immutable: 変更不可能な

詳細

具体的な 再現 sample code

mutable list

# グローバル変数の初期化
record_data_copy = []  # copyを使用
record_data_ref = []  # 参照を使用


# コピーを使用する関数
def record_with_copy(outdata):
    global record_data_copy
    copied_data = outdata.copy()
    if len(record_data_copy) == 0:
        record_data_copy = copied_data
    else:
        record_data_copy.extend(copied_data)


# 参照を使用する関数
def record_with_reference(outdata):
    global record_data_ref
    if len(record_data_ref) == 0:
        record_data_ref = outdata
    else:
        record_data_ref.extend(outdata)


def perform_modifications(outdata):
    global record_data_ref

    # コピーを使用する場合のテスト
    print("---- Copyを使用する場合 ----")
    outdata = []
    outdata[:] = [1, 2, 3]
    record_with_copy(outdata)  # 最初のデータ
    print(f"record_data_copy:{record_data_copy=}")  # 結果を表示
    outdata[:] = [4, 5, 6]
    record_with_copy(outdata)  # 次のデータ
    print(f"record_data_copy: {record_data_copy=}")  # 結果を表示

    # 参照を使用する場合のテスト
    print("\n---- 参照を使用する場合 ----")
    outdata = []
    outdata[:] = [1, 2, 3]
    record_with_reference(outdata)  # 最初のデータ
    print(f"record_data_ref: {record_data_ref=}")  # 結果を表示
    outdata[:] = [4, 5, 6]
    record_with_reference(outdata)  # 次のデータ
    print(f"record_data_ref: {record_data_ref=}")  # 結果を表示

    # 参照を使用する場合のテスト
    print("\n---- よくある参照でも問題ない場合 ----")
    outdata = []
    record_data_ref = []
    outdata = [1, 2, 3]
    record_with_reference(outdata)  # 最初のデータ
    print(f"record_data_ref: {record_data_ref=}")  # 結果を表示
    outdata = [4, 5, 6]
    record_with_reference(outdata)  # 次のデータ
    print(f"record_data_ref: {record_data_ref=}")  # 結果を表示l


outdata = []
perform_modifications(outdata)

print("\nAfter modifying outdata1:")
print(f"record_data_copy(after modification): {record_data_copy=}")  # 影響を確認
print(f"record_data_ref (after modification): {record_data_ref=}")  # 影響を確認


実行結果

result


---- Copyを使用する場合 ----
record_data_copy:record_data_copy=[1, 2, 3]
record_data_copy: record_data_copy=[1, 2, 3, 4, 5, 6]

---- 参照を使用する場合 ----
record_data_ref: record_data_ref=[1, 2, 3]
record_data_ref: record_data_ref=[4, 5, 6, 4, 5, 6]

---- よくある参照でも問題ない場合 ----
record_data_ref: record_data_ref=[1, 2, 3]
record_data_ref: record_data_ref=[1, 2, 3, 4, 5, 6]

After modifying outdata1:
record_data_copy(after modification): record_data_copy=[1, 2, 3, 4, 5, 6]
record_data_ref (after modification): record_data_ref=[1, 2, 3, 4, 5, 6]

解説

問題点

以下のように、初回データが消えている

---- 参照を使用する場合 ----
record_data_ref: record_data_ref=[1, 2, 3]
record_data_ref: record_data_ref=[4, 5, 6, 4, 5, 6]

以下で、outdata 自体を置き換えている

outdata[:] = [4, 5, 6]

record_data_ref を以下で保存したように処理しているが、
単に outdata の参照を保持しているだけである為、二回目の呼び出しによって同一データを concatenate() することになっているのが原因であった。

# 参照を使用する関数
def record_with_reference(outdata):
    global record_data_ref
    if len(record_data_ref) == 0:
        record_data_ref = outdata
    else:
        record_data_ref.extend(outdata)

ということで、以下のような対処策がある。個人的には前者かな

  1. 保存する際に、参照渡しではなく、データコピーを行う
  2. 引数に戻す処理と、そこから利用する変数を別にする

あとがき

python でやってると、あまり参照渡しとか意識することが無いからこそ、のバグだったのかなと

こういう点では組込出身者の方が、メモリを意識しているところに一日の長がある、筈 :thinking:

まぁ、結局はその人の性格と、姿勢・・かなぁとも :sweat_smile:

0
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?