0

I have a class with a property (signature in the toy example below). Some validation must be done, therefore there is a setter (@signature.setter).

class Signature:
    def __init__(self, input_array):
        self.signature = input_array

    @property
    def signature(self):
        return self._signature

    @signature.setter
    def signature(self, input_array):
        arr = np.array(input_array)
        if arr.ndim > 1:
            raise ValueError("Only one-dimensional arrays are accepted!")
        arr = np.unique(arr)
        if any(arr <= 0):
            raise ValueError("Only positive numbers are accepted!")
        self._signature = arr
lst = [2,3,4]
s = Signature(lst)
s.signature
# array([2, 3, 4])

But then I can write:

s.signature = [4, 5, 6]

and everything will work just fine because there is a @signature.setter.

Now what I want is to protect s.signature from being set after this concrete object is created.

Should I use @property inside __new__? How can I do this?

(I'm aware of How to restrict setting an attribute outside of constructor? discussion, but there is no setter there...)

user7647857
  • 369
  • 2
  • 9
  • 1
    Rather than using a setter, why not remove the setter and only set _signature during the __init__? Other wise you could do a `if not self._signature` in the setter to only change _signature if it hasn't already been defined – scotty3785 Sep 25 '20 at 10:09
  • 2
    If you expect to set ``self.signature`` only via ``__init__``, why have a public setter in the first place? – MisterMiyagi Sep 25 '20 at 10:09
  • Well... This is so obvious. Now I can hardly understand why it was a problem for me. Sorry for bothering you, guys... – user7647857 Sep 25 '20 at 10:19

1 Answers1

1

You could make the setter raise an exception if one tries to set signature. Just use a different method to check the value passed to __init__ and set it:

import numpy as np
​
class Signature:
    def __init__(self, input_array):
        self._check_and_set_signature(input_array)
​
    @property
    def signature(self):
        return self._signature
​
    @signature.setter
    def signature(self, value):
        raise ValueError("signature already set")
        
        
        
    def _check_and_set_signature(self, input_array):
        arr = np.array(input_array)
        if arr.ndim > 1:
            raise ValueError("Only one-dimensional arrays are accepted!")
        arr = np.unique(arr)
        if any(arr <= 0):
            raise ValueError("Only positive numbers are accepted!")
        self._signature = arr

Sample run:

lst = [2,3,4]
s = Signature(lst)
print(s.signature)
# array([2, 3, 4])
​
s.signature = [4, 5, 6]
[2 3 4]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-68b91a667238> in <module>
     30 # array([2, 3, 4])
     31 
---> 32 s.signature = [4, 5, 6]

<ipython-input-6-68b91a667238> in signature(self, value)
     11     @signature.setter
     12     def signature(self, value):
---> 13         raise ValueError("signature already set")
     14 
     15 

ValueError: signature already set
Thierry Lathuille
  • 23,663
  • 10
  • 44
  • 50