将基类转换为派生类 python(或者更符合 Python 风格的扩展类的方式)

2025-03-10 08:47:00
admin
原创
49
摘要:问题描述:我需要扩展 Networkx python 包并向类中添加一些方法Graph以满足我的特定需要我考虑这样做的方法是简单地派生一个新类NewGraph,然后添加所需的方法。但是 networkx 中还有其他几个函数可以创建并返回Graph对象(例如生成随机图)。我现在需要将这些Graph对象转换为Ne...

问题描述:

我需要扩展 Networkx python 包并向类中添加一些方法Graph以满足我的特定需要

我考虑这样做的方法是简单地派生一个新类NewGraph,然后添加所需的方法。

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

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


解决方案 1:

如果您只是添加行为,而不依赖于其他实例值,则可以分配给对象的__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 中的强制类型转换一样,如果不经过深思熟虑,是无法完成的。我发布了一个相当有限的示例,但如果您可以遵守限制(只添加行为,不添加新的实例变量),那么这可能有助于解决您的问题。

解决方案 2:

以下是如何“神奇地”用自定义子类替换模块中的类而无需触及模块。这只需要在正常的子类化过程中多写几行代码,因此可以为您提供(几乎)子类化的所有功能和灵活性。例如,如果您愿意,这允许您添加新属性。

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的每个子类进行子类化,并添加所需的方法和属性。使用mix-ins可能会更容易在遵守DRY原则的同时一致地扩展子类。nx.Graph

虽然这个例子看起来很简单,但这种挂接模块的方法很难推广,无法解决所有可能出现的小问题。我认为根据手头的问题进行调整会更容易。例如,如果您要挂接的类定义了自己的自定义__new__方法,则需要在替换它之前将其存储起来,然后调用此方法而不是object.__new__

解决方案 3:

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

解决方案 4:

您可以简单地创建一个新的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:

我在为 做贡献时遇到了同样的问题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 的回答,正如他/她提到的,在这种情况下不是一个好主意。

解决方案 6:

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

解决方案 7:

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

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())

加载图形数据。

解决方案 8:

赋值__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

解决方案 9:

你们尝试过
[Python]将基类转换为派生类吗

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

c.__class__ = CirclePlus
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2560  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1552  
  IPD(Integrated Product Development)流程作为一种先进的产品开发管理模式,在众多企业中得到了广泛应用。其中,技术评审与决策评审是IPD流程中至关重要的环节,它们既有明显的区别,又存在紧密的协同关系。深入理解这两者的区别与协同,对于企业有效实施IPD流程,提升产品开发效率与质量具有重要意义...
IPD管理流程   1  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、ClickUp、Freshdesk、GanttPRO、Planview、Smartsheet、Asana、Nifty、HubPlanner、Teamwork。在当今快速变化的商业环境中,项目管理软件已成为企业提升效率、优化资源分配和确保项目按时交付的关键工具。然而...
项目管理系统   2  
  建设工程项目质量关乎社会公众的生命财产安全,也影响着企业的声誉和可持续发展。高质量的建设工程不仅能为使用者提供舒适、安全的环境,还能提升城市形象,推动经济的健康发展。在实际的项目操作中,诸多因素会对工程质量产生影响,从规划设计到施工建设,再到后期的验收维护,每一个环节都至关重要。因此,探寻并运用有效的方法来提升建设工程...
工程项目管理制度   3  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用