GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — typing: remove callable() check from typing._type_check · Issue #90802 · python/cpython

typing: remove callable() check from typing._type_check #90802

New issue
Copy link
New issue
Copy link
Closed
Closed
typing: remove callable() check from typing._type_check#90802
Copy link
Labels
stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement

Description

GBeauregard
mannequin
opened on Feb 4, 2022
Issue body actions
BPO 46644
Nosy @serhiy-storchaka, @ilevkivskyi, @JelleZijlstra, @sobolevn, @uriyyo, @Gobot1234, @Fidget-Spinner, @AlexWaygood, @GBeauregard
PRs
  • bpo-46644: Remove callable() requirement from typing._type_check #31151
  • bpo-46644: No longer accept arbitrary callables as type arguments in generics #31159
  • bpo-46644: Fix test_typing test broken by GH-31151 due to a merge race #31833
  • bpo-44799: Fix InitVar resolving using get_type_hints #27553
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = <Date 2022-03-12.02:46:05.986>
    created_at = <Date 2022-02-04.23:02:57.948>
    labels = ['type-feature', 'library']
    title = 'typing: remove callable() check from typing._type_check'
    updated_at = <Date 2022-03-12.18:01:12.354>
    user = 'https://github.com/GBeauregard'

    bugs.python.org fields:

    activity = <Date 2022-03-12.18:01:12.354>
    actor = 'uriyyo'
    assignee = 'none'
    closed = True
    closed_date = <Date 2022-03-12.02:46:05.986>
    closer = 'JelleZijlstra'
    components = ['Library (Lib)']
    creation = <Date 2022-02-04.23:02:57.948>
    creator = 'GBeauregard'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 46644
    keywords = ['patch']
    message_count = 17.0
    messages = ['412548', '412550', '412584', '412585', '412596', '412597', '412599', '412623', '412651', '412657', '412658', '412660', '412662', '414961', '414962', '414966', '414967']
    nosy_count = 9.0
    nosy_names = ['serhiy.storchaka', 'levkivskyi', 'JelleZijlstra', 'sobolevn', 'uriyyo', 'Gobot1234', 'kj', 'AlexWaygood', 'GBeauregard']
    pr_nums = ['31151', '31159', '31833', '27553']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue46644'
    versions = []

    Activity

    1. GBeauregard commented on Feb 4, 2022

      @GBeauregard
      GBeauregardmannequin
      on Feb 4, 2022
      MannequinAuthor
      More actions

      I propose removing the callable() check[1] from typing._type_check. This restriction is usually met in typeform instances by implementing a __call__ method that raises at runtime[2]. _type_check is called at runtime in a few disparate locations, such as in an argument to typing.Annotated or for certain stringified annotations in typing.get_type_hints.

      Because the requirement to be callable is unexpected and shows up in situations not easily discoverable during development or common typing usage, it is the cause of several existing cpython bugs and will likely continue to be the cause of bugs in typeforms outside of cpython. Known cpython bugs caused by the callable() check are bpo-46643, bpo-44799, a substantial contributing factor to bpo-46642, and partly bpo-46511. I discovered bpo-46643 with only a cursory check of typing.py while writing this proposal. Moreover, it doesn't make any particular technical sense to me why it should be required to add an awkward __call__ method.

      Removing the callable() check fails 10 tests:
      7 tests: checking that an int literal is not a type
      2 tests: testing that list literals are not valid types (e.g. [3] raises a TypeError because the literal [('name', str), ('id', int)] doesn't pass callable())
      1 test: bpo-46642

      The responsibility of determining these invalid typeforms (e.g. int literals) would need to be passed to a static type checker. If it's desired to do this at runtime it's my opinion that a different check would be more appropriate.

      Have I missed any reasons for the callable() check? Can I remove the check and adjust or remove the tests?

      [1]

      cpython/Lib/typing.py

      Lines 183 to 184 in bf95ff9

      if not callable(arg):
      raise TypeError(f"{msg} Got {arg!r:.100}.")

      [2]

      cpython/Lib/typing.py

      Lines 392 to 393 in bf95ff9

      def __call__(self, *args, **kwds):
      raise TypeError(f"Cannot instantiate {self!r}")

      [3]

      cpython/Lib/test/test_typing.py

      Lines 4262 to 4263 in bf95ff9

      with self.assertRaises(TypeError):
      NamedTuple('Emp', fields=[('name', str), ('id', int)])

    2. GBeauregard
      added
      stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
      type-featureA feature request or enhancementA feature request or enhancement
      on Feb 4, 2022
    3. GBeauregard commented on Feb 4, 2022

      @GBeauregard
      GBeauregardmannequin
      on Feb 4, 2022
      MannequinAuthor
      More actions

      In addition to the 10 tests failed in test_typing.py, one additional test fails in test_types.py with this change:

      cpython/Lib/test/test_types.py

      Lines 834 to 838 in bf95ff9

      def test_union_parameter_substitution_errors(self):
      T = typing.TypeVar("T")
      x = int | T
      with self.assertRaises(TypeError):
      x[42]
      and those are all the tests in cpython changed.

      This falls in category (1), checking that an int literal is not a type, but with the apparent intent to prevent index subscripting.

    4. Gobot1234 commented on Feb 5, 2022

      @Gobot1234
      Gobot1234mannequin
      on Feb 5, 2022
      Mannequin
      More actions

      I also support this change. I've had to write a lot of code to make SpecialForms able to accept my types where the code has to look like:

      class Something:
          ...
          def __call__(self, *args, **kwargs):
             raise NotImplementedError

      I also know this comes up in typing-extensions a fair bit. I think type checkers should be enforcing this at type-checking-time not by typing.py run-time.

    5. JelleZijlstra commented on Feb 5, 2022

      @JelleZijlstra
      JelleZijlstra
      on Feb 5, 2022
      Member
      More actions

      I agree with removing this check. I suspect it's a holdover from very early typing when static types were supposed to be runtime types. Now the check is a bug magnet and doesn't serve a useful purpose.

      I think we can just remove the tests that check for ints. I don't see a principled reason to special-case int literals.

      I wonder if we should apply this change to 3.10 and 3.9. It's arguably a bugfix, but it's a pretty big change.

    6. GBeauregard commented on Feb 5, 2022

      @GBeauregard
      GBeauregardmannequin
      on Feb 5, 2022
      MannequinAuthor
      More actions

      Under the same failing int test cases before there were 2 more cases next to them that fail:

      with self.assertRaises(TypeError):
          ClassVar[int, str]
      with self.assertRaises(TypeError):
          Final[int, str]

      These fail because tuple literals are not callable(). There is code that clearly intends for this to be the case:

      cpython/Lib/typing.py

      Line 486 in 96b344c

      item = _type_check(parameters, f'{self} accepts only single type.')

      I can either remove support for this runtime check or change the implementation of Final et al to reject tuple literals. I will do the latter for now.

      For

      cpython/Lib/test/test_typing.py

      Lines 4262 to 4263 in bf95ff9

      with self.assertRaises(TypeError):
      NamedTuple('Emp', fields=[('name', str), ('id', int)])
      I think the best approach is to just remove these tests.

    7. GBeauregard commented on Feb 5, 2022

      @GBeauregard
      GBeauregardmannequin
      on Feb 5, 2022
      MannequinAuthor
      More actions

      Further questions: the msg argument in _type_check now wouldn't be used for anything! It was only used in the case where a type wasn't callable(). I think it should be removed. I'm also a bit negative on disallowing tuples in the case of e.g. Final and such since it complicates implementing tuple types in Python down the line if desired.

    8. GBeauregard commented on Feb 5, 2022

      @GBeauregard
      GBeauregardmannequin
      on Feb 5, 2022
      MannequinAuthor
      More actions

      I made a draft pull request where I went ahead and added a check to disallow tuple literals. This is basically already disallowed for types by restrictions on __getitem__ because Union[typeform]->type needs to be different from Union[type,type]->Union[type,type].

    9. serhiy-storchaka commented on Feb 6, 2022

      @serhiy-storchaka
      serhiy-storchaka
      on Feb 6, 2022
      Member
      More actions

      There were two reasons of accepting arbitrary callables in _type_check():

      1. NewType() returned a function.
      2. xml.etree.cElementTree.Element was a function in Python 2.

      Now NewType is a class, and Python 2 no longer supported. I agree that we should get rid of the callable() check, but I disagree with the proposed change. The check should be more explicit and strict, not more lenient. List[42] does not make sense and should not be accepted.

    10. GBeauregard commented on Feb 6, 2022

      @GBeauregard
      GBeauregardmannequin
      on Feb 6, 2022
      MannequinAuthor
      More actions

      List[42] is already accepted, and your proposed patch does not change it to make it not accepted. The issue is _type_check is only called in a few particular locations; this is part of the technical reason I'm not very concerned about relaxing the _type_check requirements.

      From a type checking philosophy point of view I agree with Jelle and am negative on strict runtime requirements in typing.py.

    11. serhiy-storchaka commented on Feb 6, 2022

      @serhiy-storchaka
      serhiy-storchaka
      on Feb 6, 2022
      Member
      More actions

      No, List[42] is not currently accepted.

      >>> List[42]
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "/home/serhiy/py/cpython/Lib/typing.py", line 318, in inner
          return func(*args, **kwds)
                 ^^^^^^^^^^^^^^^^^^^
        File "/home/serhiy/py/cpython/Lib/typing.py", line 1127, in __getitem__
          params = tuple(_type_check(p, msg) for p in params)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "/home/serhiy/py/cpython/Lib/typing.py", line 1127, in <genexpr>
          params = tuple(_type_check(p, msg) for p in params)
                         ^^^^^^^^^^^^^^^^^^^
        File "/home/serhiy/py/cpython/Lib/typing.py", line 184, in _type_check
          raise TypeError(f"{msg} Got {arg!r:.100}.")
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      TypeError: Parameters to generic types must be types. Got 42.
    12. GBeauregard commented on Feb 6, 2022

      @GBeauregard
      GBeauregardmannequin
      on Feb 6, 2022
      MannequinAuthor
      More actions

      I'm referring to within type annotations, where this code path isn't used: try a: List[42]

      This code path can show up in type aliases though.

    13. serhiy-storchaka commented on Feb 6, 2022

      @serhiy-storchaka
      serhiy-storchaka
      on Feb 6, 2022
      Member
      More actions

      But it gives the same result.

      What version of Python do you test with?

    14. GBeauregard commented on Feb 6, 2022

      @GBeauregard
      GBeauregardmannequin
      on Feb 6, 2022
      MannequinAuthor
      More actions

      I compiled your PR to run it and was testing in 3.10 as well, but I was testing in a file with from future import annotations unintentionally. I retract the comment. It turns out list[42] is okay though, which I suppose is more relevant going forward. My confusion here is sort of the crux of my problem with these runtime checks: they are inconsistently applied in different locations which is why callable() was causing a lot of bugs.

    15. JelleZijlstra commented on Mar 12, 2022

      @JelleZijlstra
      JelleZijlstra
      on Mar 12, 2022
      Member
      More actions

      New changeset 870b22b by Gregory Beauregard in branch 'main':
      bpo-46644: Remove callable() requirement from typing._type_check (GH-31151)
      870b22b

    16. JelleZijlstra commented on Mar 12, 2022

      @JelleZijlstra
      JelleZijlstra
      on Mar 12, 2022
      Member
      More actions

      Thanks for your contribution!

    17. JelleZijlstra
      closed this as completedon Mar 12, 2022
    18. JelleZijlstra
      closed this as completedon Mar 12, 2022
    19. JelleZijlstra commented on Mar 12, 2022

      @JelleZijlstra
      JelleZijlstra
      on Mar 12, 2022
      Member
      More actions

      Some tests are failing on main, probably due to a race. PR incoming.

    20. JelleZijlstra
      reopened this on Mar 12, 2022
    21. JelleZijlstra
      reopened this on Mar 12, 2022
    22. JelleZijlstra commented on Mar 12, 2022

      @JelleZijlstra
      JelleZijlstra
      on Mar 12, 2022
      Member
      More actions

      New changeset 7517437 by Jelle Zijlstra in branch 'main':
      bpo-46644: Fix test_typing test broken by #75334 due to a merge race (GH-31833)
      7517437

    23. JelleZijlstra
      closed this as completedon Mar 12, 2022
    24. JelleZijlstra
      closed this as completedon Mar 12, 2022
    25. ezio-melotti
      transferred this issue fromon Apr 10, 2022
    26. JukkaL
      added 2 commits that reference this issue on Jan 29, 2023

      Fix test_typeddict_errors test case on Python 3.11

      cd8b0c9

      Fix test_typeddict_errors test case on Python 3.11 (#28)

      1c3f2ed
    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

      stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        You are visitor 01402503 — GeezerVison does not store the pages you look up.
        Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.