If you have been using Typehints
in Python, you are already aware of how we use Types in Python, although keep in mind that the Python runtime does not enforce function and variable type annotations. They can be used by third party tools such as type checkers, IDEs, linters, etc.
In the freshly released Python 3.11 (2022-10-24) also known as PEP-664, so many features have been introduced such as faster CPython, Support for parsing TOML & Self
Type and much more.
Let's focus on Self
Type for this blog. Self
Type is defined in PEP 673 – Self Type. But What is the use case of this Self
type? Let's understand by an example.
class Shape:
def set_scale(self, scale: float):
self.scale = scale
return self
Shape().set_scale(0.5) # => Return type should be Shape
As given in the above example, set_scale
will return the instance
itself, which will allow us to do method chaining. But how do we define the return type of set_scale
above?
One way to denote the return type is to specify it as the current class, say, Shape
. Using the method makes the type checker infer the type Shape
, as expected.
class Shape:
def set_scale(self, scale: float) -> Shape:
self.scale = scale
return self
Shape().set_scale(0.5) # => Return type is Shape
So we have got the solution, but is it really going to work if we create a subclass of Shape
and use the set_scale
method in that subclass? The answer is no, let's understand from the below code.
class Circle(Shape):
# Circle is a Subclass of Shape
def set_radius(self, r: float) -> Circle:
self.radius = r
return self
Circle().set_scale(0.5) # Return type is *Shape*, not Circle
Circle().set_scale(0.5).set_radius(2.7) # => Error: Shape has no attribute set_radius
As given in the above example, the IDE will show an error on the last line because Circle().set_scale(0.5)
return type is Shape
and IDE is not able to parse that we are calling set_scale
from Circle
not Shape
. Hence, the new Self
needs to be introduced. Let's understand how it works.
from typing import Self # New Self type
class Shape:
def set_scale(self, scale: float) -> Self:
self.scale = scale
return self
class Circle(Shape):
def set_radius(self, radius: float) -> Self:
self.radius = radius
return self
Shape().set_scale(0.5) # Return type is *Shape*
Circle().set_scale(0.5) # Return type is *Circle*
Circle().set_scale(0.5).set_radius(2.7) # Return type is *Circle*
Above, the return type Self
represents the fact that the function returns self
(which is an instance
of the class) and is easier to understand.
As in the above example, the type checker will correctly infer the type of Circle().set_scale(0.5)
to be Circle
, as expected.
This is it for today.
Any thoughts? Write it down in the comments.
For more such crispy blogs daily, follow Dev.Junction, subscribe to our newsletter and get notified.