Литой базовый класс на полученный класс Python (или более питоновый способ расширения классов)
-
27-09-2019 - |
Вопрос
Мне нужно расширить пакет NetworkX Python и добавить несколько методов в Graph
класс для моей особой необходимости
То, как я думал о том, чтобы сделать это просто выводит новый класс NewGraph
, и добавляя необходимые методы.
Однако в NetworkX есть несколько других функций, которые создают и возвращают Graph
объекты (например, генерировать случайный график). Теперь мне нужно превратить эти Graph
объекты в NewGraph
Объекты, чтобы я мог использовать мои новые методы.
Какой лучший способ сделать это? Или я должен ли я решать проблему совершенно разным образом?
Решение
Если вы просто добавляете поведение, а не в зависимости от дополнительных значений экземпляра, вы можете назначить объекту __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), то это может помочь решить вашу проблему.
Другие советы
Вот как «волшебно» заменить класс в модуль с пользовательским подклассом, не касаясь модуля. Это всего несколько дополнительных линий из нормальной процедуры подклассов, и поэтому дает вам (почти) всю мощность и гибкость подклассов в качестве бонуса. Например, это позволяет добавлять новые атрибуты, если хотите.
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
, У него не будет никаких наших модных расширений. Вам нужно подкласс каждый из подклассов nx.Graph
Что вы хотите работать и добавить ваши необходимые методы и атрибуты. С использованием смешивание может облегчить последовательно расширить подклассы, пока не подчиняется СУХОЙ принцип.
Хотя этот пример может показаться достаточно простым, этот метод подключения к модулю трудно обобщить таким образом, чтобы все охватывали все небольшие проблемы, которые могут обрезать. Я верю, что легче просто адаптировать его на проблему под рукой. Например, если класс, который вы подключаете, определяет свой собственный пользовательский __new__
Способ, вам нужно хранить его, прежде чем заменять его и вызовите этот метод вместо object.__new__
.
Если функция создает объекты графика, вы не можете превратить их в Newgraph объекты.
Другой вариант для Newgraph - иметь график, а не график. Вы делегируете методы графиков в объект графа, и вы можете обернуть любой объект графа в новый объект 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):
....
Для вашего простого случая вы также можете написать свой подкласс __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())
загрузить ваши графические данные.
Вы могли бы просто создать новый 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)
Вы, ребята, попробовалиPython] литой базовый класс для производного класса
Я проверил это, и кажется, это работает. Также я думаю, что этот метод немного лучше, чем ниже один, так как ниже, не выполняется в этом Функция производной функции.
c.__class__ = CirclePlus