38

Say I want to create a class for car, tractor and boat. All these classes have an instance of engine and I want to keep track of all the engines in a single list. If I understand correctly if the motor object is mutable i can store it as an attribute of car and also the same instance in a list.

I cant track down any solid info on whether user defined classes are mutable and if there is a choice to choose when you define them, can anybody shed some light?

tMC
19.4k15 gold badges68 silver badges101 bronze badges
asked Aug 22, 2012 at 15:24
4
  • This might help: stackoverflow.com/a/8056288/341459 Commented Aug 22, 2012 at 15:30
  • 1
    If you have 2 cars, each with the same make and model of engine, do you want them to reference the same engine object, or different (yet equivalent) engine objects? Commented Aug 22, 2012 at 15:49
  • 1
    @Jonathan: You are supposed to select answers on your questions. You almost never do that. Commented Jul 3, 2013 at 19:48
  • Well spotted, and thanks for pointing it out, there are certainly a few I haven't found an answer I find fully answers the question but equally there are some I should mark as answered, Ill remedie that Commented Jul 3, 2013 at 21:08

4 Answers 4

43

User classes are considered mutable. Python doesn't have (absolutely) private attributes, so you can always change a class by reaching into the internals.

For using your class as a key in a dict or storing them in a set, you can define a .__hash__() method and a .__eq__() method, making a promise that your class is immutable. You generally design your class API to not mutate the internal state after creation in such cases.

For example, if your engines are uniquely defined by their id, you can use that as the basis of your hash:

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

Now you can use instances of class Engine in sets:

>>> 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>])

In the above sample I add another Engine(1) instance to the set, but it is recognized as already present and the set didn't change.

Note that as far as lists are concerned, the .__eq__() implementation is the important one; lists don't care if an object is mutable or not, but with the .__eq__() method in place you can test if a given engine is already in a list:

>>> Engine(1) in [eng1, eng2]
True
answered Aug 22, 2012 at 15:29
4
  • In sets, as keys for dicts, and anything else that requires a value be hashable. Just be careful, as now even though the value you are hashing is mutable, interfaces using hash generally assume that the data for the hash is immutable for a given object or value. Also, using id can be confusing since it is a reserved keyword in Python. Commented Aug 22, 2012 at 15:42
  • 3
    I think that it is worth pointing out that user defined classes have __hash__ already implemented (for CPython, __hash__ defaults to id(self), so they can be used as dictionary keys... but then you have to use the same class every time you want to get an item from the dictionary. Commented Aug 22, 2012 at 15:42
  • But you didn't make sure there won't be two instances with the same ID. Commented Aug 22, 2012 at 15:43
  • 1
    @slallum: Nope, I didn't. Note that the OP didn't (explicitly) ask for that either. Also, you don't necessarily need to prevent multiple instances with the same id, the code treats them as the same. Commented Aug 22, 2012 at 15:45
1

All objects (with the exception of a few in the standard library, some that implement special access mechanisms using things like descriptors and decorators, or some implemented in C) are mutable. This includes instances of user defined classes, classes themselves, and even the type objects that define the classes. You can even mutate a class object at runtime and have the modifications manifest in instances of the class created before the modification. By and large, things are only immutable by convention in Python if you dig deep enough.

answered Aug 22, 2012 at 15:31
1

I think you're confusing mutability with how python keeps references -- Consider:

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.

Now to your question about getting/storing a list of engines globally -- There are a few different ways to do this, here's one:

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

The example above could be improved a little bit by having the EngineFactory._engines dict store weakref.ref objects instead of actually storing real references to the objects. In that case, you'd check to make sure the reference is still alive (hasn't been garbage collected) before you return a new reference to the object.

answered Aug 22, 2012 at 16:04
-3

EDIT: This is conceptually wrong, The immutable object in python can shed some light as to why.

class Engine():
 def __init__(self, sn):
 self.sn = sn
a = Engine(42)
b = a
print (a is b)

prints True.

answered Aug 22, 2012 at 15:32
4
  • 2
    This has nothing to do with mutability. Commented Aug 22, 2012 at 15:35
  • 1
    What does this have to do with the question? You don't provide any explanation of why this code is relevant. Commented Aug 22, 2012 at 15:35
  • I thought that showed how the OP can "store it as an attribute of car and also the same instance in a list" Commented Aug 22, 2012 at 15:37
  • @LevLevitsky -- reading the question a few times, I don't think you were that far off. I think your answer could use a little work explaining why it is relevant, but I don't think the OP really understands python references (and somehow has gotten that confused with mutability). Commented Aug 22, 2012 at 16:09

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.