self when you want to assign/reference attributes of an individual instance of a class. So in your example having pi assigned to the class is perfectly valid since it should be identical for every instance. Assigning it to a named attribute of every instance is unnecessary.
Something like radius however would differ per instance and you could assign it to each instance with
self.radius = radius for example. That way new
Circle instances can each have their own
.radius whilst sharing the same
pi value which seems reasonable.
If you use
self.pi it’ll go through the standard lookup order. Since the instance does not have a
.pi attribute it’ll check the class attributes which in this case does have a
.pi attribute so you’re effectively using the class attribute anyway. Note that generally explicit is probably better than implicit and in this example it may be best be to use
Circle.pi (might not always be the case).
Definitely worth going through that discussion though.