2

I'd like to forward the call of a method to another method, and have the second method's type hints show up in the first method. Probably easier to demonstrate.

When an instance of the class below is called, it looks up the method with the name "num_to_str", calls it, and returns the result. I'd like the type hints for "num_to_str" to show up when you call the object.

class MyClass:
    
    call_func_name = 'num_to_str'
    
    def num_to_str(self, num: int) -> str:
        return str(num)
    
    def __call__(self, *args, **kwds):
        return getattr(self, 'call_func_name')(*args, **kwds)

mc = MyClass()
mc(<tab>. # want type hints for num_to_str to show up

I'm using the method name and looking up via getattr rather than just setting __call__ = num_to_start so that if the num_to_str function is changed (for example, overridden in a subclass), it uses the correct/updated function.

Using Pylance/Pyright in VS Code for autocompletion/type hints.

joudan
  • 81
  • 5
  • The second argument should be `self.call_func_name`, not `'call_func_name'`. The class attribute itself is not callable. – chepner Jan 13 '23 at 15:46
  • 4
    You are looking to use static type hinting for something that is *not* static. The class attribute could change. – chepner Jan 13 '23 at 15:47
  • 2
    *...so that if the num_to_str function is changed (for example, overridden in a subclass), it uses the correct/updated function.* And what prevents people from overriding `__call__` instead? Then you wan't play with this at all. This may have a niche use case, but please clarify it - otherwise this question is an XY-problem – STerliakov Jan 13 '23 at 22:04
  • @chepner any class attribute can certainly be changed at runtime and can be redefined inside the class definition as well. The first case doesn't really apply (although I will note that in an interactive environment IPython does get the autocompletion right because it can inspect objects). The second case doesn't really affect autocompletion since it evaluates top to bottom. It's static at class definition which is all that really matters. – joudan Jan 14 '23 at 02:05
  • @SUTerliakov `obj()` gets evaluated as `type(obj).__call__()` so overriding at the object level (which I think is what you're talking about) doesn't really change anything. If you override in a subclass definition that's fine as well. Both the execution and associated type hints should come from the new definition. – joudan Jan 14 '23 at 02:10
  • I mean overriding in subclasses, because you mentioned it in your question as possible scenario. – STerliakov Jan 14 '23 at 02:33
  • You are assuming that your type checker will "call" `getattr` to see what method the value of `call_func_name` produces. It *might*, but there is only so much simulated execution a typechecker will do. (This is already probably too much.) – chepner Jan 14 '23 at 13:44
  • I agree with @SUTerliakov. While minimal examples are generally the way to go, in this case it is very unclear what you are actually trying to achieve. Even a silly fictional use case might help illustrate your intent here. So far, this really makes little sense. Why would you want the function to be called by `__call__` to be determined at runtime, but at the same time have it fixed in the class namespace in the form of its name in a class attribute? It seems like you are getting the _worst_ of both worlds with this approach. – Daniil Fajnberg Jan 14 '23 at 18:08
  • Thanks for the comments here. The bottom section of the [mypy](https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html#classes) docs "Classes" section has some helpful tips for `__getattr__` and `__setattr__` typing as well. – joudan Jan 24 '23 at 02:35

0 Answers0