You were on the right track, I think it’s more of a syntax error problem than something else, take this example:
class Foo:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __setattr__(self, key, value):
        if not hasattr(self, key):
            return
        super().__setattr__(key, value)
if __name__ == '__main__':
    f = Foo('a', 'b')
    f.c = 'c'
    print(f.c)
Basically it’s the same code you wrote, with the difference that the method __setattr__ is written as a method correctly.
When executing this code, a AttributeError::
Traceback (most recent call last):
  File "attr.py", line 15, in <module>
    print(f.c)
AttributeError: 'Foo' object has no attribute 'c'
That is, the attribute c was not created in the object, it was ignored.
However, there is a side effect when overriding the method __setattr__ with this behavior, no attribute is accepted by the class, that is, not even those declared in the method __init__ (a and b).
A possible solution would be to declare a list of permissible attributes and base on that list, rather than checking the attributes that the object has:
class Foo:
    attributes = ('a', 'b')
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __setattr__(self, key, value):
        if key not in self.attributes:
            return
        super().__setattr__(key, value)
							
							
						 
Before I used a condition variable and performed a function that required the
__setattr__to read if the variable was true and then locked the object to new attributes... using the slots really got less "feature-technical" rs– LeandroLuk
a problem I have identified in relation to
__slots__is that if I have "nested classes" defined within the class where they serve to complement a list, the element slots conflicts with the parent class– LeandroLuk