使用命名元组的_replace方法更新包含nt的其他数据结构字段

3 投票
2 回答
3179 浏览
提问于 2025-04-30 21:11

我正在学习命名元组(namedtuple)。我想找一种方法,利用 ._replace 方法,来更新所有地方出现的 namedtuple

假设我有一个节点的列表,还有一些元素的列表(比如两个节点的梁、四个节点的四边形)和由这些节点定义的边界(“一个节点”的元素)。

我正在尝试这样做:

from collections import namedtuple as nt
Node = nt('Node', 'x y')
Beam = nt('Beam', 'i j')
Quad = nt('Quad', 'i j k l')
Boundary = nt('Boundary', 'b')
#Define some nodes:
n1 = Node(0,0)
n2 = Node(0,1)
n3 = Node(1,1)
n4 = Node(1,0)
#And some other things using those nodes:
q1 = Quad(n1,n2,n3,n4)
b1 = Boundary(n1)
be1 = Beam(n1,n4)

现在,如果我用一个新的 Node 替换 n1

n1 = n1._replace(x=0.5,y=0.5)
print(n1)  # Node(x=0.5,y=0.5)

其他的项目都没有更新:

print(b1)  # Boundary(b=Node(x=0, y=0))

我明白 Python 的命名和对象模型,以及这样做的 原因b1.b 被设置为对象 Node(0,0),而不是名字 n1。所以当 n1 被改变时,其他的命名元组仍然包含之前的同一个对象,而 n1 得到了一个新对象。

我想要做的是改变这种行为,让当我改变 n1 时,b1be1q1 等等也能“感受到”这些变化。我该怎么做呢?

暂无标签

2 个回答

2

四年半后:如果我今天再做这个,我可能会首先使用dataclasses模块

from dataclasses import make_dataclass as md

Node = md('Node', 'x y')
Beam = md('Beam', 'i j')
Quad = md('Quad', 'i j k l')
Boundary = md('Boundary', 'b')

...而且因为这些对象是可变的(和namedtuple不一样),我们可以直接更新节点的位置,而不是用一个新的实例来替换节点:

n1.x = 0.5
n1.y = 0.5
print(n1)  # Node(x=0.5,y=0.5)

这样就完全解决了这个问题。

5

namedtuple 创建的类的所有实例都是 不可变 的。这意味着你不能直接修改它们。._replace() 方法会创建一个 新的 实例,而不会更新你调用这个方法的那个实例。

因为这些实例是不可变的,所以你不能像想的那样使用 namedtuple。如果想要实现这种功能,你需要在一个子类中提供,实际上这就打破了不可变的特性。或者,你可以自己创建一个 Node 的自定义类,这样就可以直接修改属性:

class Node(object):
    __slots__ = ('x', 'y')

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return '{0.__class__.__name__}({0.x!r}, {0.y!r})'.format(self)

这个类和 namedtuple 类似,使用了 __slots__ 来减少内存使用。你可以直接在实例上设置 xy 属性,任何其他对这个实例的引用都会看到这些变化:

>>> class Node(object):
...     __slots__ = ('x', 'y')
...     def __init__(self, x, y):
...         self.x = x
...         self.y = y
...     def __repr__(self):
...         return '{0.__class__.__name__}({0.x!r}, {0.y!r})'.format(self)
... 
>>> n1 = Node(10, 20)
>>> n2 = n1
>>> n2
Node(10, 20)
>>> n1.x = 42
>>> n1.y = 81
>>> n2
Node(42, 81)

撰写回答