Skip to content

Literal for sentinel values #689

Open

Description

@srittau

This came up python/typeshed#3521: Currently I can't think of a way to type sentinel values that are often constructed by allowing a certain instance of object as the argument. For the example above it would be useful to be able to do something like this:

class PSS(AsymmetricPadding):
    MAX_LENGTH: ClassVar[object]
    def __init__(self, mgf: MGF1, salt_length: Union[int, Literal[MAX_LENGTH]]) -> None: ...

Alternatively we could add a type like Singleton type to typing:

class PSS(AsymmetricPadding):
    MAX_LENGTH: Singleton
    def __init__(self, mgf: MGF1, salt_length: Union[int, MAX_LENGTH]) -> None: ...

Activity

  1. gvanrossum commented on Dec 4, 2019

    @gvanrossum
    Member

    It would be nice if this could be written using Final and Literal:

    # Does not work (yet)
    A: Final = object()
    def f(a: Literal[A]): ...
    f(A)
    

    If you're willing to rewrite the code some more (beyond adding type annotations), you can usually solve this with an enum, since enums are allowed in Literal:

    # This does work
    
    class AA(Enum):
        A = 0
    
    A: Final = AA.A
    
    def f(a: Literal[AA.A]):
        ...
    
    f(A)
    

    (Alas, I haven't found a way to alias A = AA.A and be able to write Literal[A].)

  2. JukkaL commented on Dec 4, 2019

    @JukkaL
    Contributor

    You can use AA as the type instead of Listeral[AA.A]:

    ...
    
    A: Final = AA.A
    
    def f(a: AA):
        ...
    
    f(A)

    This mypy issue has related discussion: python/mypy#7642

  3. ilevkivskyi commented on Dec 4, 2019

    @ilevkivskyi
    Member

    Yeah it would be good to have some special-casing for ad-hoc sentinels. One of the main downsides I see for enum solution is that error messages if making a stub for existing library (as in original message) can be cryptic.

  4. JukkaL commented on Dec 4, 2019

    @JukkaL
    Contributor

    What about having a naming convention for sentinel enums in stubs? Maybe something like this:

    class _Sentinel(Enum):
        _SENTINEL = 0

    This way even if the type leaks out of the stub, at least there is a hint about what it means, and the underscore prefix suggests that this is something internal to the module/stub.

  5. ilevkivskyi commented on Dec 4, 2019

    @ilevkivskyi
    Member

    What about having a naming convention for sentinel enums in stubs?

    Yes, I was also thinking about this as a "quick fix".

  6. srittau commented on Dec 4, 2019

    @srittau
    CollaboratorAuthor

    Good idea for a quick fix.

  7. asvetlov commented on Nov 24, 2020

    @asvetlov

    Speaking about adding embedded annotations (not type stubs) I can say that I like @gvanrossum proposal (a combination of Final and Literal) very much.
    It naturally reflects what is going on in the Python runtime.

  8. erictraut commented on Nov 24, 2020

    @erictraut
    Collaborator

    Here's another possible solution using NewType.

    _MaxLengthSentinel = NewType("_MaxLengthSentinel", object)
    
    class PSS(AsymmetricPadding):
        MAX_LENGTH: _MaxLengthSentinel
    
        def __init__(self, mgf: MGF1, salt_length: Union[int, _MaxLengthSentinel]) -> None:
            ...

    By naming the sentinel with an underscore, it will be considered private, so the only way to get to its instance is through the MAX_LENGTH class variable.

    This solution appears to work fine with existing standards and type checker implementations.

  9. asvetlov commented on Nov 25, 2020

    @asvetlov

    The recipe with NewType has flaws:

    def f(arg: Union[str, SENTINEL_TYPE) -> None:
        if arg is SENTINEL:
          arg = "default"
        reveal_type(arg)  # the type is still union
    

    is doesn't remove SENTINEL type as arg is None does in mypy.

  10. erictraut commented on Nov 25, 2020

    @erictraut
    Collaborator

    Yeah, the implementation would need to include an additional check, something like this:

        def f(arg: Union[str, SENTINEL_TYPE]) -> None:
            if arg is SENTINEL:
                arg = "default"
            else:
                assert isinstance(arg, str)
            reveal_type(arg)
  11. added
    topic: featureDiscussions about new features for Python's type annotations
    on Nov 4, 2021
  12. srittau commented on Nov 4, 2021

    @srittau
    CollaboratorAuthor

    Related: PEP 661 -- Sentinel Values

  13. CaselIT commented on Sep 14, 2022

    @CaselIT

    Related: PEP 661 -- Sentinel Values

    If I read this pep correctly it would not cover the classic usage of sentinel flags, that are checked with is
    Implementing the example given using the classic is check

    def foo(value: int | Sentinel = MISSING) -> int:
      if value is MISSING:
        return 0
      else:
        return value + 1

    would fail type checkers without special support Sentinels them since value would still have type int | Sentinel

  14. layday commented on Sep 14, 2022

    @layday

    In the Discourse thread the author said that they've "decided to forego type signatures specific to each sentinel". Could you please comment there if you believe is comparisons are vital?

  15. CaselIT commented on Sep 14, 2022

    @CaselIT

    Thanks for pointing to the discussion

    As per https://discuss.python.org/t/pep-661-sentinel-values/9126/44 the proposed solution seem to implement Literal[MISSING]. So the Pep text seems outdated. That's unless I haven't missed anything in the discussion

    The above function would be typed as def foo(value: int | Literal[MISSING]= MISSING) -> int: making is work with type checkers

  16. layday commented on Sep 14, 2022

    @layday
  17. CaselIT commented on Sep 14, 2022

    @CaselIT

    Understood. That's a shame, since it makes Sentinel use very cumbersome when using type checkers

  18. AlexWaygood commented on Sep 14, 2022

    @AlexWaygood
    Member

    Understood. That's a shame, since it makes Sentinel use very cumbersome when using type checkers

    I think it was probably the correct decision for the PEP, since the PEP wasn't primarily typing-focused. I don't think this rules out the possibility of adding special-casing to type checkers in the future to better handle PEP 661 sentinel values. That could always be proposed in a future PEP.

  19. CaselIT commented on Sep 14, 2022

    @CaselIT

    Sure, but at that point I don't understand what's the point of adding Sentinel.
    If we don't care about type checking support I think the classic flag = object() (or the flag = type('flag', (), {})() works fine enough

  20. AlexWaygood commented on Sep 14, 2022

    @AlexWaygood
    Member

    Sure, but at that point I don't understand what's the point of adding Signature. If we don't care about type checking support I think the classic flag = object() (or the flag = type('flag', (), {})() works fine enough

    (The discuss.python.org thread is the better place to discuss that, and I see you've already posted there :)

  21. pelme commented on Nov 17, 2022

    @pelme

    I needed to add type hints to a function with a default sentinel object idiom, i.e.

    RAISE_ERROR = object()
    def get_something(name, default=RAISE_ERROR):
        ...

    I solved it by using @overload:

    RAISE_ERROR = object()
    
    import typing
    
    @typing.overload
    def get_something(name: str) -> str: ...
    
    @typing.overload
    def get_something(name: str, default: str) -> str: ...
    
    def get_something(name, default=RAISE_ERROR):
        ...

    It doesn't type check the function body but this was fine in my case since the function itself was trivial and I was mostly interested in adding type hints to the signature!

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

    topic: featureDiscussions about new features for Python's type annotations

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions