0

I understand that a namedtuple in python is immutable and the values of its attributes cant be reassigned directly

N = namedtuple("N",['ind','set','v'])
def solve()
    items=[]
    R = set(range(0,8))
    for i in range(0,8):
        items.append(N(i,R,8))  
    items[0].set.remove(1)
    items[0].v+=1

Here last like where I am assigning a new value to attribute 'v' will not work. But removing the element '1' from the set attribute of items[0] works.

Why is that and will this be true if set attribute were of List type

Pratyush Dhanuka
  • 1,405
  • 2
  • 11
  • 21
  • 3
    Remember: you are not changing the tuple, but the object inside the tuple. – thefourtheye Mar 22 '14 at 08:28
  • Note that a `tuple` that contains a mutable object isn't hashable. Try to put it inside a `dict` and you'll get an error. Immutable containers are truly immutable (and thus possibly hashable) if and only if all their elements are immutable. – Bakuriu Mar 22 '14 at 10:55

2 Answers2

0

You mutate the set, not the tuple. And sets are mutable.

>>> s = set()
>>> t = (s,)
>>> l = [s]
>>> d = {42: s}
>>> t
(set([]),)
>>> l
[set([])]
>>> d
{42: set([])}
>>> s.add('foo')
>>> t
(set(['foo']),)
>>> l
[set(['foo'])]
>>> d
{42: set(['foo'])}
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
0

Immutability does not get conferred on mutable objects inside the tuple. All immutability means is you can't change which particular objects are stored - ie, you can't reassign items[0].set. This restriction is the same regardless of the type of that variable - if it was a list, doing items[0].list = items[0].list + [1,2,3] would fail (can't reassign it to a new object), but doing items[0].list.extend([1,2,3]) would work.

Think about it this way: if you change your code to: new_item = N(i,R,8)

then new_item.set is now an alias for R (Python doesn't copy objects when you reassign them). If tuples conferred immutability to mutable members, what would you expect R.remove(1) to do? Since it is the same set as new_item.set, any changes you make to one will be visible in the other. If the set had become immutable because it has become a member of a tuple, R.remove(1) would suddenly fail. All method calls in Python work or fail depending on the object only, not on the variable - R.remove(1) and new_item.set.remove(1) have to behave the same way.

This also means that:

R = set(range(0,8))
for i in range(0,8):
    items.append(N(i,R,8)) 

probably has a subtle bug. R never gets reassigned here, and so every namedtuple in items gets the same set. You can confirm this by noticing that items[0].set is items[1].set is True. So, anytime you mutate any of them - or R - the modification would show up everywhere (they're all just different names for the same object).

This is a problem that usually comes up when you do something like

a = [[]] * 3
a[0].append(2)

and a will now be [[2], [2], [2]]. There are two ways around this general problem:

First, be very careful to create a new mutable object when you assign it, unless you do deliberately want an alias. In the nested lists example, the usual solution is to do a = [[] for _ in range(3)]. For your sets in tuples, move the line R = ... to inside the loop, so it gets reassigned to a new set for each namedtuple.

The second way around this is to use immutable types. Make R a frozenset, and the ability to add and remove elements goes away.

lvc
  • 34,233
  • 10
  • 73
  • 98
  • Yes you are right, i suffered above problems a few days ago when I started python, though in this one i wanted all elements to share the set, so that i need to change only 1 and can check results in others. And thanks for the answer now i got better idea of the objects in python – Pratyush Dhanuka Mar 22 '14 at 15:37