Skip to content

Adding @abstractmethod __init__ to ABC breaks staticmethods #1706

Closed

Description

@euresti

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.

Activity

  1. rwbarton commented on Jun 14, 2016

    @rwbarton
    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
  2. rwbarton commented on Jun 29, 2016

    @rwbarton
    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.

  3. rwbarton commented on Jun 29, 2016

    @rwbarton
    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.

  4. elazarg commented on Sep 24, 2017

    @elazarg
    Contributor

    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.

  5. gvanrossum commented on Sep 24, 2017

    @gvanrossum
    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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions