Приведение базового класса к производному классу python (или более pythonic способ расширения классов) - PullRequest
36 голосов
/ 12 августа 2010

Мне нужно расширить пакет Python для Networkx и добавить несколько методов в класс Graph для моей конкретной потребности

То, как я думал об этом, - просто получить новый класс, скажем NewGraph,и добавление необходимых методов.

Однако в сети x есть несколько других функций, которые создают и возвращают Graph объекты (например, генерируют случайный граф).Теперь мне нужно превратить эти Graph объекты в NewGraph объекты, чтобы я мог использовать свои новые методы.

Каков наилучший способ сделать это?Или я должен решать проблему совершенно по-другому?

Ответы [ 6 ]

49 голосов
/ 12 августа 2010

Если вы просто добавляете поведение и не зависите от дополнительных значений экземпляра, вы можете присвоить объекту __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, это не следует делать, не задумываясь над вопросом. Я опубликовал довольно ограниченный пример, но если вы можете остаться в пределах ограничений (просто добавьте поведение, без новых экземпляров vars), то это может помочь решить вашу проблему.

12 голосов
/ 17 января 2011

Вот как «волшебным образом» заменить класс в модуле на пользовательский подкласс, не касаясь модуля. Это всего лишь несколько дополнительных строк из обычной процедуры создания подклассов, и, следовательно, дает вам (почти) всю мощь и гибкость подклассов в качестве бонуса. Например, это позволяет вам добавлять новые атрибуты, если вы хотите.

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, с которым вы хотите работать, и добавить необходимые методы и атрибуты. Использование смешанных модулей может упростить последовательное расширение подклассов при соблюдении принципа DRY .

Хотя этот пример может показаться достаточно простым, этот метод подключения к модулю сложно обобщить так, чтобы охватить все небольшие проблемы, которые могут возникнуть. Я полагаю, что легче приспособить это к проблеме под рукой. Например, если класс, к которому вы подключаетесь, определяет свой собственный метод __new__, вам необходимо сохранить его перед заменой и вызвать этот метод вместо object.__new__.

0 голосов
/ 06 июня 2017

Вы могли бы просто создать новый 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 голосов
/ 25 января 2013

Ребята, вы пробовали [Python] привести базовый класс к производному классу

Я проверил его, и, похоже, он работает.Также я думаю, что этот метод немного лучше, чем ниже, так как ниже один не выполняет init функцию производной функции.

c.__class__ = CirclePlus
0 голосов
/ 18 января 2011

Для вашего простого случая вы также можете написать свой подкласс __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 голосов
/ 12 августа 2010

Если функция создает объекты Graph, вы не можете превратить их в объекты NewGraph.

Другой вариант для NewGraph - это иметь График, а не График. Вы делегируете методы 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):
        ....
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...