2

A class I cannot change the code of has a method from_lowercase that returns an instance of itself. Now I would like to inherit a class. However, when calling the inherited class over from_lowercase an instance of the original class is returned.

class Animal:
    """I CANNOT change the code in this class"""
    def __init__(self, uppercase_name: str):
        self.uppercase_name = uppercase_name

    def say(self) -> str:
        return f"I am {self.uppercase_name}"

    @staticmethod
    def from_lowercase(lowercase_name: str) -> "Animal":
        return Animal(lowercase_name.upper())


class PoliteAnimal(Animal):
    def say(self) -> str:
        return f"Hello dear sir, {super().say()}!"
    # insert magic here

calling some examples:

animal = Animal("JOHN")
animal_from_lowercase = Animal.from_lowercase("john")
nice_animal = PoliteAnimal("MARIA")
nice_animal_from_lowercase = PoliteAnimal.from_lowercase("maria")

print(animal.say())
print(animal_from_lowercase.say())
print(nice_animal.say())
print(nice_animal_from_lowercase.say())

leads to the following:

I am JOHN
I am JOHN
Hello dear sir, I am MARIA!
I am MARIA

However, the desired output is the following:

I am JOHN
I am JOHN
Hello dear sir, I am MARIA!
Hello dear sir, I am MARIA!

Obviously nice_animal_from_lowercase is an instance of Animal and not PoliteAnimal. How can this be fixed?

eto3542
  • 71
  • 5

2 Answers2

2

You need to implement the same method on your subclass so it will be overridden

class PoliteAnimal(Animal):
    def say(self) -> str:
        return f"Hello dear sir, {super().say()}!"
    # insert magic here
    @staticmethod
    def from_lowercase(lowercase_name: str) -> "Animal":
        return PoliteAnimal(lowercase_name.upper())

Ouput:

I am JOHN
I am JOHN
Hello dear sir, I am MARIA!
Hello dear sir, I am MARIA!
RootOnChair
  • 137
  • 10
2

I would patch Animal to fix from_lowercase, which should be a class method, not a static method.

def from_lowercase(cls, lc: str):
    return cls(lc.upper())

Animal.from_lowercase = classmethod(from_lowercase)

This works even if you do it after PoliteAnimal is defined.

When you call PoliteAnimal.from_lowercase(...), the method lookup will result in a call to Animal.from_lowercase, but as a class method it will receive PoliteAnimal as its first argument, leading to the correct class being instantiated.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • @ToM: FYI this technique is called [monkey patching](https://en.wikipedia.org/wiki/Monkey_patch) — also see question [What is a monkey patch?](https://stackoverflow.com/questions/5626193/what-is-a-monkey-patch) – martineau Jul 01 '21 at 17:13
  • The Problem is that in reality `from_lowercase` is a much more complex function of an external library. So I cannot simply rewrite it. – eto3542 Jul 02 '21 at 08:07
  • Can you write a wrapper that will create a subclass instance given an instance of `Animal`? – chepner Jul 02 '21 at 11:02
  • I don't think so. What exactly are you proposing? – eto3542 Jul 03 '21 at 14:11
  • Something like `PoliteAnimal.from_animal(Animal.from_lowercase("john"))`. `Animal.from_lowercase` seems like its the problem, but without more details about your *actual* class, it's difficult to provide a good solution. – chepner Jul 03 '21 at 14:21