This question is about the read-only problem for objects that are based on a super()
and if/how super can/should control __setattr__
on subclasses.
Context:
Is there a way to write a meta class or a descriptor such that all classes that are subclasses of a class containing the attribute self.read_only = True
cannot execute subclassed functions where getattr is starting with "set_", but where self.read_only = False
can?
I'm thinking that an override of object.__setattr__(self, name, value)
:
Called when an attribute assignment is attempted. This is called instead of the normal mechanism (i.e. store the value in the instance dictionary). name is the attribute name, value is the value to be assigned to it.
...is the right direction, but am in doubt whether my interpretation of the documentation is correct.
Example:
Super as intended by the system designer:
class BaseMessage(object):
def __init__(self, sender, receiver, read_only=True):
self.sender = sender
self.receiver = receiver
self.read_only = read_only
def __setattr__(self, name, value):
if self.read_only:
raise AttributeError("Can't set attributes as message is read only.")
else:
# ? set attribute...(suggestion welcome)
def get_sender(self): # Is a "get" and not "set" call, so it should be callable disregarding self.read_only's value.
return self.sender
def get_receiver(self):
return self.receiver
Sub made by the system-extender who has limited understanding of all consequences:
class MutableMessage(BaseMessage):
def __init__(self, sender, receiver, topic, read_only=False):
super().__init__(sender=sender, receiver=receiver, read_only=read_only)
self.topic = topic
# this call should be okay as super's property is read_only=False.
def set_topic_after_init(new_topic):
self.topic = topic
class ImmutableMessage(BaseMessage):
def __init__(self, sender, receiver, topic): # read_only=True !
super().__init__(sender=sender, receiver=receiver, read_only=read_only)
self.topic = topic
# this call should fail as super's property is read_only=True.
def set_topic_after_init(new_topic):
self.topic = topic
Commentary to example
In the MutableMessage
the system-extender explicitly declares that read_only is False and is knowingly aware that of the consequences of adding the function set_topic
.
In the ImmutableMessage
(below), the system-extender forgets to declare that message should be read_only=False
which should result in super
s __setattr__
to raise AttributeError
:
Core question: Will a usage as shown in the example below suffice to apply consistently to all classes who are based on the BaseMessage class?
Think of me as new to meta-programming. Therefore an explanation of any misunderstandings and/or extension and correction of my example would be supreme. I understand the hierarchy [1] but do not have insight to what python does behind the curtains during the inheritance process.
Thanks...
[1]: The hierarchy
The search order that Python uses for attributes goes like this:
__getattribute__
and__setattr__
- Data descriptors, like property
- Instance variables from the object's
__dict__
- Non-Data descriptors (like methods) and other class variables
__getattr__
Since
__setattr__
is first in line, if you have one you need to make it smart unless want it to handle all attribute setting for your class. It can be smart in either of two ways.a. Make it handle a specific set attributes only, or,
b. make it handle all but some set of attributes.
For the ones you don't want it to handle, call
super().__setattr__
.
Related questions:
This kinda works:
class BaseMessage(object):
def __init__(self, sender, receiver, read_only=True):
self._sender = sender
self._receiver = receiver
self.read_only = read_only
def __setattr__(self, name, value):
# Have to use gettattr - read_only may not be defined yet.
if getattr(self,"read_only",False):
raise AttributeError("Can't set attributes as message is read only.")
else:
self.__dict__[name] = value
def get_sender(self): # Is a "get" and not "set" call, so it should be callable
return self._sender # disregarding self.read_only's value.
def get_receiver(self):
return self._receiver
class ImmutableMessage(BaseMessage):
def __init__(self, sender, receiver, topic): # read_only=True !
# Have to make it read-write to start with
super().__init__(sender=sender, receiver=receiver, read_only=False)
# ... so we can set the topic
self.topic = topic
# Now make it read-only
self.read_only = True
# this call should fail as super's property is read_only=True.
def set_topic_after_init(self,new_topic):
self.topic = new_topic
__dict__
). __setattr__
.super.__init__
call the last call in the derived __init__
- but that doesn't feel very natural.__setattr__
could walk the stack and discover if it is being called from __init__
- but that feels very hairy.