I have a friend struggling with some code wight now, so I’m copy-pasting an interactive session with some object reference basics that I hope will clarify some things 😉
There’s also a pastie for this in http://paste.lisp.org/+2E0W for easier reading.
>>> # First, lets make a Class to help with tests >>> class Coco(object): def __init__(self, value=0): self.value = value >>> # Lets make a list of instances, the dumb way >>> a = [Coco()] * 4 >>> a [<__main__.Coco object at 0xb64c9cac>, <__main__.Coco object at 0xb64c9cac>, <__main__.Coco object at 0xb64c9cac>, <__main__.Coco object at 0xb64c9cac>] >>> # See that the addresses are the same? Lets look values >>> [i.value for i in a] [0, 0, 0, 0] >>> # Now, let's try to change one of them >>> a[0].value = 1 >>> # We now expect [1, 0, 0, 0], right? >>> [i.value for i in a] [1, 1, 1, 1] >>> # NOPE! All of them changed because it's THE SAME OBJECT >>> >>> # Let's do it again the smart way >>> a = [] >>> for i in range(4): a.append(Coco()) >>> a [<__main__.Coco object at 0xb64b906c>, <__main__.Coco object at 0xb64d128c>, <__main__.Coco object at 0xb64d1c2c>, <__main__.Coco object at 0xb64d1c0c>] >>> # See that the addresses are different? >>> [i.value for i in a] [0, 0, 0, 0] >>> a[0].value = 1 >>> [i.value for i in a] [1, 0, 0, 0] >>> # Pretty cool huh? >>> >>> # Lets also put the list in variable b >>> b = a >>> [i.value for i in a] [1, 0, 0, 0] >>> [i.value for i in b] [1, 0, 0, 0] >>> # And change a value in b >>> b[0].value = 0 >>> [i.value for i in b] [0, 0, 0, 0] >>> [i.value for i in a] [0, 0, 0, 0] >>> # a and b are the same object. A no brainer! >>> >>> # Now let's copy once with copy() and once with deepcopy() >>> from copy import copy, deepcopy >>> a_copy = copy(a) # You can also do =a[:] for lists and =a.copy() for dicts >>> a_deepcopy = deepcopy(a) >>> a [<__main__.Coco object at 0xb64b906c>, <__main__.Coco object at 0xb64d128c>, <__main__.Coco object at 0xb64d1c2c>, <__main__.Coco object at 0xb64d1c0c>] >>> a_copy [<__main__.Coco object at 0xb64b906c>, <__main__.Coco object at 0xb64d128c>, <__main__.Coco object at 0xb64d1c2c>, <__main__.Coco object at 0xb64d1c0c>] >>> a_deepcopy [<__main__.Coco object at 0xb64d1a4c>, <__main__.Coco object at 0xb64d1a2c>, <__main__.Coco object at 0xb64d1a0c>, <__main__.Coco object at 0xb64d19ec>] >>> a_copy[0].value = 4 >>> a_deepcopy[1].value = 6 >>> # Can you foretell what will happen now? >>> [i.value for i in a_copy] [4, 0, 0, 0] >>> [i.value for i in a_deepcopy] [0, 6, 0, 0] >>> [i.value for i in a] [4, 0, 0, 0] >>> # While a and a_copy are not the same, their contents are shared! >>> # Here's how to try when in doubt >>> a is b True >>> a is a_copy False >>> a[0] is a_copy[0] True >>> a[0] is a_deepcopy[0] False >>> >>> # PS. If you make a list the dumb way >>> a = [Coco()] * 4 >>> # ..you'll have a list of the same objects even after a deepcopy >>> k = [Coco()] * 4 >>> k_deepcopy = deepcopy(k) >>> k_deepcopy[0].value = 1 >>> [i.value for i in k_deepcopy] [1, 1, 1, 1] >>> k_deepcopy[0] is k_deepcopy[3] True >>> # ..although, not the same ones as the originals >>> [i.value for i in k] [0, 0, 0, 0] >>> k_deepcopy[0] is k[0] False >>> >>> # Hope that's helpful ;) >>>
Nick
Nice article, just a few notes.
The dumb way is pretty clever when your objects in the lists are simple (int, float, etc) because it is much faster and it has the same result as the one demonstrated with the more complicated objects that you elaborate. That’s why it still exists.
It may be helpful for someone that the operator == checks whether two variables refer to the same values while the operator is checks whether to variables refer to the same object.
Yep! Even the “dumb” way is quite useable if you know what happens behind the scenes.
And yes, the ‘is’ operator is great for testing and debugging! 😉