Являются пользовательскими классами, изменяемыми
Скажем, я хочу создать класс для car
, tractor
и boat
. Все эти классы имеют экземпляр engine
, и я хочу отслеживать все движки в одном списке. Если я правильно понимаю, если объект двигателя изменен, я могу сохранить его как атрибут car
, а также тот же экземпляр в списке.
Я не могу найти какую-либо достоверную информацию о том, изменяются ли пользовательские классы, и если есть выбор, когда вы их определяете, может ли кто-нибудь пролить некоторый свет?
Ответы
Ответ 1
Пользовательские классы считаются изменчивыми. Python не имеет (абсолютно) частных атрибутов, поэтому вы всегда можете изменить класс, достигнув внутренних элементов.
Для использования вашего класса в качестве ключа в dict
или сохранения его в set
вы можете определить метод .__hash__()
и .__eq__()
, обещая, что ваш класс неизменен. Обычно вы создаете свой API класса, чтобы не мутировать внутреннее состояние после создания в таких случаях.
Например, если ваши двигатели уникально определены своим идентификатором, вы можете использовать это как основу своего хэша:
class Engine(object):
def __init__(self, id):
self.id = id
def __hash__(self):
return hash(self.id)
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.id == other.id
return NotImplemented
Теперь вы можете использовать экземпляры класса Engine в наборах:
>>> eng1 = Engine(1)
>>> eng2 = Engine(2)
>>> eng1 == eng2
False
>>> eng1 == eng1
True
>>> eng1 == Engine(1)
True
>>> engines = set([eng1, eng2])
>>> engines
set([<__main__.Engine object at 0x105ebef10>, <__main__.Engine object at 0x105ebef90>])
>>> engines.add(Engine(1))
>>> engines
set([<__main__.Engine object at 0x105ebef10>, <__main__.Engine object at 0x105ebef90>])
В приведенном выше примере я добавляю в набор еще один экземпляр Engine(1)
, но он распознается как уже присутствующий, и набор не изменился.
Обратите внимание, что в отношении списков важна реализация .__eq__()
; спискам не важно, изменен ли объект или нет, но с помощью метода .__eq__()
вы можете проверить, присутствует ли данный движок в списке:
>>> Engine(1) in [eng1, eng2]
True
Ответ 2
Все объекты (за исключением нескольких в стандартной библиотеке, некоторые, которые реализуют специальные механизмы доступа, используя такие вещи, как дескрипторы и декораторы или некоторые, реализованные на C), являются изменяемыми. Сюда входят экземпляры пользовательских классов, самих классов и даже типы объектов, которые определяют классы. Вы можете даже мутировать объект класса во время выполнения и проявлять изменения в экземплярах класса, созданного до модификации. По большому счету, вещи могут быть неизменными по соглашению на Python, если вы копаете достаточно глубоко.
Ответ 3
Я думаю, вы смешиваете изменчивость с тем, как python сохраняет ссылки - Рассмотрим:
class Foo(object):
pass
t = (1,2,Foo()) # t is a tuple, :. t is immutable
b = a[2] # b is an instance of Foo
b.foo = "Hello" # b is mutable. (I just changed it)
print (hash(b)) # b is hashable -- although the default hash isn't very useful
d = {b : 3} # since b is hashable, it can be used as a key in a dictionary (or set).
c = t # even though t is immutable, we can create multiple references to it.
a = [t] # here we add another reference to t in a list.
Теперь на ваш вопрос о получении/хранении списка движков по всему миру. Существует несколько разных способов сделать это, вот один из них:
class Engine(object):
def __init__(self, make, model):
self.make = make
self.model = model
class EngineFactory(object):
def __init__(self,**kwargs):
self._engines = kwargs
def all_engines(self):
return self._engines.values()
def __call__(self,make, model):
""" Return the same object every for each make,model combination requested """
if (make,model) in _engines:
return self._engines[(make,model)]
else:
a = self._engines[(make,model)] = Engine(make,model)
return a
engine_factory = EngineFactory()
engine1 = engine_factory('cool_engine',1.0)
engine2 = engine_factory('cool_engine',1.0)
engine1 is engine2 #True !!! They're the same engine. Changing engine1 changes engine2
Приведенный выше пример может быть немного улучшен благодаря наличию объектов EngineFactory._engines
dict store weakref.ref
вместо фактического хранения реальных ссылок на объекты. В этом случае вы должны убедиться, что ссылка все еще жива (не была собрана мусором), прежде чем вы вернете новую ссылку на объект.
Ответ 4
EDIT: это концептуально неправильно, Неизменяемый объект в python может пролить свет на вопрос почему.
class Engine():
def __init__(self, sn):
self.sn = sn
a = Engine(42)
b = a
print (a is b)
выводит True
.