我需要扩展networkx python包,并为我的特殊需要向Graph类添加一些方法
我想这样做的方法是简单地派生一个新类,比如NewGraph,并添加所需的方法。
但是,networkx中还有其他几个函数可以创建和返回Graph对象(例如生成随机图)。现在我需要将这些Graph对象转换为NewGraph对象,以便使用我的新方法。
最好的方法是什么?或者我应该以完全不同的方式来解决这个问题?
如果只是添加行为,而不依赖于其他实例值,则可以将其分配给对象的__class__:
from math import pi
class Circle(object):
def __init__(self, radius):
self.radius = radius
def area(self):
return pi * self.radius**2
class CirclePlus(Circle):
def diameter(self):
return self.radius*2
def circumference(self):
return self.radius*2*pi
c = Circle(10)
print c.radius
print c.area()
print repr(c)
c.__class__ = CirclePlus
print c.diameter()
print c.circumference()
print repr(c)
印刷品:
10
314.159265359
20
62.8318530718
这与您在Python中所能得到的"强制转换"非常接近,就像在C中强制转换一样,如果不考虑这个问题,就不可能做到这一点。我已经发布了一个相当有限的示例,但是如果您可以保持在约束范围内(只添加行为,不添加新的实例变量),那么这可能有助于解决您的问题。
好的,那么当您需要添加变量时会发生什么?
您可以在运行时添加/设置实例变量。小心点,尽管你不会与一个circleplus init添加的实例变量混淆,因为我想这个强制转换方法会绕过init而忘记添加它。顺便说一下,由于python的类型系统可以被重写,所以这种强制转换方法并不总是有效。
如果您发现您也需要添加实例变量,那么我认为您很快就超出了可维护代码的范围——可能需要使用某种形式的包含和/或委托来重新考虑您的设计。
下面介绍如何"神奇地"用定制的子类替换模块中的类,而不必触及模块。它只是普通子类化过程中的几行额外的代码,因此(几乎)为您提供了子类化的所有功能和灵活性作为奖励。例如,如果您愿意,这允许您添加新的属性。
import networkx as nx
class NewGraph(nx.Graph):
def __getattribute__(self, attr):
"This is just to show off, not needed"
print"getattribute %s" % (attr,)
return nx.Graph.__getattribute__(self, attr)
def __setattr__(self, attr, value):
"More showing off."
print" setattr %s = %r" % (attr, value)
return nx.Graph.__setattr__(self, attr, value)
def plot(self):
"A convenience method"
import matplotlib.pyplot as plt
nx.draw(self)
plt.show()
到目前为止,这与普通的子类完全相同。现在,我们需要将这个子类挂接到networkx模块中,以便nx.Graph的所有实例化都会导致NewGraph对象。下面是用nx.Graph()实例化nx.Graph对象时通常发生的情况。
1. nx.Graph.__new__(nx.Graph) is called
2. If the returned object is a subclass of nx.Graph,
__init__ is called on the object
3. The object is returned as the instance
我们将更换nx.Graph.__new__,并让它返回NewGraph。在它中,我们调用object的__new__方法,而不是NewGraph的__new__方法,因为后者只是调用我们要替换的方法的另一种方法,因此会导致无休止的递归。
def __new__(cls):
if cls == nx.Graph:
return object.__new__(NewGraph)
return object.__new__(cls)
# We substitute the __new__ method of the nx.Graph class
# with our own.
nx.Graph.__new__ = staticmethod(__new__)
# Test if it works
graph = nx.generators.random_graphs.fast_gnp_random_graph(7, 0.6)
graph.plot()
在大多数情况下,这是你所需要知道的,但有一个是正确的。我们对__new__方法的重写只影响nx.Graph而不影响它的子类。例如,如果您调用nx.gn_graph,它返回nx.DiGraph的一个实例,那么它将没有我们想要的扩展。您需要对希望使用的nx.Graph的每个子类进行子类化,并添加所需的方法和属性。使用mix-ins可以更容易地在遵循dry原则的同时一致地扩展子类。
尽管这个例子看起来足够简单,但是这种连接到模块中的方法很难用一种涵盖所有可能出现的小问题的方式进行概括。我相信根据手头的问题调整它会更容易。例如,如果要挂接的类定义了自己的自定义__new__方法,则需要在替换它之前存储它,并调用此方法而不是object.__new__。
我能用一个内置的吗?例如,如果我想将set强制转换为SpecialSet,我可以更改内置的__new__行为吗?
@格兰特那不行。大多数Python内置组件都是用C实现的,因此没有纯Python类那样具有延展性。你会得到这个错误:TypeError: can't set attributes of built-in/extension type 'set'。
在定义自己的属性之前,您可以简单地创建一个从Graph对象派生的新NewGraph,并让__init__函数包含类似self.__dict__.update(vars(incoming_graph))的内容作为第一行。这样,你就基本上把你拥有的Graph的所有属性复制到一个新的对象上,这个新的对象源于Graph,但是用你的特殊酱汁。
class NewGraph(Graph):
def __init__(self, incoming_graph):
self.__dict__.update(vars(incoming_graph))
# rest of my __init__ code, including properties and such
用途:
graph = function_that_returns_graph()
new_graph = NewGraph(graph)
cool_result = function_that_takes_new_graph(new_graph)
对于简单的例子,您也可以这样编写子类__init__,并将图形数据结构中的指针分配给子类数据。
from networkx import Graph
class MyGraph(Graph):
def __init__(self, graph=None, **attr):
if graph is not None:
self.graph = graph.graph # graph attributes
self.node = graph.node # node attributes
self.adj = graph.adj # adjacency dict
else:
self.graph = {} # empty graph attr dict
self.node = {} # empty node attr dict
self.adj = {} # empty adjacency dict
self.edge = self.adj # alias
self.graph.update(attr) # update any command line attributes
if __name__=='__main__':
import networkx as nx
R=nx.gnp_random_graph(10,0.4)
G=MyGraph(R)
您也可以在分配中使用copy()或deepcopy(),但如果您这样做,您也可以使用
G=MyGraph()
G.add_nodes_from(R)
G.add_edges_from(R.edges())
加载图形数据。
这对我有用。但是如何使用双下划线方法呢?
如果函数正在创建图形对象,则不能将其转换为newgraph对象。
newgraph的另一个选择是拥有一个图而不是一个图。将图形方法委托给您拥有的图形对象,然后可以将任何图形对象包装为新的图形对象:
class NewGraph:
def __init__(self, graph):
self.graph = graph
def some_graph_method(self, *args, **kwargs):
return self.graph.some_graph_method(*args, **kwargs)
#.. do this for the other Graph methods you need
def my_newgraph_method(self):
....
谢谢,我在其他地方读到我可以更改class属性。例如,myrandomgraphobject.uuu class_uuu=newgraph。它确实有效。不好的练习?
你们试过了吗将基类强制转换为派生类
我已经测试过了,看来它是有效的。另外,我认为这个方法比下面的方法好一点,因为下面的方法不执行派生函数的init函数。
c.__class__ = CirclePlus
请在发布同一个解决方案之前阅读所有现有解决方案。