5

I'm trying to make a list which is used throughout the application immutable. I thought wrapping this list in a tuple would do the trick, but it seems that tuple(list) doesn't actually wrap, but copies the list elements.

>>> a = [1, 2, 3, 4]
>>> b = tuple(a)
>>> b
(1, 2, 3, 4)
>>> a[0] = 2
>>> b # was hoping b[0] to be 2
(1, 2, 3, 4)

Is there an easy way of creating a list-backed "view" on this list that is immutable (wrt. operations on this view), but reflects any change that happened to the backing list?

I realise that this question has been asked before, but none of the responses address this view-backing list relationship (in fact some of the comments even suggest that tuples work the way I was hoping they do, but the above snippet suggests otherwise).

orange
  • 7,755
  • 14
  • 75
  • 139
  • 1
    So, basically, you want it to be selectively mutable? That's kind of hard to enforce. What problem are you actually trying to solve here? – Mark Tozzi Mar 12 '14 at 02:12
  • 2
    Why do you want the list to be immutable? It might be preferable to just state in the documentation that the list should not be changed. If the user changes it, that's his or her fault. – jme Mar 12 '14 at 02:29
  • I really am curious about the use case here...sounds interesting. – Paul Becotte Mar 12 '14 at 02:52
  • @PaulBecotte: The main reason was some refactoring for which I wanted to restrict access to a list member variable that I previously directly used. Internally, I still use a list and mutate it, but every time I change it, I don't necessarily want to be reminded to also update its external interface method (I use `@property` instead of the original member variable now). – orange Mar 12 '14 at 12:34

1 Answers1

12

If you don't want to copy the data, and want to pass an unchangeable "list" around, one way to do so is to create a proxy object, copy of a list, which disables all changing methods, and refer the reading methods to the original list - something along:

from collections import UserList

class ReadOnlyList(UserList):
    def __init__(self, original):
        self.data = original
    def insert(self, index=None, value=None):
        raise TypeError()
    __setitem__ = insert
    __delitem__ = insert
    append = insert
    extend = insert
    pop = insert
    reverse = insert
    sort = insert

By subclassing "UserList" one ensures all code dealing with the list data will go through the publicly exposed Python methods, and better yet, all the remaining methods are already implemented and proxy to the internal data attribute.

bellow, the original answer from 2014, focusing on Python 2


    class ReadOnlyList(list):
        def __init__(self, other):
            self._list = other
        
        def __getitem__(self, index):
            return self._list[index]
        
        def __iter__(self):
            return iter(self._list)
        
        def __slice__(self, *args, **kw):
            return self._list.__slice__(*args, **kw)
        
        def __repr__(self):
            return repr(self._list)

        def __len__(self):
            return len(self._list)
        
        def NotImplemented(self, *args, **kw):
            raise ValueError("Read Only list proxy")
        
        append = pop = __setitem__ = __setslice__ = __delitem__ = NotImplemented

And, of course, implement whatever other methods you judge necessary, either raising the error (or ignoring the writting instruction) - or acessing the corresponding object in the internal list.

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • 2
    The O.P. wants to keep changing the original list in other contexts, by what I understand of the question. Therefore it is unhashable by definition. If one wants a hash of the snapshot of the momment it wraps the inner list, the solution is to create a hash value that temporarily creates a tuple and picks that hash, and return that value on a call to `__hash__`. – jsbueno Mar 12 '14 at 02:17
  • @eyquem: I just did post it. And clicked on `edit` following the publishing of the post, to type in the example code. – jsbueno Mar 12 '14 at 02:19
  • Thanks @jsbueno. Too bad that no 'one line'-Python idiom or a special list came out of as a response, but this certainly does the trick. Thanks again. – orange Mar 13 '14 at 08:43
  • 1
    @jsbueno: One method you need to add to `ReadOnlyList` is `__len__`, otherwise the length is always `0`. – orange Mar 26 '14 at 00:10
  • @orange: yes, for sure. I've noticed it when playing around with this code, afterwars, but did not update the answer here. – jsbueno Mar 27 '14 at 02:49