title: Python read-only lists using the property decorator
body: <h2>Short Version</h2>
  
  <p>Can I make a read-only list using Python's property system?</p>
  
  <h2>Long Version</h2>
  
  <p>I've created a Python class that has a list as a member.  Internally, I'd like it to do something every time the list is modified.  If this were C++, I'd create getters and setters that would allow me to do my bookkeeping whenever the setter was called, and I'd have the getter return a <code>const</code> reference, so that the compiler would yell at me if I tried to do modify the list through the getter.  In Python, we have the property system, so that writing vanilla getters and setters for every data member is (thankfully) no longer necessary.  However, consider the following script:</p>
  
  <pre><code>def main():
  
      foo = Foo()
      print('foo.myList:', foo.myList)
  
      # Here, I'm modifying the list without doing any bookkeeping.
      foo.myList.append(4)
      print('foo.myList:', foo.myList)
  
      # Here, I'm modifying my "read-only" list.
      foo.readOnlyList.append(8)
      print('foo.readOnlyList:', foo.readOnlyList)
  
  
  class Foo:
      def __init__(self):
          self._myList = [1, 2, 3]
          self._readOnlyList = [5, 6, 7]
  
      @property
      def myList(self):
          return self._myList
  
      @myList.setter
      def myList(self, rhs):
          print("Insert bookkeeping here")
          self._myList = rhs
  
      @property
      def readOnlyList(self):
          return self._readOnlyList
  
  
  if __name__ == '__main__':
      main()
  </code></pre>
  
  <p>Output:</p>
  
  <pre><code>foo.myList: [1, 2, 3]
  # Note there's no "Insert bookkeeping here" message.
  foo.myList: [1, 2, 3, 4]
  foo.readOnlyList: [5, 6, 7, 8]
  </code></pre>
  
  <p>This illustrates that the absence of the concept of <code>const</code> in Python allows me to modify my list using the <code>append()</code> method, despite the fact that I've made it a property.  This can bypass my bookkeeping mechanism (<code>_myList</code>), or it can be used to modify lists that one might like to be read-only (<code>_readOnlyList</code>).</p>
  
  <p>One workaround would be to return a deep copy of the list in the getter method (i.e. <code>return self._myList[:]</code>).  This could mean a lot of extra copying, if the list is large or if the copy is done in an inner loop.  (But premature optimization is the root of all evil, anyway.)  In addition, while a deep copy would prevent the bookkeeping mechanism from being bypassed, if someone were to call <code>.myList.append()</code> , their changes would be silently discarded, which could generate some painful debugging.  It would be nice if an exception were raised, so that they'd know they were working against the class' design.</p>
  
  <p>A fix for this last problem would be not to use the property system, and make "normal" getter and setter methods:</p>
  
  <pre><code>def myList(self):
      # No property decorator.
      return self._myList[:]
  
  def setMyList(self, myList):
      print('Insert bookkeeping here')
      self._myList = myList
  </code></pre>
  
  <p>If the user tried to call <code>append()</code>, it would look like <code>foo.myList().append(8)</code>, and those extra parentheses would clue them in that they might be getting a copy, rather than a reference to the internal list's data.  The negative thing about this is that it is kind of un-Pythonic to write getters and setters like this, and if the class has other list members, I would have to either write getters and setters for those (eww), or make the interface inconsistent.  (I think a slightly inconsistent interface might be the least of all evils.)</p>
  
  <p>Is there another solution I'm missing?  Can one make a read-only list using Pyton's property system?</p>
  
  <h2>Edit: Solution</h2>
  
  <p>The two main suggestions seem to be either using a tuple as a read-only list, or subclassing list.  I like both of those approaches.  Returning a tuple from the getter, or using a tuple in the first place, prevents one from using the += operator, which can be a useful operator and also triggers the bookkeeping mechanism by calling the setter.  However, returning a tuple is a one-line change, which is nice if you would like to program defensively but judge that adding a whole other class to your script might be unnecessarily complicated.  (It can be a good sometimes to be minimalist and assume You Ain't Gonna Need It.)</p>
  
  <p>Here is an updated version of the script which illustrates both approaches, for anyone finding this via Google.</p>
  
  <pre><code>import collections
  
  
  def main():
  
      foo = Foo()
      print('foo.myList:', foo.myList)
  
      try:
          foo.myList.append(4)
      except RuntimeError:
          print('Appending prevented.')
  
      # Note that this triggers the bookkeeping, as we would like.
      foo.myList += [3.14]
      print('foo.myList:', foo.myList)
  
      try:
          foo.readOnlySequence.append(8)
      except AttributeError:
          print('Appending prevented.')
      print('foo.readOnlySequence:', foo.readOnlySequence)
  
  
  class UnappendableList(collections.UserList):
      def __init__(self, *args, **kwargs):
          data = kwargs.pop('data')
          super().__init__(self, *args, **kwargs)
          self.data = data
  
      def append(self, item):
          raise RuntimeError('No appending allowed.')
  
  
  class Foo:
      def __init__(self):
          self._myList = [1, 2, 3]
          self._readOnlySequence = [5, 6, 7]
  
      @property
      def myList(self):
          return UnappendableList(data=self._myList)
  
      @myList.setter
      def myList(self, rhs):
          print('Insert bookkeeping here')
          self._myList = rhs
  
      @property
      def readOnlySequence(self):
          # or just use a tuple in the first place
          return tuple(self._readOnlySequence)
  
  
  if __name__ == '__main__':
      main()
  </code></pre>
  
  <p>Output:</p>
  
  <pre><code>foo.myList: [1, 2, 3]
  Appending prevented.
  Insert bookkeeping here
  foo.myList: [1, 2, 3, 3.14]
  Appending prevented.
  foo.readOnlySequence: (5, 6, 7)
  </code></pre>
  
  <p>Thanks, everyone.</p>