我有一个模块,其中我定义了一个使用另一个类Vertex的Graph类。
# Graph.py
class Graph(object):
def __init__(self):
self.vertList = {}
self.numVertices = 0
def addVertex(self,key):
self.numVertices += 1
newVert = Vertex(key)
self.vertList[key] = newVert
return newVert
def getVertex(self,k):
if k in self.vertList:
return self.vertList[k]
else:
return None
class Vertex(object):
def __init__(self,key):
self.id = key
self.connectedTo = {}
我想扩展 Vertex 类以在另一个模块中使用:
# BFSGraph.py
from Graph import Vertex,Graph
class Vertex(Vertex):
def __init__(self,key):
super(Vertex,self).__init__(key)
# extensions for BFS
self.predecessor = None
self.dist = 0
self.color = 'w' # white, grey, and black
class BFSGraph(Graph):
def getColor(self,k):
return self.getVertex(k).color
def test():
g=BFSGraph()
g.addVertex('a')
g.getColor('a')
当我运行测试例程时,它返回"'顶点'对象没有属性'颜色'",所以我对顶点所做的更改不会向下传播到 Graph,并且 BFSGraph 没有使用扩展的顶点。
如何让 Graph 和 BFSGraph 使用新的顶点?
基本上,你不能,不修改Graph
和BFSGraph
类。 如果Graph
引用Vertex
,它引用Vertex
,并且你不能让它引用其他任何东西而不实际更改Graph
代码。 也就是说,有三种方法可以做这样的事情。
最简单的解决方案是创建重写addVertex
的 Graph
派生版本,以便它使用新的 Vertex
子类。 然后你使用该新类而不是原始Graph
一切都很好。
第二种更狡猾、风险更大的方法是给它打猴子补丁:
import graph
graph.Vertex = MyNewVertex
现在,任何尝试在graph
模块中使用Vertex
类的人实际上都会使用您的类。 但是,这是有风险的,因为您永远不知道它会对其他认为它正在使用原始Vertex
的代码执行什么操作。 (将类命名为不同名称仍然是一个好主意,否则判断正在使用哪个类会令人困惑。 此外,如果另一个类在您的 monkeypatch 生效之前导入Vertex
,它可能会静默地无法工作。
如果你正在设计整个代码库,并且你确实需要做很多事情,那么更大规模的解决方案是将顶点参数化为类的一部分。 这样可以更轻松地编写自己的派生类,这些类可以相互互操作。 也就是说,您可以执行以下操作:
class Graph(object):
vertexClass = Vertex
def addVertex(self, key):
# ...
newVert = self.vertexClass(key)
# etc.
# etc.
然后稍后,如果需要,您可以执行以下操作:
class MyVertex(Vertex):
# blah
class MyGraph(Graph):
vertexClass = MyVertex
这个想法是使用类变量,以便 Graph 类知道要用于其顶点的类。 然后,您可以轻松创建一个仅更改此变量的派生类,而无需重写所有实际的方法代码(假设您确实在MyVertex类中保持了相同的API)。 这增加了一层间接性,对于小型项目来说可能有点矫枉过正,但是如果你有很多相互依赖的类,那么这样做可以让它们明确地跟踪它们需要如何使用彼此会很有用。
做法是允许Graph
类将用户定义的类作为参数,并使用它来表示顶点。您的graph
模块可以提供合适的默认值。
在graph.py
(模块应以小写字母开头):
class Vertex(object):
def __init__(self,key):
self.id = key
self.connectedTo = {}
class Graph(object):
def __init__(self, vertexclass=Vertex):
self.vertList = {}
self.numVertices = 0
self.vertexclass = vertexclass
def addVertex(self,key):
self.numVertices += 1
newVert = self.vertexclass(key)
self.vertList[key] = newVert
return newVert
def getVertex(self,k):
if k in self.vertList:
return self.vertList[k]
else:
return None
唯一的变化是Graph.__init__
有一个参数,其默认值为纯Vertex
类,允许您在创建Graph
实例时传递不同的类。该类将被保存,每当调用addVertex
创建新顶点时,您都可以使用它。
然后,在要使用自定义顶点类的另一个模块或脚本中:
#!/usr/bin/python
import graph
class MyVertex(graph.Vertex):
def __init__(self,key):
super(Vertex,self).__init__(key)
# extensions for BFS
self.predecessor = None
self.dist = 0
self.color = 'w' # white, grey, and black
class BFSGraph(Graph):
def __init__(self):
super(BFSGraph, self).__init__(MyVertex)
def getColor(self,k):
return self.getVertex(k).color
def test():
g=BFSGraph()
g.addVertex('a')
g.getColor('a')
您的BFSGraph.__init__
在调用时,只需使用要使用的graph.Vertex
子类调用其父类的__init__
。
这不是组织代码的唯一方法,但要记住的关键是,您的Graph
类(或从它派生的任何子类)应该准确记录实现顶点的类需要实现的行为。
Graph
中唯一明确引用和依赖Vertex
的部分是addVertex
,它使用Vertex
的构造函数来创建一个对象,然后将其填充到字典中。 我建议更改addVertex
以便将Vertex
对象添加为参数,从而使调用者进行构造并让他们确定使用哪类Vertex
,例如:
def addVertex(self, key, newVert):
self.numVertices += 1
self.vertList[key] = newVert
return newVert
或者,为了减少多余的参数,在对Vertex
进行简单添加之后:
def addVertex(self, newVert):
self.numVertices += 1
self.vertList[newVert.getKey()] = newVert
return newVert