Skip to content
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

Allow overriding of return type of __init__() #7477

Closed
achimnol opened this issue Sep 6, 2019 · 4 comments
Closed

Allow overriding of return type of __init__() #7477

achimnol opened this issue Sep 6, 2019 · 4 comments

Comments

@achimnol
Copy link
Contributor

achimnol commented Sep 6, 2019

I'm trying to avoid logical split of the object instantiation and the async initialization step in asynchronous contexts, by using an aobject base class as exemplified in this stackoverflow answer.
At runtime, my code based on aobject works as expected.

The problem is that I cannot remove The return type of "__init__" must be None mypy error during type checks.

I have added an explicit -> None annotation like:

class aobject(object):

    async def __new__(cls, *args, **kwargs) -> 'aobject':
        instance = super().__new__(cls)
        await instance.__init__(*args, **kwargs)
        return instance

    async def __init__(self) -> None:
        pass

Of course, reveal_type(aobject.__init__) says it is typing.Coroutine[Any, Any, None] because __init__() is now a coroutine function.

Would there be any way to override the particular return type error for __init__() methods of all subclasses of a specific class?
I understand that it is difficult to handle all edge cases in mypy since Python is a highly dynamic language which allows overrides of default class behaviors using metaclasses and __new__ method overrides. I just want to "manually enforce" specific non-default typing for specific set of classes and subclasses somehow.

@achimnol
Copy link
Contributor Author

achimnol commented Sep 6, 2019

With a slight modification, I tried:

class aobject(object):
    async def __new__(cls, *args, **kwargs) -> 'aobject':
        instance = super().__new__(cls)
        instance.__init__(*args, **kwargs)
        await instance.__ainit__(*args, **kwargs)
        return instance

    async def __ainit__(self) -> None:
        pass

class MyBase(aobject):
    async def __ainit__(self, x: int) -> None:
        await asyncio.sleep(0)
        self.x = x

class MyDerived(MyBase):
    async def __ainit__(self, x: int, y: int) -> None:  # error
        await super().__ainit__(x)
        await asyncio.sleep(0)
        self.y = y

but in this case the derived __ainit__ method generates Signature of "__ainit__" incompatible with supertype "MyBase" error. This does not happen with plain __init__ methods.

@achimnol
Copy link
Contributor Author

achimnol commented Sep 6, 2019

Also, would there be a way to let mypy check missing await for super().__ainit__(...) calls?

@achimnol
Copy link
Contributor Author

achimnol commented Sep 6, 2019

I ended up using the following version:

class aobject(object):

    async def __new__(cls, *args, **kwargs) -> 'aobject':
        instance = super().__new__(cls)
        instance.__init__(*args, **kwargs)
        await instance.__ainit__()
        return instance

    def __init__(self, *args, **kwargs) -> None:
        pass

    async def __ainit__(self) -> None:
        pass

where __ainit__() is always called with only self without extra arguments, so that synchronous initialization works like in the standard Python and async initialization works separately, while keeping both initializers are guaranteed to be executed when creating aobject instances using await.

This version keeps mypy silent and also satisfies my requirement. Closing.

@achimnol
Copy link
Contributor Author

achimnol commented Sep 8, 2019

A final version to pass type checks:

T_aobj = TypeVar('T_aobj', bound='aobject')

class aobject(object):

    async def __new__(cls: Type[T_aobj], *args, **kwargs) -> T_aobj:
        instance = super().__new__(cls)
        instance.__init__(*args, **kwargs)
        await instance.__ainit__()
        return instance

    @classmethod
    async def new(cls: Type[T_aobj], *args, **kwargs) -> T_aobj:
        instance = super().__new__(cls)
        instance.__init__(*args, **kwargs)
        await instance.__ainit__()
        return instance

    def __init__(self, *args, **kwargs) -> None:
        pass

    async def __ainit__(self) -> None:
        pass

await SomeAObjectVariant() works fine in runtime but does not pass the type check via mypy.
Use await SomeAObjectVariant.new() to avoid this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant