the_candyman
the_candyman

Reputation: 1663

Python - Force an error if an undeclared variable is used inside a class

Consider the following Python code:

class a_class:
    defined_var = 1

    def a_function(self):
        self.defined_var = 2
        self.undefined_var = 3 

The possibility to assign a value to a variable (and then implicitly declare it) that I did not declared at the beginning of the class (like I do for undefined_var in the previous example) is creating me several problems, since for huge classes I forget what I define and I what I don't.

I know that this question may sound silly. I've used to develop using C/C++/Java for a long time where the definition of variables in a class is mandatory...

Is there a way to avoid this? I mean, I would like a behavior like in C/C++/Java where I get an error if I use an undefined variable.

Upvotes: 3

Views: 823

Answers (3)

Charif DZ
Charif DZ

Reputation: 14751

Just add some Notes here:

The creator of Python, Guido van Rossum, states that he actually created slots for faster attribute access.

from book fluent python, __slots__ is used for faster attribute access and reduce memory usage not to prevent the user from adding more attributes.

check this code:

class MyClass:
    __slots__ = ['defined_var', '__dict__']

obj = MyClass()
obj.undefined_var = 3  # works fine

The problem in using __slots__,If you create a subclass of this class you need to repeat the __slot__ attribute in it too.

class MyClass:
    __slots__ = ['defined_var']

class MySubClass(MyClass):
    # you need to redefine the __slots__ here too
    pass

obj = MyClass()
sub_obj = MySubClass()
sub_obj.undefined_var = 3  # works fine not excepting this
obj.undefined_var = 3  # throw exception

So If you really need this go with overriding the setattr method as @Olvin Roght suggested.

class MyClass:
    x = None 
    def __setattr__(self, key, value):
        if hasattr(self, key):
            object.__setattr__(self, key, value)
        else:
            raise AttributeError(f"\"{key}\" is not defined in \"{self.__class__.__name__}\"")


class MySubClass(MyClass):
    pass


obj = MyClass()
sub_obj = MySubClass()
sub_obj.x = 3  # works fine
obj.x = 3  # works fine
sub_obj.undefined_var = 3  # error
obj.undefined_var = 3  # error  

If some day you want to do monkey patching on your object:

obj = MyClass()
obj.__dict__['z'] = 4
print(obj.z)  # print 4, we added a new attribute

So don't worry about preventing the client from adding new attribute it's a bad habit. and you will prevent your self from controlling your object and change its behavior, removing the flexibility that python give you, why would you want that.

Upvotes: 2

Olvin Roght
Olvin Roght

Reputation: 7812

You can override object.__setattr__:

class a_class:
    defined_var = 1

    def a_function(self):
        self.defined_var = 2
        self.undefined_var = 3

    def __setattr__(self, key, value):
        if hasattr(self, key):
            object.__setattr__(self, key, value)
        else:
            raise AttributeError(f"\"{key}\" is not defined in \"{self.__class__.__name__}\"")

a = a_class()
print(a.defined_var)
a.a_function()  # <-- throws an error

P.S. defined_var is class variable shared by all instances. It means that all changes will affect all instances of class (maybe it's not what you're expecting). More information here.

Upvotes: 4

Aran-Fey
Aran-Fey

Reputation: 43276

By default, python instances have a __dict__ - a dictionary that stores all of the object's attributes. This is what allows them to store arbitrary attributes. You can suppress the creation of the __dict__ by defining __slots__. This is a class-level variable listing the names of all attributes instances of the class can have. If a class has __slots__, trying to assign to an undefined attribute will throw an exception.

Example:

class MyClass:
    __slots__ = ['defined_var']

    def a_function(self):
        self.defined_var = 2

obj = MyClass()
obj.a_function()
obj.undefined_var = 3  # throws AttributeError

Upvotes: 6

Related Questions