Search code examples
pythonreflectionstatic

Python : cannot call static method on run-time retrieved type


I have a base class:

class MyBaseClass:
    @abstractmethod
    def from_list(list_float: List[float]) -> Self:
        pass

    @staticmethod
    @abstractmethod
    def fn() -> int: 
        pass

All children of that class implement from_list and static fn, since they are abstract.

Now, I have another class 'MyOptions' :

class MyOptions:
    @abstractclass
    def related_to_type(): type(MyBaseClass)

All MyOptions children implement related_to_type, which returns a type that is a child of MyBaseClass. What I want is:

Provided a MyOptions child (which is unknown until runtime), call fn on its related type, returned by related_to_type(). In other term, I want:

options: MyOptions = MyOptionChild()
type_opt = options.related_to_type() # this should return a MyBaseClass child type 
result = type_opt.fn() # error here 

The error is that type_opt contains an variable of type <class 'abc.ABCMeta'>, on which I cannot call my static function fn. PS : I cannot make MyOptions generic with a type argument because the related type is known at run-time only

What can I do to call a static function on a variable containing a "reflected class" in Python ? Typically, in java, you would do:

Method method = myBaseClassChild.getMethod("fn");
int result = method.invoke(null);

Solution

  • Your MyOptionChild needs to return a concrete type that implements fn; it's not clear from your pseudocode whether you did this in your actual code, but it should "just work".

    You can use Generic to type-annotate it in such a way that a function can take an arbitrary MyOptions and do reasonable things with its associated MyBaseClass, and a type-checker can validate that the appropriate interfaces are being used.

    Here's a working example:

    from abc import abstractmethod
    from typing import Generic, Type, TypeVar
    
    class MyBaseClass:
        @staticmethod
        @abstractmethod
        def fn() -> int:
            pass
    
    _MyBaseClass = TypeVar("_MyBaseClass", bound=MyBaseClass)
    
    class MyOptions(Generic[_MyBaseClass]):
        @abstractmethod
        def related_to_type(self) -> Type[_MyBaseClass]:
            pass
    
    def call_fn(options: MyOptions) -> int:
        return options.related_to_type().fn()
    
    # Everything before this line deals with abstract types.
    # Now the concrete implementations:
    
    class MyChildClass(MyBaseClass):
        @staticmethod
        def fn() -> int:
            return 42
    
    class MyOptionChild(MyOptions[MyChildClass]):
        def related_to_type(self) -> Type[MyChildClass]:
            return MyChildClass
    
    # Now call_fn with our concrete MyOptionChild:
    print(call_fn(MyOptionChild()))  # 42