Внедрить базовый класс в производный класс python (или более питоновский путь расширения классов)
Мне нужно расширить пакет python Networkx и добавить несколько методов в класс 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>
Это как можно ближе к "cast", как вы можете попасть на 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) 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
, с которым вы хотите работать, и добавить необходимые методы и атрибуты. Использование mix-ins может упростить последовательное расширение подклассов при соблюдении DRY.
Хотя этот пример может показаться достаточно простым, этот метод подключения к модулю трудно обобщить таким образом, чтобы он охватывал все небольшие проблемы, которые могут возникнуть. Я считаю, что проще просто адаптировать его к проблеме. Например, если класс, с которым вы подключаетесь, определяет свой собственный метод __new__
, вам нужно сохранить его перед заменой и вызвать этот метод вместо object.__new__
.
Ответ 3
Если функция создает объекты 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):
....
Ответ 4
Для вашего простого случая вы также можете записать свой подкласс __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())
чтобы загрузить данные графика.
Ответ 5
Вы можете просто создать новый 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)
Ответ 6
Вы, ребята, пытались
[Python] лить базовый класс в производный класс
Я тестировал его, и кажется, что он работает. Также я думаю, что этот метод лучше, чем ниже одного, поскольку ниже не выполняется функция init производной функции.
c.__class__ = CirclePlus