Python3 でリスト内のリストの要素を一つだけ書き換えたい

[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]

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




[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]

