如何在Python中分配数组大小

6

我是一名Python新手。我已经搜索了很多解决方案,但都不太符合我的需求。我想在程序开始时分配一个空数组,它有a行和b列。我想出了一个解决方案,但遇到了一个意料之外的问题。这就是我的解决方案:

a = 7
b = 5
array_ab = [['?'] * b] * a

生成

[['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?']]

然而,如果我尝试更改单个元素,它会将每一行都视为相同的对象,并将整列有效地更改为该元素。例如:

array_ab[4][2] = '1'

产生

[['?', '?', '1', '?', '?'],
 ['?', '?', '1', '?', '?'],
 ['?', '?', '1', '?', '?'],
 ['?', '?', '1', '?', '?'],
 ['?', '?', '1', '?', '?'],
 ['?', '?', '1', '?', '?'],
 ['?', '?', '1', '?', '?']]

显然,我需要一种比乘法更好的方法来创建一个空数组。在Python中有没有解决方案?(在FORTRAN中这是如此简单!)


4
你可能想要查看numpy。它是一个处理数组的模块。 - kylieCatt
它不会将每一行视为相同的对象,每一行都是同一个对象;乘法只是创建了对该对象的多个引用。在Python中,通常不需要预先分配列表,它们会随着您附加项目而增长。因此,您对基本问题的方法可能不理想-您计划做什么? - Tim Pietzcker
Tim,感谢您的澄清!我正在编写一个文本冒险游戏,并希望创建一个小的“迷你地图”网格,它开始是空白的,但随着您的移动而填充,将'?'更改为'1'。 - JohannesKepler
@JohannesKepler:好的,既然是这样,Shurane的答案确实是处理这个问题的最佳方式。 - Tim Pietzcker
5个回答

7

Something along the lines of

In [12]: a = 5

In [13]: b = 7

In [14]: array_ab = [ [ '?' for i in xrange(a) ] for j in xrange(b) ]

In [15]: array_ab
Out[15]:
[['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?']]

In [16]: array_ab[4][2] = '1'

In [17]: array_ab
Out[17]:
[['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '1', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?']]

特别地,您正在使用列表推导式xrange


4

使用列表推导式[['?'] * b for _ in range(a)]

In [1405]: a = 7
      ...: b = 5
      ...: array_ab = [['?'] * b for _ in range(a)]

In [1406]: array_ab
Out[1406]: 
[['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?']]

In [1407]: array_ab[4][2] = '1'

In [1408]: array_ab
Out[1408]: 
[['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '1', '?', '?'],
 ['?', '?', '?', '?', '?'],
 ['?', '?', '?', '?', '?']]

['?']*b 是安全的,因为 '?' 是一个不可变字符串,改变字符串列表的元素不会影响其他元素:

In [1419]: a=['a']*5

In [1420]: a[2]=123

In [1421]: a
Out[1421]: ['a', 'a', 123, 'a', 'a']

虽然 [[1,2]]*3 很危险,因为列表是 可变的,但这等同于:

In [1427]: b=[1,2]
      ...: a=[b,b,b] #a is just a list of b's references
      ...: print a
[[1, 2], [1, 2], [1, 2]]

修改内部列表 b 的元素不会影响到 a 的内容。


你不应该仍然通过数组乘法获得镜像吗? - Ehtesh Choudhury
@Shurane,不行,因为['?']*b会生成一个不可变字符串列表。 - zhangxaochen

4
如果您要将数组用于数值计算,并且可以使用外部库,则建议查看 numpy。 它提供了一个数组类和许多有用的数组操作。
创建一个MxN数组很简单。
import numpy as np

A = np.empty((M,N)) # Empty array
B = np.zeros((M,N)) # Array filled with zeros

索引的操作就像这样进行:
x = A[i,j]
A[4,2] = 1

row1 = A[0, :] # or simply A[0]

2
对于任何计算密集型任务,在 numpy 上加 1。 - Ehtesh Choudhury
1
如果您知道数组的类型,甚至可以执行np.empty((M,N),dtype='int')等操作... - ntg
他正在使用字符串数组:非数字数据类型如何与numpy配合?任意数据类型呢? - WestCoastProjects

3
问题出在这里:
array_ab = [['?'] * 4] * 3

问题的原因是Python选择通过对象引用传递列表。因为列表是可变对象。但由于列表可能变得非常大,Python选择仅使用一个引用(C术语中的“指针”),而不是在内存中移动整个列表。如果将一个变量分配给另一个变量,则只将其引用分配给它。这意味着您可以有两个指向内存中同一列表的变量。
>>> a = [1]
>>> b = a
>>> a[0] = 2
>>> print b
[2]

所以在你的第一行代码中,你有['?'] * 4。现在['?']是指向内存中的值?的指针,当你乘以它时,你会得到4个指向同一内存位置的指针。但是,当你改变其中一个值时,Python知道指针需要改变指向新值:
>>> a = 4 * ['?']
>>> a
['?', '?', '?', '?']]

您可以验证列表中元素的id:

>>> [id(v) for v in a]
[33302480, 33302480, 33302480, 33302480]
>>> a[0] = 1
>>> a
[1, '?', '?', '?']

问题出现在你复制这个列表的时候 - 你会得到四个指向该列表的指针。 当你改变一个列表中的值时,所有四个列表都会一起改变。
建议的方法是先创建一个所需长度的列表,然后用新创建的列表填充每个元素:
>>> A = [None] * 3
>>> for i in range(3):
...     A[i] = [None] * 4
...
>>> A
[[None, None, None, None], [None, None, None, None], [None, None, None, None]]
>>>

这会生成一个包含3个长度为4的不同列表的列表。 或者您可以使用列表推导式:
w, h = 4, 3
A = [[None] * w for i in range(h)]
[[None, None, None, None], [None, None, None, None], [None, None, None, None]]

编辑2

根据您的标题,您无法事先为列表分配确切的内存。Python列表使用某种算法来超额分配列表大小,以供未来的额外增长。

来自源代码:

 /* This over-allocates proportional to the list size, making room
 * for additional growth.  The over-allocation is mild, but is
 * enough to give linear-time amortized behavior over a long
 * sequence of appends() in the presence of a poorly-performing
 * system realloc().
 * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
 */

1
+1 对于解释和解决方案。请提供需要翻译的具体内容。 - desbo
1
非常有用的信息,谢谢!列表推导式绝对是最好的解决方案。 - JohannesKepler
1
提到id()加1,这对查看对象的引用非常有用。 - Ehtesh Choudhury

0

试试这个:

a = 7
b = 5
array_ab = []
for i in range(a):
    array_ab.append([])
    for j in range(b):
        array_ab[i].append('?')

这段代码:

array_ab[4][2] = '1'

array_ab 更改为:

[['?', '?', '?', '?', '?'], 
['?', '?', '?', '?', '?'], 
['?', '?', '?', '?', '?'], 
['?', '?', '?', '?', '?'], 
['?', '?', '1', '?', '?'], 
['?', '?', '?', '?', '?'], 
['?', '?', '?', '?', '?']]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接