关于继承:将基类强制转换为派生类python(或更多扩展类的python方式)

Cast base class to derived class python (or more pythonic way of extending classes)

我需要扩展networkx python包,并为我的特殊需要向Graph类添加一些方法

我想这样做的方法是简单地派生一个新类,比如NewGraph,并添加所需的方法。

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

最好的方法是什么?或者我应该以完全不同的方式来解决这个问题?


如果只是添加行为,而不依赖于其他实例值,则可以将其分配给对象的__class__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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)

印刷品:

1
2
3
4
5
6
10
314.159265359
<__main__.Circle object at 0x00A0E270>
20
62.8318530718
<__main__.CirclePlus object at 0x00A0E270>

这与您在Python中所能得到的"强制转换"非常接近,就像在C中强制转换一样,如果不考虑这个问题,就不可能做到这一点。我已经发布了一个相当有限的示例,但是如果您可以保持在约束范围内(只添加行为,不添加新的实例变量),那么这可能有助于解决您的问题。


下面介绍如何"神奇地"用定制的子类替换模块中的类,而不必触及模块。它只是普通子类化过程中的几行额外的代码,因此(几乎)为您提供了子类化的所有功能和灵活性作为奖励。例如,如果您愿意,这允许您添加新的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
2
3
4
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__方法,因为后者只是调用我们要替换的方法的另一种方法,因此会导致无休止的递归。

1
2
3
4
5
6
7
8
9
10
11
12
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__


在定义自己的属性之前,您可以简单地创建一个从Graph对象派生的新NewGraph,并让__init__函数包含类似self.__dict__.update(vars(incoming_graph))的内容作为第一行。这样,你就基本上把你拥有的Graph的所有属性复制到一个新的对象上,这个新的对象源于Graph,但是用你的特殊酱汁。

1
2
3
4
5
class NewGraph(Graph):
  def __init__(self, incoming_graph):
    self.__dict__.update(vars(incoming_graph))

    # rest of my __init__ code, including properties and such

用途:

1
2
3
graph = function_that_returns_graph()
new_graph = NewGraph(graph)
cool_result = function_that_takes_new_graph(new_graph)

对于简单的例子,您也可以这样编写子类__init__,并将图形数据结构中的指针分配给子类数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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(),但如果您这样做,您也可以使用

1
2
3
G=MyGraph()
G.add_nodes_from(R)
G.add_edges_from(R.edges())

加载图形数据。


如果函数正在创建图形对象,则不能将其转换为newgraph对象。

newgraph的另一个选择是拥有一个图而不是一个图。将图形方法委托给您拥有的图形对象,然后可以将任何图形对象包装为新的图形对象:

1
2
3
4
5
6
7
8
9
10
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):
        ....


你们试过了吗将基类强制转换为派生类

我已经测试过了,看来它是有效的。另外,我认为这个方法比下面的方法好一点,因为下面的方法不执行派生函数的init函数。

1
c.__class__ = CirclePlus