1

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.

Gribouille
  • 102
  • 6
  • Edit the question to show the code that doesn't work and explain exactly how it doesn't work. If there are errors show them as properly formatted text in the question. – Michael Butscher Jan 31 '23 at 13:39
  • If you don't want extra attributes, why are you adding `__dict__` to `__slots__` in the first place? What's the reason for letting the caller pick one arbitrary attribute to add? – chepner Jan 31 '23 at 13:51
  • Note the intentional asymmetry between `__getattr__` and `__setattr__`. `__getattr__` is only invoked when the normal attribute lookup fails; `__setattr__` is *always* invoked, not just when creating a new attribute. – chepner Jan 31 '23 at 13:52

2 Answers2

2

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
Chris
  • 2,461
  • 1
  • 23
  • 29
0

person.custom_attribute = value # I want to prevent this

To achieve this your class should do NOT have __dict__ attribute, that is __slots__ must not contain __dict__. Consider following simple example

class C1:
    __slots__ = ["__dict__"]
class C2:
    __slots__ = ["x","y"]
c1 = C1()
c1.custom = "hello"
print(c1.custom) # hello
c2 = C2()
c2.x = 10
c2.y = 30
print(c2.x,c2.y) # 10 30
c2.z = 100 # cause AttributeError: 'C2' object has no attribute 'z'
Daweo
  • 31,313
  • 3
  • 12
  • 25