将基类转换为派生类python(或更多pythonic方式的扩展类)

时间:2010-08-12 01:20:13

标签: python inheritance derived-class base-class

我需要扩展Networkx python包并根据我的特殊需要为Graph类添加一些方法

我想这样做的方法是简单地派出一个新的课程NewGraph,并添加所需的方法。

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

这样做的最佳方式是什么?或者我应该以完全不同的方式解决问题?

9 个答案:

答案 0 :(得分:46)

如果您只是添加行为而不依赖于其他实例值,则可以指定对象的__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 :(得分: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.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。在其中,我们使用__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__

答案 2 :(得分:0)

如果函数正在创建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):
        ....

答案 3 :(得分: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)

你也可以在作业中使用copy()或deepcopy(),但如果你这样做,你也可以使用

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

加载图表数据。

答案 4 :(得分:0)

您可以简单地创建一个从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)

答案 5 :(得分:0)

__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

答案 6 :(得分:0)

我扩展了 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'>

答案 7 :(得分:0)

我在为 networkx 做贡献时遇到了同样的问题,因为我需要许多 Graph 的新方法。 answer by @Aric 是最简单的解决方案,但不使用继承。这里使用了一个原生的 networkx 功能,它应该更高效。

a section in networkx tutorial, using the graph constructors,展示了如何从一个图形的现有对象初始化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 对象时,您都可以通过以下方式init(而不是直接将其转换为)NewGraph 对象:

>>> G = nx.some_function()
...
>>> NG = NewGraph(G)

或者你可以初始化一个空的 NewGraph 对象:

>>> NG_2 = NewGraph()

出于同样的原因,您可以从 Graph 中初始化另一个 NG 对象:

>>> G_2 = nx.Graph(NG)

最有可能的是,当初始化一个 super().__init__() 对象时,NewGraph 之后有很多操作,所以他/她提到的 answer by @PaulMcG 在这种情况下不是一个好主意。< /p>

答案 8 :(得分:-1)

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

我测试了它,似乎有效。另外我认为这个方法比下面的方法好一点,因为下面的方法不会执行派生函数的 init 函数。

c.__class__ = CirclePlus