Могу ли я эффективно обменивать два экземпляра класса путем замены __dict__?
У меня большой класс с большим количеством участников, и довольно много ссылок на экземпляры этого класса. К сожалению (по разумным причинам) все эти ссылки неверны.
Вместо повторного создания каждого (и поиска и обновления везде, на которые ссылаются объекты), или добавления дополнительного уровня косвенности каждый раз, когда я обращаюсь к этому классу или индивидуально меняя элементы, я определил метод:
def swap(self, other):
assert(isinstance(other, self.__class__))
self.__dict__, other.__dict__ = other.__dict__, self.__dict__
чтобы я мог:
instance_a.swap(instance_b)
# now all references to instance_a everywhere are as if instance_a is instance_b
Вопрос:
Кажется, что он работает нормально (я не использую __slots__
), но похоже, что может быть причина, по которой я не должен этого делать, есть ли?
Вот мой фактический прецедент:
У меня есть тип, который реализует операторы сравнения (обязательно) дорогостоящим способом. У меня есть различные отсортированные структуры данных, содержащие объекты этого типа.
Когда я что-то делаю с одним из объектов, я знаю, что порядок сравнения изменился, и этот порядок в моих структурах данных (все из них!) можно восстановить, заменив измененный объект на "следующий больше".
Ответы
Ответ 1
Если я правильно понимаю, вы фактически меняете экземпляры класса, а не классы.
Состояние экземпляра сохраняется в двух возможных местах: __slots__
и __dict__
. Если вы меняете их, вы в основном меняете экземпляры, сохраняя первоначальные привязки имен. Одно из предостережений заключается в том, что класс не может быть неизменным (не должен определять __hash__()
), поскольку любые экземпляры, которые уже были членами набора или ключей в словаре, затем становятся безвозвратными.
Если бы это был я, я бы подумал, что вместо этого метод .swap() будет методом класса - я думаю, что он будет читать легче:
class SomeBigClass():
@staticmethod
def swap_dict(instance_a, instance_b):
instance_a.__dict__, instance_b.__dict__ = \
instance_b.__dict__, instance_a.__dict__
@classmethod
def swap_slot(cls, instance_a, instance_b):
values = []
for attr in cls.__slots__:
values.append(
(attr,
getattr(instance_a, attr),
getattr(instance_b, attr),
))
for attr, val1, val2 in values:
setattr(instance_a, attr, val2)
setattr(instance_b, attr, val1)
а затем позже
SomeBigClass.swap_dict(this_instance, other_instance)
или
SomeBigClass.swap_slot(this_instance, other_instance)
Почему бы вам не сделать это? Если у вас есть привязанные к именам экземпляры, вы не должны этого делать. Рассмотрим:
frubbah = SomeBigClass(attr="Hi, Mom!")
zikroid = SomeBigClass(attr='Hi, Dad!")
SomeBigClass.swap_dict(frubbah, zikroid)
После свопа потенциально все, что вы думали, что знаете о zikroid, изменилось.
Ответ 2
Изменить
То, что вы делаете, возможно, хотя это заставит людей съеживаться, потому что это хаки. Если это вообще возможно, я предлагаю вам взглянуть на переписывание/рефакторинг операторов сравнения. Это даст вам наилучший результат. Конечно, не зная о масштабах или сроках, очень сложно сказать, сразу ли это практично, но поверьте мне, вы потратите меньше времени на переписывание в долгосрочной перспективе, если сможете сделать что-то "правильно".
Оригинальные
Реально, похоже, что вы имеете дело с тремя классами - объектом данных и двумя служебными классами, но это еще одна проблема.
Это сломается, поэтому я собираюсь пойти дальше и сказать: "Нет, вы не можете менять классы путем замены __dict__
s":
>>> class Foo:
... def __init__(self):
... self.__bar = 1
... def printBar(self):
... print self.__bar
...
>>> class Bar:
... def __init__(self):
... self.__bar=2
... def printBar(self):
... print self.__bar
...
>>> f=Foo()
>>> f.printBar() # works as expected
1
>>> f=Foo()
>>> b=Bar()
>>> f.__dict__, b.__dict__ = b.__dict__, f.__dict__
>>> f.printBar() # attempts to access private value from another class
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in printBar
AttributeError: Foo instance has no attribute '_Foo__bar'
Ответ 3
Это обменивание должно быть реализовано в ваших отсортированных структурах данных, а не в этом классе для его экземпляров.
Ответ 4
Для вашего собственного здравомыслия и того, кто может использовать ваш код после вас, если вы решите использовать эту функцию, вы должны задокументировать ад, чтобы вы знали, что происходит. Ваша более самостоятельная документальная альтернатива, конечно же, состоит в том, чтобы написать функцию, которая поменяет все элементы данных по отдельности. Очевидно, это не очень перспективно, если ваша модель данных будет меняться, но она будет менее запутанной. Если это не то, что вы хотите записать вручную, вы можете определить функцию для обмена одним полем данных и запустить ее по всем вашим атрибутам класса:
def swap (a, b):
for key in a.__dict__.keys():
swapOneField(key, a, b)
и swapOneField
получить значения из a.__dict__
и b.__dict__
и использовать алгоритм подкачки по вашему выбору. Это более подробный и, вероятно, менее эффективный, но если вы сохраняете больше времени, не отлаживая его, чем теряете при его запуске, это, вероятно, стоит вашего времени.