Python read-only lists using the property decorator
You could have method return a wrapper around your original list -- collections.Sequence
might be of help for writing it. Or, you could return a tuple
-- The overhead of copying a list into a tuple is often negligible.
Ultimately though, if a user wants to change the underlying list, they can and there's really nothing you can do to stop them. (After all, they have direct access to self._myList
if they want it).
I think that the pythonic way to do something like this is to document that they shouldn't change the list and that if the do, then it's their fault when their program crashes and burns.
despite the fact that I've made it a property
It does not matter if it's a property, you are returning a pointer to the list so you can modify it.
I'd suggest creating a list subclass and overriding append
and __add__
methods
The proposed solutions of returning a tuple or subclassing list for the return, seem like nice solutions, but i was wondering whether it wouldn't be easier to subclass the decorator instead? Not sure it this might be a stupid idea:
- using this
safe_property
protects against accidental sending mixed API signals (internal immutable attributes are "protected" against all operations, while the for mutable attributes, some operations are still allowed with the normalproperty
builtin) - advantage: easier to use than to implement custom return types everywhere -> easier to internalize
- disadvantage: necessity to use different name
class FrozenList(list):
def _immutable(self, *args, **kws):
raise TypeError('cannot change object - object is immutable')
pop = _immutable
remove = _immutable
append = _immutable
clear = _immutable
extend = _immutable
insert = _immutable
reverse = _immutable
class FrozenDict(dict):
def _immutable(self, *args, **kws):
raise TypeError('cannot change object - object is immutable')
__setitem__ = _immutable
__delitem__ = _immutable
pop = _immutable
popitem = _immutable
clear = _immutable
update = _immutable
setdefault = _immutable
class safe_property(property):
def __get__(self, obj, objtype=None):
candidate = super().__get__(obj, objtype)
if isinstance(candidate, dict):
return FrozenDict(candidate)
elif isinstance(candidate, list):
return FrozenList(candidate)
elif isinstance(candidate, set):
return frozenset(candidate)
else:
return candidate
class Foo:
def __init__(self):
self._internal_lst = [1]
@property
def internal_lst(self):
return self._internal_lst
@safe_property
def internal_lst_safe(self):
return self._internal_lst
if __name__ == '__main__':
foo = Foo()
foo.internal_lst.append(2)
# foo._internal_lst is now [1, 2]
foo.internal_lst_safe.append(3)
# this throws an exception
Very much interested in other opinions on this as i haven't seen this implemented somewhere else.