Under normal circumstances, when we define a class and create an instance of that class, we can bind any attributes and methods to the instance, which embodies the flexibility of dynamic languages. First, define the class:
class Student(object):
pass
Then, try binding an attribute to the instance:
>>> s = Student()
>>> s.name = 'Michael' # Dynamically bind an attribute to the instance
>>> print(s.name)
Michael
We can also try binding a method to the instance:
>>> def set_age(self, age): # Define a function as an instance method
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # Bind a method to the instance
>>> s.set_age(25) # Call the instance method
>>> s.age # Test the result
25
However, a method bound to one instance will not take effect on another instance:
>>> s2 = Student() # Create a new instance
>>> s2.set_age(25) # Try to call the method
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'set_age'
To bind a method to all instances, we can bind it to the class instead:
>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score
After binding the method to the class, all instances can call it:
>>> s.set_score(100)
>>> s.score
100
>>> s2.set_score(99)
>>> s2.score
99
Normally, the set_score method above can be directly defined inside the class. However, dynamic binding allows us to dynamically add functionality to a class during program runtime, which is difficult to achieve in static languages.
But what if we want to restrict the attributes of an instance? For example, we only allow adding name and age attributes to Student instances.
To achieve this restriction, Python allows us to define a special __slots__ variable when defining a class to limit the attributes that can be added to instances of that class:
class Student(object):
__slots__ = ('name', 'age') # Use a tuple to define the names of allowed attributes
Then, let’s test it:
>>> s = Student() # Create a new instance
>>> s.name = 'Michael' # Bind attribute 'name'
>>> s.age = 25 # Bind attribute 'age'
>>> s.score = 99 # Bind attribute 'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
Since ‘score’ is not included in __slots__, we cannot bind the score attribute, and attempting to do so will result in an AttributeError.
Note when using __slots__: the attributes defined by __slots__ only take effect on instances of the current class, not on inherited subclasses:
>>> class GraduateStudent(Student):
... pass
...
>>> g = GraduateStudent()
>>> g.score = 9999
Unless __slots__ is also defined in the subclass, in which case the attributes allowed for subclass instances are the combination of the subclass’s own __slots__ and the parent class’s __slots__.
use_slots.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Student(object):
__slots__ = ("name", "age")
class GraduateStudent(Student):
pass
s = Student()
s.name = "Michael"
s.age = 25
# ERROR: AttributeError: 'Student' object has no attribute 'score'
try:
s.score = 99
except AttributeError as e:
print("AttributeError:", e)
g = GraduateStudent()
g.score = 99
print("g.score =", g.score)