使用命名元组的_replace方法更新包含nt的其他数据结构字段
我正在学习命名元组(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
时,b1
、be1
、q1
等等也能“感受到”这些变化。我该怎么做呢?
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__
来减少内存使用。你可以直接在实例上设置 x
和 y
属性,任何其他对这个实例的引用都会看到这些变化:
>>> 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)