web-dev-qa-db-ja.com

基本クラスを派生クラスにキャストpython(またはクラスを拡張するよりPython的な方法)

Networkx pythonパッケージを拡張し、特定のニーズに合わせてGraphクラスにいくつかのメソッドを追加する必要があります

これを行うことについて私が考えた方法は、単純に新しいクラスNewGraphを派生させ、必要なメソッドを追加することです。

ただし、networkxには、Graphオブジェクトを作成して返す(ランダムグラフを生成するなど)関数が他にもいくつかあります。新しいメソッドを使用できるように、これらのGraphオブジェクトをNewGraphオブジェクトに変換する必要があります。

これを行う最良の方法は何ですか?または、まったく別の方法で問題に取り組む必要がありますか?

41
zenna

動作を追加するだけで、追加のインスタンス値に依存しない場合は、オブジェクトの__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でキャストするのと同じように、問題を考えずに行うことはできません。かなり限定された例を掲載しましたが、制約内に留まることができる場合(動作を追加するだけで、新しいインスタンス変数はありません)、これは問題の解決に役立つ可能性があります。

54
PaulMcG

モジュールに手を触れずに、モジュール内のクラスをカスタムメイドのサブクラスに「魔法のように」置き換える方法を次に示します。これは、通常のサブクラス化手順のほんの数行にすぎないため、サブクラス化の(ほぼ)すべての機能と柔軟性をボーナスとして提供します。たとえば、これにより、必要に応じて新しい属性を追加できます。

_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を返すようにします。その中で、objectの___new___メソッドの代わりにNewGraphの___new___メソッドを呼び出します。後者は、置き換え、したがって、無限の再帰が発生します。

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

ほとんどの場合、これはあなたが知る必要があるすべてですが、1つの落とし穴があります。 ___new___メソッドのオーバーライドは、サブクラスではなく_nx.Graph_にのみ影響します。たとえば、_nx.gn_graph_を呼び出すと、_nx.DiGraph_のインスタンスが返されます。これには、特別な拡張機能はありません。処理する_nx.Graph_の各サブクラスをサブクラス化し、必要なメソッドと属性を追加する必要があります。 mix-ins を使用すると、 [〜#〜] dry [〜#〜] の原則に従いながら、サブクラスを一貫して拡張しやすくなります。

この例は簡単に思えるかもしれませんが、モジュールにフックするこの方法は、発生する可能性のあるすべての小さな問題をカバーする方法で一般化することは困難です。目前の問題に合わせて調整する方が簡単だと思います。たとえば、フックするクラスが独自のカスタム___new___メソッドを定義している場合、それを保存してから置き換える前に、_object.__new___ではなくこのメソッドを呼び出す必要があります。

13

単純なケースでは、サブクラスを書くこともできます__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())

グラフデータをロードします。

0
Aric

関数がGraphオブジェクトを作成している場合、それらをNewGraphオブジェクトに変換することはできません。

NewGraphのもう1つのオプションは、グラフではなくグラフを持つことです。持っている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):
        ....
0
Ned Batchelder

独自に定義する前に、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)
_
0
cjbarth