Reputation: 1663
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
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
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
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