Skip to content

Allow covariants of __enter__, __aenter__, and @classmethod - #1336

Merged
lovelydinosaur merged 8 commits into
encode:masterfrom
Congee:master
Oct 2, 2020
Merged

Allow covariants of __enter__, __aenter__, and @classmethod#1336
lovelydinosaur merged 8 commits into
encode:masterfrom
Congee:master

Conversation

@Congee

Copy link
Copy Markdown
Contributor

The problem we currently have is the return type of classes such as
Client does not allow covariants when subclassing or context manager.
In other words:

classBase:
def__enter__(self) ->Base: # XXXreturnselfclassDerived(Base):
...
withDerived() asderived:
# The type of derived is Base but not Derived. It is WRONG
...

There are three approaches to improve type annotations.

  1. Just do not type-annotate and let the type checker infer
    return self.
  2. Use a generic type with a covariant bound
    _AsyncClient = TypeVar('_AsyncClient', bound=AsyncClient)
  3. Use a generic type T = TypeVar('T') or Self = TypeVar('Self')

They have pros and cons.

  1. It just works and is not friendly to developers as there is no type
    annotation at the first sight. A developer has to reveal its type via
    a type checker. Aslo, documentation tools that rely on type
    annotations lack the type. I haven't found any python docuementation
    tools that rely on type inference to infer return self. There are
    some tools simply check annotations.

  2. This approach is correct and has a nice covariant bound that adds
    type safety. It is also nice to documentation tools and somewhat
    friendly to developers. Type checkers, pyright that I use, always
    shows the the bounded type '_AsyncClient' rather than the subtype.
    Aslo, it requires more key strokes. Not good, not good.

    It is used by BaseException.with_traceback
    See https://github.com/python/typeshed/pull/4298/files

  3. This approach always type checks, and I believe it will be the
    official solution in the future. Fun fact, Rust has a Self type
    keyword. It is slightly unfriendly to documentation, but is simple to
    implement and easy to understand for developers. Most importantly,
    type checkers love it.

    See SelfType or another way to spell "type of self" (or, How to define a copy() function) python/mypy#1212

But, we can have 2 and 3 combined:

_Base=typing.TypeVar('_Base', bound=Base)
classBase:
def__enter__(self: _Base) ->_Base:
returnselfclassDerive(Base): ...
withDerived() asderived:
... # type of derived is Derived and it's a subtype of Base

The problem we currently have is the return type of classes such as
Client does not allow covariants when subclassing or context manager.
In other words:
```python
class Base:
def __enter__(self) -> Base: # XXX
return self
class Derived(Base):
...
with Derived() as derived:
# The type of derived is Base but not Derived. It is WRONG
...
```
There are three approaches to improve type annotations.
1. Just do not type-annotate and let the type checker infer
`return self`.
2. Use a generic type with a covariant bound
`_AsyncClient = TypeVar('_AsyncClient', bound=AsyncClient)`
3. Use a generic type `T = TypeVar('T')` or `Self = TypeVar('Self')`
They have pros and cons.
1. It just works and is not friendly to developers as there is no type
annotation at the first sight. A developer has to reveal its type via
a type checker. Aslo, documentation tools that rely on type
annotations lack the type. I haven't found any python docuementation
tools that rely on type inference to infer `return self`. There are
some tools simply check annotations.
2. This approach is correct and has a nice covariant bound that adds
type safety. It is also nice to documentation tools and _somewhat_
friendly to developers. Type checkers, pyright that I use, always
shows the the bounded type '_AsyncClient' rather than the subtype.
Aslo, it requires more key strokes. Not good, not good.
It is used by `BaseException.with_traceback`
See https://github.com/python/typeshed/pull/4298/files
3. This approach always type checks, and I believe it _will_ be the
official solution in the future. Fun fact, Rust has a Self type
keyword. It is slightly unfriendly to documentation, but is simple to
implement and easy to understand for developers. Most importantly,
type checkers love it.
See python/mypy#1212
But, we can have 2 and 3 combined:
```python
_Base = typing.TypeVar('_Base', bound=Base)
class Base:
def __enter__(self: _Base) -> _Base:
return self
class Derive(Base): ...
with Derived() as derived:
... # type of derived is Derived and it's a subtype of Base
```
@Congee

Copy link
Copy Markdown
ContributorAuthor

Hi, this PR is a follow up to #1333 . This PR introduces a typing problem for StreamContextManager, it returns a httpx.Response. I am not sure how to type it. How would you like it to be typed?

Comment threadhttpx/_client.py
Comment threadhttpx/_client.py
Comment threadhttpx/_client.py Outdated
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@Congee@lovelydinosaur@johtso@florimondmanca