mypy icon indicating copy to clipboard operation
mypy copied to clipboard

super(cls, self) seemingly has no attribute it actually has

Open pjoziak opened this issue 1 year ago • 2 comments

Bug Report

I wanted to have a quick way of concatenating results of several mixin-like implementations of similar algorithms. Below is a MWE of what I wanted to implement

To Reproduce

MWE:

from abc import abstractmethod
from typing import List

class A:
    @abstractmethod
    def foo(self, arg: str) -> str:
        raise NotImplementedError
    
    def bar(self, arg: str) -> List[str]:
        ret: List[str] = []
        for c in self.__class__.mro():
            c_super = super(c, self)
            if hasattr(c_super, 'foo'):
                try:
                    ret.append(c_super.foo(arg))
                except NotImplementedError:
                    pass
        try:
            ret.append(self.foo(arg))
        except NotImplementedError:
            pass
        return ret

class B(A):
    def foo(self, arg: str) -> str:
        return f'B.foo[{arg}]'

    
class C(B):
    def foo(self, arg: str) -> str:
        return f'C.foo[{arg}]'

hello = 'hello'

c = C()
print(f'foo: {c.foo(hello)}')
print(f'bar: {c.bar(hello)}')

Expected Behavior

No warning

Actual Behavior

mypy recognizes

mwe.py:15: error: "super" has no attribute "foo"

even though it is executed after if hasattr(...)

What's even more strange, if you construct a similar pattern but with the method foo with no arguments, then the same example just passes mypy checks.

Your Environment

  • Mypy version used: 0.812, 0.900, 0.971
  • Python version used: 3.8
  • Operating system and version: Ubuntu 22.04

pjoziak avatar Aug 24 '22 15:08 pjoziak

Mypy has to implement quite a lot of special-casing in order to support super() at all. I think it's pretty unlikely that mypy is going to add the necessary complexity in order to support highly dynamic (and quite unusual) uses of super() like this, unfortunately.

AlexWaygood avatar Aug 24 '22 15:08 AlexWaygood

This

class A():
    def foo(self) -> None:
        pass

class A1000(A):
    def foo(self) -> None:
        parent = super()
        for _ in range(1000):
            parent.foo()

already fails.

n1ngu avatar Sep 23 '22 00:09 n1ngu