NetworkX中循环内为全局图对象边属性赋值后无法在外部访问的问题
这是一个简化后的问题,我在macOS下使用Python和NetworkX时,遇到了一个奇怪的情况——在循环里给图的边属性赋值后,到了循环外面就没法正常访问这个属性的值了。以下是我的代码和运行输出:
import networkx as nx import matplotlib.pyplot as plt import pandas as pd import matplotlib.pyplot as plt from pyvis.network import Network from io import StringIO csv_cities= """City,Location,Lon,Lat,x,y,,LonDat,LatDat Los Angeles,"Los Angeles, California, United States",-118.254190,34.048050,140,328,q,-123.114980,25.761681 New York,"New York, New York, United States",-74.005994,40.712749,1415,591,q,-71.056800,51.045700 Atlanta,"Atlanta, Georgia, United States",-84.389854,33.750800,1116,316,q,-94.971032,39.002842 Chicago,"Chicago, Illinois, United States",-87.632360,41.881954,1022,638,q,52.05818,25.284019 """ csv_connections= """City1,City2,numTracks,NumCarSpots,Colors,SegmentTotalCarSpots Chicago,Los Angeles,1,4,Blue,4 New York,Chicago,2,2,"Orange, Black",4 Atlanta,Chicago,1,3,Blue,3""" cities = pd.read_csv(StringIO(csv_cities)) interCityConnections = pd.read_csv(StringIO(csv_connections)) G = nx.Graph() #Add cities to graph coords={} for index, row in cities.iterrows(): print(f"{row['City']:<17} {int(row['x']):<6} {int(row['y']):<6}") G.add_node(row['City'],city=True, x=row['x'], y=row['y'], physics=False, label=row['City'], title=f'{row['x']},{row['y']}') coords[row['City']]=int(row['x']),int(row['y']) print("- 1 - ") #Add intercity connections for index, row in interCityConnections.iterrows(): print(f"{row['City1']:>17} <-{row['NumCarSpots']} {row['Colors']}-> {row['City2']:<17} ") G.add_edge(row['City1'],row['City2'],interCity=True,numTracks=row['numTracks']) G[row['City1']][row['City2']]['taxes']="Major" # This works shortPaths=nx.all_shortest_paths(G,row['City1'], row['City2']) G[row['City1']][row['City2']]['shortPaths']=shortPaths #some assignment happens here # but value not usable outside loop? for p in shortPaths: #This works print(f"Path:{p}") #This works print("- 2 - ") print(G['Atlanta']['Chicago']) print(f"type:{type(G['Atlanta']['Chicago']['shortPaths'])}") for p in G['Atlanta']['Chicago']['shortPaths']: print("For looping...") # This never executes print(f"Path:{p}") # This never executes print("Done. There should be a path displayed above this line.")
运行输出:
q@q-mbp ttr % py simplebug.py Los Angeles 140 328 New York 1415 591 Atlanta 1116 316 Chicago 1022 638 - 1 - Chicago <-4 Blue-> Los Angeles Path:['Chicago', 'Los Angeles'] New York <-2 Orange, Black-> Chicago Path:['New York', 'Chicago'] Atlanta <-3 Blue-> Chicago Path:['Atlanta', 'Chicago'] - 2 - {'interCity': True, 'numTracks': 1, 'taxes': 'Major', 'shortPaths': <generator object _build_paths_from_predecessors at 0x10d107480>} type:<class 'generator'> Done. There should be a path displayed above this line.
我想知道,怎么修改才能让循环内对边属性的修改在循环外也能生效?怎么才能在循环外访问G[row['City1']][row['City2']]['shortPaths']的赋值结果呢?我感觉这像是作用域的问题,但我刚接触Python和NetworkX,完全摸不着头绪,难道要把图对象G传入循环里吗?
问题解答
别急,这根本不是作用域的bug,也完全不用把图对象传入循环!问题的根源出在你赋值的生成器对象上。
你用nx.all_shortest_paths()得到的shortPaths是一个生成器,它的特性是:只能被迭代一次,迭代完成后就会被“耗尽”,里面的元素就全部消失了。
你在循环里已经用for p in shortPaths:遍历过这个生成器了,等你把它赋值给边属性再拿到循环外的时候,这个生成器已经是空的了,自然遍历不出任何内容。
解决办法超简单,把生成器转换成列表保存就行——列表是可多次迭代的容器,能把所有路径都存下来,随时都能访问:
# 把生成器转成列表再赋值 shortPaths = list(nx.all_shortest_paths(G, row['City1'], row['City2'])) G[row['City1']][row['City2']]['shortPaths'] = shortPaths
这样修改之后,不管你在循环里遍历(如果需要的话),还是到循环外面去访问,都能正常拿到所有最短路径。另外,如果你不需要在循环里打印路径,也可以直接转成列表赋值,避免提前耗尽生成器。
再补充一句:NetworkX的图对象本身就是全局的,你在循环里对它的修改本来就是持久化的,这次的问题纯粹是生成器的特性导致的,和图的作用域完全没关系哦~
备注:内容来源于stack exchange,提问作者Perry Horwich




