
[code]

    [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
    
[/code]

というリストを作りたかったのですが、

[code]

    >>> def makeboard():
        _ = [0 for i in range(5)]
        board = [_ for i in range(5)]
        return board
    >>> board = makeboard()
    >>> board
    [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
    >>> board[2][2] = 1
    >>> board
    [[0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0]]
    
[/code]

のようになってしまいました。シャローコピーであるためかと思い、

[code]

    >>> import copy
    >>> board = copy.deepcopy(makeboard())
    >>> board[2][2] = 1
    
[/code]

というふうに試してみても

[code]

    >>> board
    [[0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0]]
    
[/code]

と何も変わらず。どのようにすればよいでしょうか？理由も併せてお願いします。


CC BY-SA 4.0
2020-10-16T12:11:06.997
71256
2020-10-16T15:18:28.007
2020-10-16T12:16:08.837
3060
42290
question
python python3
Python3 でリスト内のリストの要素を一つだけ書き換えたい


[id(object)](https://docs.python.org/3.8/library/functions.html#id) を使用して
`board`変数(リスト型)の中身を調べてみます。

**id(object)**

> Return the “identity” of an object. This is an integer which is guaranteed
> to be unique and constant for this object during its lifetime. Two objects
> with non-overlapping lifetimes may have the same id() value.  
>  CPython implementation detail: **This is the address of the object in
> memory.**
[code]

    >>> def makeboard():
          _ = [0 for i in range(5)]
          board = [_ for i in range(5)]
          return board
    >>> board = makeboard()
    >>> for i, b in enumerate(board):
          print(f'board[{i}] = {hex(id(b))}')
    
    board[0] = 0x7f81d81bc840
    board[1] = 0x7f81d81bc840
    board[2] = 0x7f81d81bc840
    board[3] = 0x7f81d81bc840
    board[4] = 0x7f81d81bc840
    
[/code]

instance id(memory address)が全て同じである事が判ります。次に、質問にある様に `copy.deepcopy()`
を実行してみます。

[code]

    >>> import copy
    >>> board = copy.deepcopy(makeboard())
    >>> for i, b in enumerate(board):
          print(f'board[{i}] = {hex(id(b))}')
    
    board[0] = 0x7f98c8f85800
    board[1] = 0x7f98c8f85800
    board[2] = 0x7f98c8f85800
    board[3] = 0x7f98c8f85800
    board[4] = 0x7f98c8f85800
    
[/code]

こちらも instance id(memory
address)が全て同じです。理由は、[ここ](https://github.com/python/cpython/blob/master/Lib/copy.py#L201)と[ここ](https://github.com/python/cpython/blob/master/Lib/copy.py#L128)を見ると分かります。

[code]

    def _deepcopy_list(x, memo, deepcopy=deepcopy):
        y = []
        memo[id(x)] = y
        append = y.append
        for a in x:
            append(deepcopy(a, memo))
        return y
    
    d[list] = _deepcopy_list
    
    def deepcopy(x, memo=None, _nil=[]):
        """Deep copy operation on arbitrary Python objects.
        See the module's __doc__ string for more info.
        """
    
        if memo is None:
            memo = {}
    
        d = id(x)
        y = memo.get(d, _nil)
        if y is not _nil:
            return y
               :
    
[/code]

`_deepcopy_list()` 関数の `for a in x: append(deepcopy(a, memo))` において、`board[0]`
〜 `board[4]` の instance id(memory address)が全て同一ですので、memoize(メモ化)された `board[0]`
の instance id が `board[1]` 〜 `board[4]` に割り当てられてしまう結果になります(実質的に shallow copy
と同じ動作)。

以上を回避して、本来の意味での deepcopy を行なう方法ですが、`makeboard` 関数の実行時点で別個の instance
を作成する様にします。

[code]

    >>> def makeboard(w, h):
    >>>   return [[0]*w for _ in range(h)]
    
    >>> board = makeboard(5, 5)
    >>> board[2][2] = 1
    >>> print(board)
    [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
    
    >>> for i, b in enumerate(board):
    >>>   print(f'board[{i}] = {hex(id(b))}')
    
    board[0] = 0x7f8dcef49900
    board[1] = 0x7f8dcef49940
    board[2] = 0x7f8dcef49840
    board[3] = 0x7f8dcef49800
    board[4] = 0x7f8dcef45600
    
[/code]

その他、numpy を使う方法があります。

[code]

    >>> import numpy as np
    >>> board = np.zeros((5, 5), dtype=int)
    >>> board[2, 2] = 1
    >>> board
    array([[0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0],
           [0, 0, 1, 0, 0],
           [0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0]])
    
[/code]


CC BY-SA 4.0
2020-10-16T14:08:07.643
71259
2020-10-16T14:29:14.263
2020-10-16T14:29:14.263
71256
answer

[code]

    [obj for _ in range(n)] 
    
[/code]

と書いた場合同じ obj を参照する要素を n 個持つリストを生成します。

> _ = [0 for i in range(5)]  
>  board = [_ for i in range(5)]

これは

[code]

    board = [[0, 0, 0, 0, 0] for i in range(5)]
    
[/code]

ということであり、１つの [0, 0, 0, 0, 0] オブジェクトを参照する要素を5つ持つリストを生成してしまいます。

[code]

    board = [[0 for _ in range(5)] for _ in range(5)]
    
[/code]

とすれば、同じ 0 を参照する要素を5つ持つ [0, 0, 0, 0, 0] オブジェクトを5回生成し、これを要素にしたリストを生成できます。

[code]

    board = [[0] * 5 for _ in range(5)] 
    
[/code]

としても同じ 0 を参照する要素を5つ持つ [0, 0, 0, 0, 0] オブジェクトを5回生成し、これを要素にしたリストを生成できます。

なお、

[code]

    board = [[0] * 5] * 5
    
[/code]

とした場合は同じ 0 を参照する要素を5つ持つ [0, 0, 0, 0, 0] オブジェクトに対して、この１つの [0, 0, 0, 0, 0]
を参照する要素を5つ持つリストを生成してしまいます。

python の変数やコンテナの要素はすべて参照変数であり参照要素です。  
（リファレンスでは束縛と表現していますが他の言語の参照相当です。Java や C# の参照相当です。C++の参照とは違います。）  
変数やコンテナの要素には、実体のオブジェクトへの参照情報が格納されていると考えると分かりやすいと思います。


CC BY-SA 4.0
2020-10-16T15:18:28.007
71260
2020-10-16T15:18:28.007
41756
71256
answer