如何给python igraph列表属性的单个元素赋值?
在Python的igraph库中,顶点和边的属性可以是任何类型的Python对象,比如列表或字典。我创建了一个顶点属性,每个顶点都有一个列表作为值:
import igraph
g = igraph.Graph.Barabasi(10,5)
g.vs['foo'] = [[]]
然后我为每个顶点准备了(空的)列表:
print g.vs['foo']
# [[], [], [], [], [], [], [], [], [], []]
每个顶点的属性值可以通过两种方式来引用:
print g.vs[0]['foo']
# []
print g.vs['foo'][0]
# []
如果我想给属性赋值,可以这样做:
g.vs[0]['foo'] = ['bar']
g.vs['foo'][0] = ['bar']
结果是,顶点0的值变成了 ['bar']
,而其他顶点的属性保持不变:
print g.vs[0]['foo']
# ['bar']
print g.vs['foo']
# [['bar'], [], [], [], [], [], [], [], [], []]
我遇到的第一个小问题是:用第二种引用方式(也就是属性名 > 顶点索引)赋值不成功:
g.vs['foo'] = [[]]
g.vs['foo'][0] = ['bar']
print g.vs['foo'][0]
# []
print g.vs['foo']
# [[], [], [], [], [], [], [], [], [], []]
但我主要的问题是,修改一个顶点的列表时,结果很奇怪:
g.vs['foo'] = [[]]
g.vs[0]['foo'].append('bar')
print g.vs['foo']
# [['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar']]
g.vs['foo'] = [[]]
g.vs[0]['foo'] += ['bar']
print g.vs['foo']
# [['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar']]
不仅顶点0的属性被修改了,所有顶点的属性都被加上了这个值!这不是我所期望的。我可以通过这种方式给每个顶点的属性赋值:
g.vs['foo'] = [[]]
g.vs[0]['foo'] = g.vs[0]['foo'] + ['bar']
print g.vs['foo']
# [['bar'], [], [], [], [], [], [], [], [], []]
但不能使用 append()
方法:
g.vs['foo'] = [[]]
g.vs[0]['foo'] = g.vs[0]['foo'].append('bar')
print g.vs['foo']
# [None, ['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar'], ['bar']]
这真的很让人惊讶,值0变成了 None
,而其他的顶点都有了添加的元素。我认为这是因为Python中指针的特性。如果我单独初始化每个值,一切都能按我直觉的方式正常工作:
for v in g.vs:
v['foo'] = []
g.vs[0]['foo'].append('bar')
print g.vs['foo']
# [['bar'], [], [], [], [], [], [], [], [], []]
那么,推荐的在列表顶点属性上使用 append()
的方法是什么呢?在添加顶点时,属性会被初始化为 None
。在这种情况下,用户需要编写自定义的辅助函数来保持属性类型的一致性吗?我们应该完全避免这样初始化属性: g.vs['foo'] = [[]]
吗?
2 个回答
我遇到的第一个小问题是:用第二种引用方式(也就是属性名 > 顶点索引)来赋值时不管用。
之所以不管用,是因为 g.vs["foo"]
返回的是一个存储 foo
属性值的列表的“副本”,而不是这个列表本身。所以,当你执行 g.vs["foo"][0] = ["bar"]
时,实际上是在修改这个副本,而不是在修改真正的图形数据。
看起来你在用 *
操作符初始化 Python 的列表时遇到了问题。
>>> g.vs['foo'] = [[]]
这可能是像这样做的(假设10是你特定情况下的顶点数量):
>>> g.vs['foo'] = [[]] * 10
但这可能不会按你想的那样工作:
>>> [id(i) for i in g.vs['foo']]
[4348211640, 4348211640, 4348211640, 4348211640, 4348211640, 4348211640, 4348211640, 4348211640, 4348211640, 4348211640]
正如你所看到的,所有的子列表其实都是完全相同的列表。它们的引用都是一样的。所以当你往其中一个子列表添加内容时,实际上你是在修改同一个子列表。
一个解决办法是像这样创建确切数量的顶点列表:
>>> g.vs['foo'] = [[] for _ in range(10)]
然后,如果你打印这些引用,你会发现它们实际上是不同的子列表:
>>> [id(i) for i in g.vs['foo']]
[4348211784, 4348212576, 4348212648, 4348212720, 4348212792, 4348212864, 4348212936, 4348213008, 4348213080, 4348213152]