pythonoop

How to force the @property setter work only once?


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...)


Solution

  • 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