Allow super to control __setattr__ on subclasses

Viewed 1566

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 supers __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:

  1. __getattribute__ and __setattr__
  2. Data descriptors, like property
  3. Instance variables from the object's __dict__
  4. Non-Data descriptors (like methods) and other class variables
  5. __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:

1 Answers
Related