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

zen*_*nna 38 python inheritance base-class derived-class

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

我想这样做的方法是简单地推导出一个新类说NewGraph,并添加所需的方法.

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

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

Pau*_*McG 52

如果您只是添加行为,而不依赖于其他实例值,则可以分配给对象__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)
Run Code Online (Sandbox Code Playgroud)

打印:

10
314.159265359
<__main__.Circle object at 0x00A0E270>
20
62.8318530718
<__main__.CirclePlus object at 0x00A0E270>
Run Code Online (Sandbox Code Playgroud)

这与你在Python中可以得到的"强制转换"一样接近,就像在C中进行投射一样,如果不考虑这个问题就不可能完成.我发布了一个相当有限的示例,但如果您可以保持在约束内(只添加行为,没有新的实例变量),那么这可能有助于解决您的问题.

  • 好的,那么当你需要添加变量时会发生什么? (3认同)
  • 如果您发现您也需要添加实例变量,那么我认为您很快就超出了可维护代码的范围 - 是时候重新考虑您的设计了,可能使用某种形式的包含和/或委托。 (3认同)

Lau*_*low 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()
Run Code Online (Sandbox Code Playgroud)

到目前为止,这与正常的子类化完全相同.现在我们需要将这个子类挂钩到networkx模块中,以便nx.GraphNewGraph对象中实现结果的所有实例化.以下是使用实例化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()
Run Code Online (Sandbox Code Playgroud)

在大多数情况下,这只是你需要知道的,但有一个问题.我们覆盖的__new__方法只会影响nx.Graph它的子类.例如,如果你调用nx.gn_graph,返回一个实例nx.DiGraph,它将没有我们花哨的扩展.您需要子类化nx.Graph您希望使用的每个子类,并添加所需的方法和属性.使用混合可以更容易在遵循DRY原则的同时一致地扩展子类.

虽然这个例子看起来很简单,但这种挂钩模块的方法难以概括,涵盖了可能出现的所有小问题.我相信只是根据手头的问题量身定做更容易.例如,如果您要连接的类定义了自己的自定义__new__方法,则需要在替换它之前存储它,并调用此方法而不是object.__new__.