Pergunta

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

Foi útil?

Solução

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.

Outras dicas

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'])}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top