我在python中有一个自定义节点类,它构建在一个图形(这是一个字典)中。由于这些需要花费一些时间来创建,我想腌制它们,这样我每次运行代码时都不必重新构建它们。
不幸的是,因为这个图有周期,所以cPickle会达到最大递归深度:
RuntimeError:蚀刻对象时超出了最大递归深度
这是我的节点对象:
class Node:
def __init__(self, name):
self.name = name
self.uid = 0
self.parents = set()
self.children = set()
def __hash__(self):
return hash(self.name)
def __eq__(self, that):
return self.name == that.name
def __str__(self):
return "\n".join(["Name: " + self.name,
"\tChildren:" + ", ".join([c.name for c in self.children]),
"\tParents:" + ", ".join([p.name for p in self.parents])
]
)
这是我构建图表的方式:
def buildGraph(input):
graph = {}
idToNode = {}
for line in input:
## Input from text line by line looks like
## source.node -> target.node
source, arr, target = line.split()
if source in graph:
nsource = graph[source]
else:
nsource = Node(source)
nsource.uid = len(graph)
graph[source] = nsource
idToNode[nsource.uid] = nsource
if target in graph:
ntarget = graph[target]
else:
ntarget = Node(target)
ntarget.uid = len(graph)
graph[target] = ntarget
idToNode[ntarget.uid] = ntarget
nsource.children.add(ntarget)
ntarget.parents.add(nsource)
return graph
然后在我的主要内容中,我有
graph = buildGraph(input_file)
bo = cPickle.dumps(graph)
,第二行是我得到递归深度错误的地方。
除了改变Node的结构外还有其他解决方案吗?
答案 0 :(得分:2)
你需要为pickle准备对象:如果你有一个循环,你需要打破循环并以其他形式存储这些信息。
Pickle 使用方法__getstate__
准备pickle(之前调用)和__setstate__
初始化对象。
class SomethingPickled(object):
## Compress and uncycle data before pickle.
def __getstate__(self):
# deep copy object
state = self.__dict__.copy()
# break cycles
state['uncycled'] = self.yourUncycleMethod(state['cycled'])
del state['cycle']
# send to pickle
return state
## Expand data before unpickle.
def __setstate__(self, state):
# restore cycles
state['cycle'] = self.yourCycleMethod(state['uncycled'])
del state['uncycle']
self.__dict__.update(state)
我相信你会发现如何打破和加入周期:)
答案 1 :(得分:2)
我不认为你的图是循环的这个问题 - pickle(和cPickle)应该处理循环数据结构就好了。我尝试了以下(使用Node的定义)并且它工作得很好:
>>> n1 = Node('a')
>>> n2 = Node('b')
>>> n1.parents.add(n2)
>>> n2.parents.add(n1)
>>> n2.children.add(n1)
>>> n1.children.add(n1)
>>> import cPickle as pickle
>>> pickle.dumps(n1)
实际上,即使是大周期,我也没有遇到问题。例如,这对我来说很好:
>>> def node_cycle(n):
... start_node = prev_node = Node('node0')
... for i in range(n):
... node = Node('node%d' % (i+1))
... node.parents.add(prev_node)
... prev_node.children.add(node)
... prev_node = node
... start_node.parents.add(node)
... node.children.add(start_node)
>>> cycle = node_cycle(100000) # cycle of 100k nodes
>>> pickle.dumps(cycle)
(这都是在Python 2.7.1上测试过的)
还有其他原因可以解释为什么pickle可能会以非常深的递归结束,具体取决于数据结构的形状。如果这是真正的问题,那么您可以使用以下内容修复它:
>>> import sys
>>> sys.setrecursionlimit(10000)
答案 2 :(得分:1)
此处,此修改后的节点类仅将对象的名称保存为节点中的字符串,并在检索节点的“children”或“parents”属性时为您提供具有完整“Node”对象的集合
在内部没有循环 - 因此它应该避免无限循环陷阱。您可以实现其他辅助方法以轻松导航。
class Node(object):
all_nodes = {}
def __new__(cls, name):
self = object.__new__(cls)
cls.all_nodes[name] = self
return self
def __getstate__(self):
self.all_nodes = self.__class__.all_nodes
return self.__dict__
def __setstate__(self, dct):
self.__class__.all_nodes = dct["all_nodes"]
del dct["all_nodes"]
self.__dict__ = dct
def __init__(self, name):
#self.all_nodes = self.__class__.all_nodes
self.name = name
self.uid = 0
self._parents = set()
self._children = set()
def __hash__(self):
return hash(self.name)
def __eq__(self, that):
return self.name == that.name
def __repr__(self):
return "\n" + "\n".join(["Name: " + self.name,
"\tChildren:" + ", ".join([c.name for c in self.children]),
"\tParents:" + ", ".join([p.name for p in self.parents])
]
)
def get_relations(self, which):
names = getattr(self, which)
return set(self.__class__.all_nodes[name] for name in names)
@property
def children(self):
return self.get_relations("_children")
@property
def parents(self):
return self.get_relations("_parents")
def __contains__(self, item):
return item.name in self._children
def add(self, child):
self._children.add(child.name)
child._parents.add(self.name)
connect_child = add
#example and testing:
from cPickle import loads, dumps
n1 = Node("n1")
n2 = Node("n2")
n3 = Node("n3")
n1.add(n2)
n2.add(n3)
n3.add(n1)
print n1, n2, n3
p1 = dumps(n1)
Node.all_nodes.clear()
p2 = loads(p1)
print p2
print p2.children
print p2.children.pop().children
print Node.all_nodes
缺点是它维护了一个名为“all_nodes”的类字典,其中引用了所有实际创建的节点。 (Pickle很聪明,只能为给定的图形挑选一次这个字典,因为它被所有Node对象引用)。 类范围“all_nodes”引用的问题是如果你需要pickle和unpickle不同的图形集9let说你用一组节点创建图形g1,在另一个运行中,用另一组节点创建图形g2,并且然后,如果你取消g1,以及后来的g2,g2的unpickling将覆盖g1的节点引用。如果你需要这个工作,请在评论中提问我可以想出一些东西 - 我能想到的更简单的事情是有一个“图”类,它将为所有节点保存一个字典(而不是在Node类中使用它) )