Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Static methods and designing for inheritance

Tags:

I am unclear on what is the best way to design a class with static methods that are meant to be overridable. I will try to explain with an example.


We have a class Goat with a method can_climb. (By the way this is Python 3, in Python 2 I would write class Goat(object):.)

class Goat:
    def __init__(self, *args):
        ...

    def can_climb(self, mountain):
        return mountain.steepness < 10

billy = Goat("Billy")
if billy.can_climb(mount_everest):
    print("wow Billy, impressive")

This works as intended, but the method can_climb doesn't use self. It seems cleaner to make it a static method, and pylint even gives a warning for the above method. So let's change that:

class Goat:
    def __init__(self, *args):
        ...

    @staticmethod
    def can_climb(mountain):
        return mountain.steepness < 10

billy = Goat("Billy")
if billy.can_climb(mount_everest):
    print("wow Billy, impressive")

billy.can_climb near the end can instead be Goat.can_climb and it won't make a difference in this example. Some people might even see it as being clearer and more direct to call a static method via its class and not an instance.

However, this leads to a subtle error when we use inheritance and polymorphism is introduced:

class Goat:
    def __init__(self, *args):
        ...

    @staticmethod
    def can_climb(mountain):
        return mountain.steepness < 10

class MountaineeringGoat(Goat):
    @staticmethod
    def can_climb(mountain):
        return True

billy = get_ourselves_a_goat_called_billy()
if Goat.can_climb(mount_everest):     # bug
    print("wow Billy, impressive")

The code that calls Goat.can_climb knows that billy is an instance of Goat and makes the erroneous assumption that its type is Goat, when it could really be any subtype of Goat. Or maybe it incorrectly assumes that subclasses won't override a static method.

This seems to me like an easy mistake to make; perhaps Goat didn't have any subclasses at the time this bug was introduced, so the bug was not noticed.


How can we design and document a class so that this sort of bug is avoided? In particular, should can_climb from this example be a static method or should something else be used instead?

like image 350
flornquake Avatar asked Jun 06 '16 14:06

flornquake


People also ask

Can we use static method in inheritance?

Static methods take all the data from parameters and compute something from those parameters, with no reference to variables. We can inherit static methods in Java.

How do you call a static method in inherited class?

A static method is the one which you can call without instantiating the class. If you want to call a static method of the superclass, you can call it directly using the class name.

Are static methods members inherited to derived class?

In essence, static members are not inherited, they are just class-level (i.e. universal) methods that are accessible from anywhere.

Are static methods inherited Python?

Like static methods in Java, Python classmethods are inherited by subclasses. Unlike Java, it is possible for subclasses to override classmethods.


2 Answers

Inheritance clearly means a knowledge of the base class. @staticmethod is ignorant of the class it is 'attached' to(hence they earlier-not now-a-days-called it 'unbound;' now technically @staticmethod is not a method at all; it is a function.

But @classmethod is fully aware of the class it is attached to; it is technically not a function, but a method.

Why then @staticmethod at all? It is inherited in a derived class, but as said earlier, without the knowledge of base class; we can use it 'as such' as if we have defined it in the derived class.

Non-technically speaking @classmethods are 'bounded'; @staticmethods are NOT.

Had anybody asked me I would have suggested the name @staticfunction for @staticmethod.

Additionally, to ensure that the latest staticmethod is called, you may use type(billy).can_climb(mountain) or billy.__class__.can_climb(mountain) (although type(billy) is more pythonic it seems), which calls the staticmethod from billy's (sub)class. If it's not overridden, it would use the base-classe's staticmethod.

like image 173
Rathinavelu Muthaliar Avatar answered Oct 26 '22 15:10

Rathinavelu Muthaliar


Actually, at least with Python 3.8, I do see a difference between billy.can_climb and Goat.can_climb and as far as I can see the code does not make the erroneous assumption that the type of billy is Goat:

class Mountain:
        def __init__(self, steepness):
            self.steepness = steepness

class Goat:
    @staticmethod
    def can_climb(mountain):
        return mountain.steepness < 10

class MountaineeringGoat(Goat):
    @staticmethod
    def can_climb(mountain):
        return True

def get_ourselves_a_goat_called_billy():
    return MountaineeringGoat()

mount_everest = Mountain(100)

billy = get_ourselves_a_goat_called_billy()
if billy.can_climb(mount_everest):
    print("wow Billy, impressive")

In the above code, billy.can_climb(mount_everest) actually calls MountaineeringGoat.can_climb(mount_everest) and I do see this output:
wow Billy, impressive.

like image 34
Dimitri Avatar answered Oct 26 '22 17:10

Dimitri



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!