Domanda

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

È stato utile?

Soluzione

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.

Altri suggerimenti

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'])}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top