Search code examples
pythonpython-3.xpython-typingmypy

Calling a helper function from inside a class variable


Consider the following code:

# foo.py

class A:
    def _foo():
        print('hello world')
    
    bar = {'foo': _foo}

    def run_bar(self):
        self.bar['foo']()

def main():
    A().run_bar()

if __name__ == '__main__':
    raise SystemExit(main())

It runs just fine with Python 3.9.5:

python3.9 foo.py
> hello world

but mypy will give me the following error:

mypy foo.py
> foo.py:2: error: Method must have at least one argument
> Found 1 error in 1 file (checked 1 source file)

Is there a way to tell mypy that this function will only ever get called as a class variable ? If no, is this bad practice? I know I could simply add # type: ignore, but that seems too hacky.


Solution

  • Two ways of doing this:

    1. Use a staticmethod

    Either like this:

    from typing import Callable, Any
    
    class A:
        bar: dict[str, Callable[..., Any]] = {}
    
        def __init__(self):
            self.bar.update({'foo': self._foo})
    
        @staticmethod
        def _foo():
            print('hello world')
    
        def run_bar(self):
            self.bar['foo']()
        
    
    def main():
        A().run_bar()
      
    
    if __name__ == '__main__':
        raise SystemExit(main())
    

    Or like this:

    class A:
        @staticmethod
        def _foo():
            print('hello world')
     
        def run_bar(self):
            getattr(self, '_foo')()
            
    
    def main():
        A().run_bar()
    
    
    if __name__ == '__main__':
        raise SystemExit(main())
    

    2. Put the function outside of the class

    def _foo():
        print('hello world')
    
    
    class A:
        bar = {'foo': _foo}
       
        def run_bar(self):
            self.bar['foo']()
        
    
    def main():
        A().run_bar()
      
    
    if __name__ == '__main__':
        raise SystemExit(main())