Search code examples
pythonpython-3.xinheritancecastingtype-safety

What is a clean alternative to casting in order to call a hidden class method in python?


I have two projects with some shared structure, but different project specific requirements. To cope with that, I created two base classes:

class AbstractBaseClassA:
    def __init__(self) -> None:
        # do something
        pass


class AbstractBaseClassB:
    def __init__(self, a: AbstractBaseClassA) -> None:
        self.a = a
        # do something

I have a couple of abstract methods in there, do some work that is shared between the projects, but now want to go into project specifics. I thus create two further classes:

class ProjectSpecificClassA(AbstractBaseClassA):
    def __init__(self) -> None:
        super().__init__()
        # do something

    def someOtherMethod(self) -> None:
        # do something
        pass


class ProjectSpecificClassB(AbstractBaseClassB):
    def __init__(self, a: ProjectSpecificClassA) -> None:
        super().__init__(a)
        # do something
        self.a.someOtherMethod()  # <-- unresolved attribute reference

Now, in the last line of the ProjectSpecificClassB, I would like to call a project specific function of ProjectSpecificClassA, but due to the inheritance of AbstractBaseClassB, this method is not known. In a language like Java, this is the point where I would cast self.a to ProjectSpecificClassA, but since python does not support casting, I am looking for an alternative. I am aware, that technically, above solution works, but I'm wondering whether there is a cleaner solution that is also acknowledged by linters / precompilers?


Solution

  • Python is dynamic. That means that the someOtherMethod will be searched at run time, and will be found since a is actually a ProjectSpecificClassA object.

    Casting is required in Java, like it would be in C++, because the method is searched at compile time, hence the compiler has to know where to find it.

    Python is even more tolerant than that. Provided you use not special processing like __slots__ to prevent dynamic attribute creation, you can add a method to a specific object and use it. You could use your classes that way:

    a = ProjectSpecificClassA()
    b = ProjectSpecificClassB(a)
    b.a.special = lambda x: 2 * x
    ProjectSpecificClassB.special = lambda self: self.a.special(3)
    
    assert (b.special() == 6)