@property
is a nice way to define getters. When the property is mutable, the reference returned can be used to modify the property in ways not controlled by the class definition. I'll use a banana stand as a motivating analogy, but this issue applies to any class that wraps a container.
class BananaStand:
def __init__(self):
self._money = 0
self._bananas = ['b1', 'b2']
@property
def bananas(self):
return self._bananas
def buy_bananas(self, money):
change = money
basket = []
while change >= 1 and self._bananas:
change -= 1
basket.append(self._bananas.pop())
self._money += 1
return change, basket
I would like visitors to the banana stand to pay for their bananas. Unfortunately, there's nothing stopping a monkey (who doesn't know any better) from taking one of my bananas. The monkey didn't have to use the internal attribute _banana
, they just took a banana without paying.
def take_banana(banana_stand):
return banana_stand.bananas.pop()
>>> stand = BananaStand()
>>> stand.bananas
['b1', 'b2']
>>> take_banana(stand)
'b2'
>>> stand.bananas
['b1']
This analogy is a little silly, but any class that has mutable attributes is not protected from accidental vandalism. In my actual case, I have a class with two array attributes that must remain the same length. With array, there's nothing stopping a user from splicing a second array into the first and silently breaking my equal size invariant:
>>> from array import array
>>> x = array('f', [1,2,3])
>>> x
array('f', [1.0, 2.0, 3.0])
>>> x[1:2] = array('f', [4,5,6])
>>> x
array('f', [1.0, 4.0, 5.0, 6.0, 3.0])
This same behavour occurs when the array is a property.
I can think of two ways of avoiding issue:
- Subclass array and override
__setitem__
. I am resistant to this because I would like to be able to use this array splicing behaviour internally. - Change the accessor to return a deepcopy of the array. The returned array is still mutable, but changes to it won't affect the parent object.
Is there an elegant way around this problem? I'm particularly interested in fancy ways of subclassing property.