I want to refactor a big part of my code into a generic descriptor for read only attribute access. The following is an example of property based implementation
class A:
def __init__(self, n):
self._n = n
self._top = -1
@property
def n(self):
return self._n
@property
def top(self):
return self._top
def increase(self):
self._top += 1
you see I can initialize my A class and increase self._top but not to let user set a.top by
omitting property setter method
a = A(7)
a.top
Out[25]: -1
a.increase()
a.top
Out[27]: 0
if I do a.top = 4, it will give me an error
AttributeError: property 'top' of 'A' object has no setter
which is expected. Now, I want to refactor this logic into a descriptor
class ReadOnly:
def __init__(self):
self._name = None
def __set_name__(self, owner, name):
self._name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self._name]
def __set__(self, instance, value):
raise AttributeError("Can't set attribute")
def __delete__(self, instance):
raise AttributeError("Can't delete attribute")
class A:
n = ReadOnly()
top = ReadOnly()
def __init__(self, n):
self.n = n
self.top = -1
def increase(self):
self.top += 1
Well, this doesn't work. I can't even initialize the class A anymore, cause in __init__ it will set n and top immediately and prevent my from initialize.
How to write this logic from property into descriptor?
P.S.
Thank @chepner for this solution. This is what I'm looking for. I made it work. One last thing, if I have a attribute is a list say
class Stack:
S = ReadOnly()
n = ReadOnly()
top = ReadOnly()
def __init__(self, n):
self._S = [None] * n
self._n = n
self._top = -1 # python offset 1
Now I can't change self.top anymore
>>> s = Stack(4)
>>> s.S
[None, None, None, None]
Nor I can change s
>>> s.S = [1, 3] # not allowed anymore. Great!
But I can still change an element in the list
>>> s.S[3] = 3
[None, None, None, 3]
How can I prevent list element changes?
A, soAshould not interact with it directly. UsingReadOnlyinAhasAtacitly agree to "host" the attributeReadOnlyuses so that you can use the descriptor without instance ofAneeding to be hashable.s.S, it's not used when you operate on the list contents.