Static and Abstract
Many programming languages include a special keyword
static. In essence, a
static attribute or method is part of the class in which it is declared instead of part of objects instantiated from that class. If we think about it, the word static means “lacking in change”, and that’s sort of a good way to think about it.
In a UML diagram, static attributes and methods are denoted by underlining them.
In Python, any attributes declared outside of a method are class attributes, but they can be considered the same as static attributes until they are overwritten by an instance. Here’s an example:
class Stat: x = 5 # class or static attribute def __init__(self, an_y): self.y = an_y # instance attribute
In this class, we’ve created a class attribute named
x, and a normal attribute named
y. Here’s a
main() method that will help us explore how the static keyword operates:
from Stat import * class Main: def main(): some_stat = Stat(7) another_stat = Stat(8) print(some_stat.x) # 5 print(some_stat.y) # 7 print(another_stat.x) # 5 print(another_stat.y) # 8 Stat.x = 25 # change class attribute for all instances print(some_stat.x) # 25 print(some_stat.y) # 7 print(another_stat.x) # 25 print(another_stat.y) # 8 some_stat.x = 10 # overwrites class attribute in instance print(some_stat.x) # 10 (now an instance attribute) print(some_stat.y) # 7 print(another_stat.x) # 25 (still class attribute) print(another_stat.y) # 8 if __name__ == "__main__": Main.main()
First, we can see that the attribute
x is set to 5 as its default value, so both objects
another_stat contain that same value. Interestingly, since the attribute
x is static, we can access it directly from the class
Stat, without even having to instantiate an object. So, we can update the value in that way to 25, and it will take effect in any objects instantiated from
Below that, we can update the value of
x attached to
some_stat to 10, and we’ll see that it now creates an instance attribute for that object that contains 10, overwriting the previous class attribute. The value attached to
another_stat is unchanged.
Python also allows us to create static methods that work in a similar way:
class Stat: x = 5 # class or static attribute def __init__(self, an_y): self.y = an_y # instance attribute @staticmethod def sum(a): return Stat.x + a
We have now added a static method
sum() to our
Stat class. To create a static method, we place the
@staticmethod decorator above the method declaration. We haven’t learned about decorators yet, but they allow us to tell Python some important information about the code below the decorator.
In addition, it is important to remember that a static method cannot access any non-static attributes or methods, since it doesn’t have access to an instantiated object in the
As a tradeoff, we can call a static method without instantiating the class either, as in this example:
from Stat import * class Main: @staticmethod def main(): # other code omitted Stat.x = 25 moreStat = Stat(7) print(moreStat.sum(5)) # 30 print(Stat.sum(5)) # 30 if __name__ == "__main__": Main.main()
This becomes extremely useful in our
main() method. Since we aren’t instantiating our
Main class, we can use the decorator
@staticmethod above the method to clearly mark that it should be considered a static method.
Another major feature of class inheritance is the ability to define a method in a parent class, but not provide any code that implements that function. In effect, we are saying that all objects of that type must include that method, but it is up to the child classes to provide the code. These methods are called abstract methods, and the classes that contain them are abstract classes. Let’s look at how they work!
Abstract in Python
In the UML diagram above, we see that the
describe() method in the
Vehicle class is printed in italics. That means that the method should be abstract, without any code provided. To do this in Python, we simply inherit from a special class called
ABC, short for “Abstract Base Class,” and then use the
from abc import ABC, abstractmethod class Vehicle(ABC): def __init__(self, name): self.__name = name self._speed = 1.0 @property def name(self): return self.__name def move(self, distance): print("Moving"); return distance / self._speed; @abstractmethod def describe(self): pass
Notice that we must first import both the
ABC class and the
@abstractmethod decorator from a library helpfully called
ABC. Then, we can use
ABC as the parent class of our class, and update each method using the
@abstractmethod decorator before the method, similar to how we’ve already used
@staticmethod in an earlier module.
In addition, since we have declared the method
describe() to be abstract, we can either add some code to that method that can be called using
super().describe() from a child class, or we can simply choose to use the
pass keyword to avoid including any code in the method.
Now, any class that inherits from the
Vehicle class must provide an implementation for the
describe() method. If it does not, that class must also be declared to be abstract. So, for example, in the UML diagram above, we see that the
MotorVehicle class does not include an implementation for
describe(), so we’ll also have to make it abstract.
Of course, that means that we’ll have to inherit from both
ABC. In Python, we can do that by simply including both classes in parentheses after the subclass name, separated by a comma.