somolyot
@somolyot (somolyot)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

【Python】 自分で作った関数の引数にリストを代入すると、そのオリジナルのリストが改変されてしまうのを防ぐ方法

Q&A

Closed

あまりにも初歩的な質問で申し訳ないのですが…

以下のように種類の違うソートの関数を自分で作って、そのそれぞれの引数にリストを代入して違いを検討したいと思っています。
しかし、一つ目の関数を通った時点でオリジナルのリストがソートされてしまって困っています。
オリジナルのリストはそのままで、関数の結果をそれぞれ取得するにはどこを直せばいいのかご教授いただけるとありがたいです。

sort.py
def bblsrt(cards):
    cards1 = cards
    for i in range(len(cards1)):
        for j in range(len(cards1) - 1, i, -1):
            if cards1[j][1] < cards1[j - 1][1]:
                cards1[j], cards1[j - 1] = cards1[j - 1], cards1[j]
    return cards1


def slcsrt(cards):
    cards2 = cards
    for i in range(len(cards2)):
        minj = i
        for j in range(i, len(cards2)):
            if cards2[j][1] < cards2[minj][1]:
                minj = j
        cards2[i], cards2[minj] = cards2[minj], cards2[i]
    return cards2


n = int(input())
C = list(input().split())
print(C)
print(slcsrt(C))
print(C)
print(bblsrt(C))

入力

5
H4 C9 S4 D2 C3

出力

['H4', 'C9', 'S4', 'D2', 'C3']
['D2', 'C3', 'S4', 'H4', 'C9']
['D2', 'C3', 'S4', 'H4', 'C9']
['D2', 'C3', 'S4', 'H4', 'C9']
1

3Answer

どこかで「Pythonでは、変数はオブジェクトに結びつけられた名前にすぎない。」というようなことを聞いたような気もするのですが、それと関係あるのでしょうか。

Pythonでは、数値 も 関数 も クラス も メソッド も すべて オブジェクト変換 されます。
オブジェクト には オブジェクトidオブジェクト参照値)が付与されます。
変数 は、変数名辞書キー として オブジェクトid辞書値 として 変数辞書 に格納されます。
変数 は オブジェクトid を 保持 するだけなので、関数 だろうと クラス だろうと、オブジェクト なら何でも 代入 できます。

Pythonの 変数代入 は、オブジェクトidコピー するだけで、オブジェクト は コピーせず に 共有 します。
関数引数変数代入 と同じで、オブジェクト共有 します。
変数 に別の オブジェクト代入 すれば、変数の オブジェクトid つまり 参照先 が変わるだけで、それまでに参照していた オブジェクト には 影響しません。
array[0] = 123 のような 代入 は、変数 array参照 して オブジェクト を取り出し、オブジェクト内[0] に 値(オブジェクトid) を 代入 します。 これは、オブジェクト共有 しているすべての 変数影響が及ぶ ことになります。

オブジェクトの変更を他に影響影響させないようにするには、変数に代入するかオブジェクトをコピーする必要があります。
変数代入 しているのか、変数参照 して オブジェクト代入 しているのか、しっかり 理解・認識・区別 する必要があります。

参考: https://docs.python.org/ja/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference

数値代入の場合、変数に代入するので呼び出し元に影響しない:
image.png

実行結果
>>> def main():
...     data = 123
...     print("main:", id(data))
...     sub(data)
...     print("main:", id(data), vars())
...
>>> def sub(arg):
...     print("sub:", id(arg))
...     arg = 456
...     print("sub:", id(arg), vars())
...
>>> main()
main: 15861393760
sub: 15861393760
sub: 123145300880816 {'arg': 456}
main: 15861393760 {'data': 123}

リストの場合でも、変数に代入すれば呼び出し元に影響しない:
image.png

実行結果
>>> def main():
...     data = [1, 2, 3]
...     print("main:", id(data))
...     sub(data)
...     print("main:", id(data), vars())
...
>>> def sub(arg):
...     print("sub:", id(arg))
...     arg = [4, 5, 6]
...     print("sub:", id(arg), vars())
...
>>> main()
main: 123145300973960
sub: 123145300973960
sub: 123145300035208 {'arg': [4, 5, 6]}
main: 123145300973960 {'data': [1, 2, 3]}

オブジェクトに代入すると呼び出し元に影響する:
image.png

実行結果
>>> def main():
...     data = [1, 2, 3]
...     print("main:", id(data))
...     sub(data)
...     print("main:", id(data), vars())
...
>>> def sub(arg):
...     print("sub:", id(arg))
...     arg[2] = 4
...     print("sub:", id(arg), vars())
...
>>> main()
main: 123145300973960
sub: 123145300973960
sub: 123145300973960 {'arg': [1, 2, 4]}
main: 123145300973960 {'data': [1, 2, 4]}
4Like

Comments

  1. @somolyot

    Questioner

    たいへん丁寧な説明ありがとうございます。大分理解できました。
    関数内でリストの中身を操作して新しいモノを作る時はしっかり意識しないといけませんね。

Python だと list.sort() は破壊的、sorted(list) は 非破壊 ですね。
オブジェクトのメソッドは、オブジェクト自身(self)を破壊的に処理する。
一般関数は、引数のデータを破壊しないように処理を書く。

list.sort() は、「リストよ、ソートせよ!」 (sortは命令形動詞、動詞の原形)
data = sorted(list) は「ソートされたリスト を 変数 に 代入」

2Like

Comments

  1. @somolyot

    Questioner

    そうですね。「破壊・非破壊」の視点では考えていませんでしたが、いつもなんかこわいので`sorted`しか使っていませんでした…。
    この例のように機能の違いが関数名に端的にあらわされていると嬉しいですね。

Comments

  1. @somolyot

    Questioner

    ありがとうございます。おかげさまで所望のものが出来上がりました。
    しかし、何故関数を適用するとオリジナルの値が改変されてしまうのかはまだよく理解できていません。今まで何となく「関数はオリジナルの値を使って新しい値を作り出すモノ。」という認識だったのですが、そうではないみたいですね。
    どこかで「Pythonでは、変数はオブジェクトに結びつけられた名前にすぎない。」というようなことを聞いたような気もするのですが、それと関係あるのでしょうか。

Your answer might help someone💌