我需要扩展 Networkx python 包并向Graph
类添加一些方法以满足我的特殊需要
我考虑这样做的方式是简单地派生一个新类 sayNewGraph
并添加所需的方法。
然而,networkx 中还有其他几个函数可以创建和返回Graph
对象(例如生成随机图)。我现在需要将这些Graph
对象转换为NewGraph
对象,以便我可以使用我的新方法。
这样做的最佳方法是什么?还是我应该以完全不同的方式解决问题?
我需要扩展 Networkx python 包并向Graph
类添加一些方法以满足我的特殊需要
我考虑这样做的方式是简单地派生一个新类 sayNewGraph
并添加所需的方法。
然而,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
<__main__.Circle object at 0x00A0E270>
20
62.8318530718
<__main__.CirclePlus object at 0x00A0E270>
这与 Python 中的“强制转换”非常接近,并且就像在 C 中进行强制转换一样,如果不考虑问题就不能这样做。我发布了一个相当有限的示例,但是如果您可以保持在约束范围内(只需添加行为,没有新的实例变量),那么这可能有助于解决您的问题。
以下是如何在不触及模块的情况下用定制的子类“神奇地”替换模块中的类。它只是正常子类化过程中的几行额外代码,因此(几乎)为您提供了子类化的所有功能和灵活性作为奖励。例如,如果您愿意,这允许您添加新属性。
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) 被调用 2.如果返回的对象是nx.Graph的子类, __init__ 在对象上被调用 3.对象作为实例返回
我们将替换nx.Graph.__new__
并使其返回NewGraph
。在其中,我们调用__new__
方法object
而不是__new__
方法NewGraph
,因为后者只是调用我们要替换的方法的另一种方式,因此会导致无限递归。
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__
.
我扩展了 PaulMcG 所做的工作,并使其成为工厂模式。
class A:
def __init__(self, variable):
self.a = 10
self.a_variable = variable
def do_something(self):
print("do something A")
class B(A):
def __init__(self, variable=None):
super().__init__(variable)
self.b = 15
@classmethod
def from_A(cls, a: A):
# Create new b_obj
b_obj = cls()
# Copy all values of A to B
# It does not have any problem since they have common template
for key, value in a.__dict__.items():
b_obj.__dict__[key] = value
return b_obj
if __name__ == "__main__":
a = A(variable="something")
b = B.from_A(a=a)
print(a.__dict__)
print(b.__dict__)
b.do_something()
print(type(b))
结果:
{'a': 10, 'a_variable': 'something'}
{'a': 10, 'a_variable': 'something', 'b': 15}
do something A
<class '__main__.B'>
对于您的简单情况,您还可以__init__
像这样编写子类,并将 Graph 数据结构中的指针分配给您的子类数据。
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())
加载您的图形数据。
如果函数正在创建 Graph 对象,则不能将它们转换为 NewGraph 对象。
NewGraph 的另一个选择是拥有一个 Graph 而不是成为一个 Graph。您将 Graph 方法委托给您拥有的 Graph 对象,并且可以将任何 Graph 对象包装到新的 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):
....
在定义自己的属性之前,您可以简单地从对象创建一个新的NewGraph
派生Graph
对象,并让__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)
我在投稿时遇到了同样的问题networkx
,因为我需要很多新的方法Graph
。@Aric的答案是最简单的解决方案,但不使用继承。这里使用了本机networkx
功能,它应该更有效。
教程中有一个部分networkx
,使用图构造函数,展示了如何Graph
从一个图的现有对象初始化对象,尤其是另一个图对象。这是那里显示的示例,您可以从现有对象中初始化一个新DiGraph
对象, :H
Graph
G
>>> G = Graph()
>>> G.add_edge(1, 2)
>>> H = nx.DiGraph(G) # create a DiGraph using the connections from G
>>> list(H.edges())
[(1, 2), (2, 1)]
请注意将现有图转换为有向图时的数学含义。您可能可以通过一些函数或构造函数来实现此功能,但我认为它是networkx
. 没有检查他们的实现,但我想它更有效。
要在类中保留此功能NewGraph
,您应该使其能够将现有对象作为参数__init__
,例如:
from typing import Optional
import networkx as nx
class NewGraph(nx.Graph):
def __init__(self, g: Optional[nx.Graph] = None):
"""Init an empty directed graph or from an existing graph.
Args:
g: an existing graph.
"""
if not g:
super().__init__()
else:
super().__init__(g)
然后,每当您有一个Graph
对象时,您可以通过以下方式初始化(不直接将其转换为)一个NewGraph
对象:
>>> G = nx.some_function()
...
>>> NG = NewGraph(G)
或者你可以初始化一个空NewGraph
对象:
>>> NG_2 = NewGraph()
出于同样的原因,您可以从以下内容中初始化另一个Graph
对象NG
:
>>> G_2 = nx.Graph(NG)
super().__init__()
最有可能的是,在启动NewGraph
对象之后有很多操作,所以@PaulMcG 的回答,正如他/她所提到的,在这种情况下不是一个好主意。
__class__
赋值方法实际上改变了变量。如果你只想从你可以使用的超类中调用一个函数super
。例如:
class A:
def __init__(self):
pass
def f(self):
print("A")
class B(A):
def __init__(self):
super().__init__()
def f(self):
print("B")
b = B()
b.f()
super(type(b), b).f()
正在返回
B
A
你们有没有试过 [Python] 将基类转换为派生类
我已经对其进行了测试,并且似乎可以正常工作。另外我认为这种方法比下面的方法好一点,因为下面的方法不执行派生函数的初始化函数。
c.__class__ = CirclePlus