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

sample use case: return type of @contextmanager decorated functions #15

Open
ippeiukai opened this issue Jul 31, 2023 · 3 comments
Open
Labels
sample use case here to make sure none of these get lost

Comments

@ippeiukai
Copy link

ippeiukai commented Jul 31, 2023

I want to contribute a real life use case that we encountered which may justify the needs of Intersection types.

Can anyone suggest an alternative without using Intersection?

from typing import *
from contextlib import AbstractContextManager, ContextDecorator, contextmanager

    
@contextmanager
def _hoge():
    ...
    try:
        yield
    finally:
        ...


def foo() -> ContextManager[Never] & ContextDecorator:  # How should we annotate this without Intersection?
    ...
    return _hoge()


# ====
# foo is used both as ContextManager and ContextDecorator

with foo():
    ...

@foo()
def bar():
    ...

NOTE: the spec of @contextmanager is here:
https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager

Essentially, what the decorated function returns is a ContextManager that uses ContextDecorator mixin.

@ippeiukai
Copy link
Author

The best I came up with was using @contextmanager within if TYPE_CHECKING block and trusting type checkers to infer the type of foo. This isn't the same as explicitly annotating foo's return type.

if TYPE_CHECKING:
    @contextmanager
    def foo() -> Iterator[Never]:
        ...
else:
    def foo():
        ...
        return _hoge()

@erictraut
Copy link
Collaborator

erictraut commented Jul 31, 2023

The correct return type for foo is _GeneratorContextManager (imported from contextlib).

from contextlib import _GeneratorContextManager

def foo() -> _GeneratorContextManager:
    ...
    return _hoge()

_GeneratorContextManager is the actual class used at runtime, and this class subclasses from both AbstractContextManager and ContextDecorator.

The fact that it's named with an underscore means that it's private, so you might be reluctant to use this type in a type annotation. I'm not sure why the authors of contextlib decided not to expose this type publicly. If you're using pyright, you don't need to supply a return type for foo because it infers the proper return type. Mypy doesn't do this currently.

image

@ippeiukai
Copy link
Author

ippeiukai commented Jul 31, 2023

The fact that it's named with an underscore means that it's private, so you might be reluctant to use this type in a type annotation.

Yap, exactly that. It is clearly not meant to be exported from contextlib.
https://github.com/python/cpython/blob/a24e25d74bb5d30775a61853d34e0bb5a7e12c64/Lib/contextlib.py#L10-L14
https://github.com/python/typeshed/blob/a079b0de6e703fcb615a1169a32bc2186efee9bd/stdlib/contextlib.pyi#L10-L23

I'm not sure why the authors of contextlib decided not to expose this type publicly.

I think not exposing such implementation detail is good. That way, we can later swap the implementation _hoge with our own AbstractContextManager subclass with ContextDecorator mixed in.

@mikeshardmind mikeshardmind added the sample use case here to make sure none of these get lost label Feb 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
sample use case here to make sure none of these get lost
Projects
None yet
Development

No branches or pull requests

3 participants