2010-08-12 72 views
28

我需要延长Networkx Python包,并添加一些方法给Graph类我特别需要铸造基类派生类的Python(或扩展类的更Python的方式)

我想过做的方式这是简化派生一个新的类说NewGraph,并添加所需的方法。

然而,networkx中还有其他几个函数可以创建并返回对象(例如生成一个随机图)。我现在需要将这些Graph对象转换为NewGraph对象,以便我可以使用我的新方法。

这样做的最好方法是什么?还是应该以完全不同的方式解决问题?

回答

39

如果你是刚刚加入的行为,而不是依赖于额外的实例值,你可以指定对象的__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,如果不考虑这个问题,不要这样做。我已经发布了一个相当有限的例子,但是如果你能保持约束(只是添加行为,没有新的实例变量),那么这可能有助于解决你的问题。

+1

好的,那么当你需要添加变量时会发生什么? – 2015-06-05 15:42:17

+0

您可以在运行时添加/设置实例变量。但请注意,您不会与被CirclePlus __init__添加的实例变量混淆,而您忘记添加实例变量,因为此铸造方法会绕过__init__我想?顺便说一下,由于Python的类型系统可以被覆盖,所以这种转换方法并不总是可行的。 – 2016-03-08 20:01:12

+0

如果您发现您还需要添加实例变量,那么我认为您很快就会超越可维护代码的范围 - 重新考虑您的设计的时间,可能使用某种形式的遏制和/或委派。 – PaulMcG 2017-03-02 20:40:07

0

如果函数正在创建Graph对象,则不能将它们转换为NewGraph对象。

另一种选择是为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): 
     .... 
+2

谢谢 我看别的地方,我可以只改变__class__属性。例如MyRandomGraphObject .__ class__ = NewGraph。它确实有效。不好的做法? – zenna 2010-08-12 01:35:29

12

以下是如何“神奇地”用一个定制的子类替换一个模块中的类而不用触摸模块。这只是普通子类化过程中的一些额外的行,因此可以提供(几乎)所有子类的强大功能和灵活性作为奖励。例如,如果你愿意,这可以让你添加新的属性。

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.Graphnx.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代替。其中,我们将__new__方法称为object而不是方法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的子类,并添加所需的方法和属性。在遵守DRY原则的同时,使用mix-ins可以更容易地一致地扩展子类。

虽然这个例子可能看起来很直截了当,但这种挂钩到模块中的方法很难以涵盖所有可能出现的小问题的方式进行概括。我相信只是针对手头的问题而定制它更容易。例如,如果您正在接入的类定义了自己的自定义方法__new__,则需要在替换它之前将其存储,并调用此方法而不是object.__new__

+0

我可以使用内置功能吗?例如,如果我想将'set'转换为'SpecialSet',我可以更改内置的'__new__'行为吗? – GrantJ 2014-10-17 17:22:55

+1

@GrantJ这不起作用。大多数python内置函数都是用C实现的,因此不像纯Python类那样可塑。你会得到这个错误:'TypeError:不能设置内置/扩展类型'set''的属性。 – 2014-10-17 21:28:36

0

为了您的简单情况,您也可以像这样编写子类__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) 

你也可以使用复制()或deepcopy的()中分配,但如果你正在做的,你还不如用

G=MyGraph() 
G.add_nodes_from(R) 
G.add_edges_from(R.edges()) 

来加载图形数据。

+0

这对我有效。但如何用双下划线方法来做到这一点? – GrantJ 2014-10-18 01:38:33

-1

有你们试过 [Python] cast base class to derived class

我已经测试过它,而且似乎它的工作原理。此外,我认为这种方法比下面的方法好一点,因为下面的方法不执行init派生函数的函数。

c.__class__ = CirclePlus 
+0

请在发布相同的解决方案之前阅读所有现有解决方案。 – wieczorek1990 2017-02-15 16:00:36

0

你可以简单地创建一个新的NewGraphGraph对象派生,并有__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)