Skip to content

Adding @abstractmethod __init__ to ABC breaks staticmethods #1706

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
euresti opened this issue Jun 14, 2016 · 5 comments · Fixed by #6645
Closed

Adding @abstractmethod __init__ to ABC breaks staticmethods #1706

euresti opened this issue Jun 14, 2016 · 5 comments · Fixed by #6645
Labels
bug mypy got something wrong priority-2-low

Comments

@euresti
Copy link
Contributor

euresti commented Jun 14, 2016

I was trying to create an ABC that also worked as a factory.

from abc import (
    ABCMeta,
    abstractmethod,
)

class WidgetWithInit(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def __init__(self, foo, bar):
        # type: (int, int) -> None
        pass

    @staticmethod
    def add(foo, bar):
        # type: (int, int) -> int
        return foo + bar

    @classmethod
    def add_class(cls, foo, bar):
        # type: (int, int) -> int
        return foo + bar

WidgetWithInit.add(1, '2')
WidgetWithInit.add_class(1, '2')

class WidgetNoInit(object):
    __metaclass__ = ABCMeta

    @staticmethod
    def add(foo, bar):
        # type: (int, int) -> int
        return foo + bar

    @classmethod
    def add_class(cls, foo, bar):
        # type: (int, int) -> int
        return foo + bar

WidgetNoInit.add(1, '2')
WidgetNoInit.add_class(1, '2')

MyPy will respond:

rt6.py:40: error: Argument 2 to "add" of "WidgetNoInit" has incompatible type "str"; expected "int"
rt6.py:41: error: Argument 2 to "add_class" of "WidgetNoInit" has incompatible type "str"; expected "int"

So adding the def __init__ seems to make the staticmethod and classmethod have a type of Any.

Fortunately I can work around it by not defining init in the ABC, and having my def create enforce the signature.

@rwbarton
Copy link
Contributor

In fact mypy thinks the expression WidgetNoInit already has type Any with the definition

class WidgetWithInit(object):
    @abstractmethod
    def __init__(self, foo, bar):
        # type: (int, int) -> None
        pass

@rwbarton
Copy link
Contributor

In fact __init__ having any decorator will result in the class being treated as Any, since type_object_type starts like this:

    init_method = info.get_method('__init__')
    if not init_method:
        # Must be an invalid class definition.
        return AnyType()
    else: ...

and get_method doesn't return decorated things.

@rwbarton
Copy link
Contributor

Here's a possible test case

[case testAbstractInit]
from abc import abstractmethod, ABCMeta
class A(metaclass=ABCMeta):
    @abstractmethod
    def __init__(self, a: int) -> None:
        pass
class B(A):
    pass
class C(B):
    def __init__(self, a: int) -> None:
        self.c = a
a = A(1) # E: Cannot instantiate abstract class 'A' with abstract attribute '__init__'
A.c # E: "A" has no attribute "c"
b = B(2) # E: Cannot instantiate abstract class 'B' with abstract attribute '__init__'
B.c # E: "B" has no attribute "c"
c = C(3)
c.c
C.c

But I gave up on trying to fix this because the logic around recognizing decorators is too hairy.

@JukkaL JukkaL added the bug mypy got something wrong label Jun 30, 2016
@elazarg
Copy link
Contributor

elazarg commented Sep 24, 2017

I think this bug is deep and important. I've encountered something similar in #3227. The logic that handles decorators seems to be flawed in a way which is difficult to fix.

@gvanrossum
Copy link
Member

True, decorators are one of mypy's weak spots. We're slowly chipping away at it (e.g. #3918) but it's not easy because of special-casing in various places (both in Python and in mypy).

ilevkivskyi added a commit that referenced this issue Apr 27, 2019

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
Fixes #1706
Fixes #5398

Previously, decorated constructors either resulted in silent `Any` types for class objects (in case of a decorated `__init__()`) or incorrect signatures (in case of a decorated `__new__()`).

This PR adds support for decorated constructors, implementation is straightforward.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong priority-2-low
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants