pythongetattrslots

Can not make property and __getattr__ working together


I am working on a python class that has declared properties, and in which I want to add extra attributes at object instanciation (passed in the init method). I want them to be read and written. Finally, I don't want the user to be able to declare custom attributes; it should raise an Error.

class Person:

    __slots__ = ["_name", "__dict__"]

    def __init__(self, name, extra_arg):
        self.__dict__[extra_arg] = None
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    def __getattr__(self, item):
        if item in self.__dict__:
            return self.__dict__[item]
        raise AttributeError(item)

person = Person("gribouille", "hello")

person.custom_attribute = value # I want to prevent this

In this example, I can't manage to prevent new attributes to be declared. When I override setattr method, it seems to collide with my property and I can't manage to retrieve my "name" attribute.


Solution

  • How about checking for existing attributes via hasattr and __slots__?

    class Person:
    
        __slots__ = ["_name", "__dict__"]
    
        def __init__(self, name, extra_arg):
            self.__dict__[extra_arg] = None
            self._name = name
    
        @property
        def name(self):
            return self._name
    
        @name.setter
        def name(self, value):
            self._name = value
    
        def __getattr__(self, item):
            if item in self.__dict__:
                return self.__dict__[item]
            raise AttributeError(item)
            
        def __setattr__(self, attr_name, attr_value):
            if not (hasattr(self, attr_name) or attr_name in self.__slots__):
                raise AttributeError(attr_name)
            super().__setattr__(attr_name, attr_value)
    
    person = Person("gribouille", "hello")
    person.name = "test"
    
    person.custom_attribute = None # Now: AttributeError: custom_attribute