对象的numpy数组

31 投票
3 回答
77399 浏览
提问于 2025-04-16 11:06

我正在尝试用Python实现一个晶格模型的模拟(晶格玻尔兹曼)。这个晶格的每个点都有一些属性,并且会根据特定的规则与邻近的点进行交互。我觉得可以创建一个类,把所有的属性放在里面,然后用这个类的实例来构建一个网格。(由于我对Python不太熟悉,这个想法可能并不好,所以欢迎大家对我的方法提出意见。)

下面是我正在做的一个简单示例:

class site:
    def __init__(self,a,...):
        self.a = a
        .... other properties ...
    def set_a(self, new_a):
        self.a = new_a

现在我想处理一个这样的2D或3D晶格(网格),所以我尝试了以下方法(这里是一个2D的3x3网格作为例子,但在模拟中我需要的规模是超过1000x1000x1000的)

lattice = np.empty( (3,3), dtype=object)
lattice[:,:] = site(3)

现在的问题是,每个晶格点都指向同一个实例,比如说:

lattice[0,0].set_a(5)

这也会把lattice[0,2].a的值设置为5。这种行为是我不想要的。为了避免这个问题,我可以遍历每个网格点,逐个分配对象,比如:

for i in range(3):
    for j in range(3):
        lattice[i,j] = site(a)

但是有没有更好的方法(不涉及循环)来将对象分配给多维数组呢?

谢谢!

3 个回答

8

我不确定这样做是否更好,但作为一种替代方案,你可以写下面的代码:

lattice = np.empty( (3,3), dtype=object)
lattice.flat = [site(3) for _ in lattice.flat]

这段代码应该适用于任何形状的格子。

9

你需要知道的是,Python把所有东西都当作引用来处理。(有一些“不可变”的对象,比如字符串、数字和元组,它们更像是值。)当你执行

lattice[:,:] = site(3)

时,你实际上是在告诉Python:“创建一个新的对象site,然后让lattice中的每个元素都指向这个对象。”为了验证这一点,你可以打印数组,看看这些对象的内存地址是否都是一样的:

array([[<__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>],
       [<__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>],
       [<__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>,
        <__main__.Site object at 0x1029d5610>]], dtype=object)

用循环的方式是一种正确的做法。对于numpy数组,这可能是你最好的选择;而对于Python列表,你也可以使用列表推导式:

lattice = [ [Site(i + j) for i in range(3)] for j in range(3) ]

你可以用列表推导式来构建numpy.array

lattice = np.array( [ [Site(i + j) for i in range(3)] for j in range(3) ],
                    dtype=object)

现在当你打印lattice时,它是

array([[<__main__.Site object at 0x1029d53d0>,
        <__main__.Site object at 0x1029d50d0>,
        <__main__.Site object at 0x1029d5390>],
       [<__main__.Site object at 0x1029d5750>,
        <__main__.Site object at 0x1029d57d0>,
        <__main__.Site object at 0x1029d5990>],
       [<__main__.Site object at 0x1029d59d0>,
        <__main__.Site object at 0x1029d5a10>,
        <__main__.Site object at 0x1029d5a50>]], dtype=object)

这样你就能看到里面的每个对象都是独一无二的。

你还应该注意,“setter”和“getter”方法(比如set_a)在Python中并不常见。直接设置和获取属性会更好,如果你真的需要防止对某个属性的写入,可以使用@property装饰器。

另外,Python类的命名标准是使用驼峰命名法,而不是小写字母。

30

你可以把这个类的 __init__ 函数进行 向量化

import numpy as np

class Site:
    def __init__(self, a):
        self.a = a
    def set_a(self, new_a):
        self.a = new_a

vSite = np.vectorize(Site)

init_arry = np.arange(9).reshape((3,3))

lattice = np.empty((3,3), dtype=object)
lattice[:,:] = vSite(init_arry)

这样做可能看起来更整洁,但在性能上并没有比你用循环的方法更好。使用列表推导式会生成一个中间的 Python 列表,这样反而会影响性能。

撰写回答