Skip to content

bpo-42195: Ensure consistency of Callable's __args__ in collections.abc and typing #23060

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

Merged
merged 31 commits into from
Dec 13, 2020

Conversation

Fidget-Spinner
Copy link
Member

@Fidget-Spinner Fidget-Spinner commented Oct 31, 2020

This patch fixes bpo-42195 via changing the GenericAlias builtin and subclassing for collections.abc.Callable's __class_getitem__. The changes are:

  1. Made GenericAlias subclassable.
  2. Subclassed it in collections.abc.Callable's __class_getitem__ to allow for consistentency with typing.Callable's __args__. This includes flattening the __args__.
>>> import collections.abc
>>> alias = collections.abc.Callable[[int, str], dict]

>>> alias
collections.abc.Callable[[int, str], dict]

>>> alias.__args__
(int, str, dict)
  1. Added tests for the above.
  2. Remove bad typing tests which subclassed from Callable[... T].
  3. Loosened type checks for Callable's argtypes in Callable[[argtypes], resulttype] to prepare for PEP 612.

https://bugs.python.org/issue42195

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it would be even nicer if the subclass was written in C and used by builtins.callable as well? So we can write callable[[int, int], str].

@Fidget-Spinner
Copy link
Member Author

I suppose it would be even nicer if the subclass was written in C and used by builtins.callable as well? So we can write callable[[int, int], str].

There is work on that by Shantanu at this bpo and PR #22848.

@Fidget-Spinner
Copy link
Member Author

For now, I'll wait till we've decided how to go about doing this on the bpo. It seems that there are a few sensible approaches here. Apologies if I don't follow-up to your review that quickly as a result.

@Fidget-Spinner
Copy link
Member Author

Fidget-Spinner commented Nov 19, 2020

@gvanrossum, I gave the issue more thought and I'm probably going ahead with the Python implementation since callable genericalias seems contentious. In the future someone else can just replace the Python version with the C version if it comes to fruition.

Supporting __args__ for a Callable[[int, str], dict] as ((int, str), dict) is surprisingly rather inconvenient work. A few typing methods have to become recursive to account for nested tuples of types (mainly the ones that check for TypeVars and replacement of TypeVars). The C counterparts to those methods have to become recursive too. Update: We can represent the internal args as (Tuple[int, str], dict) which avoids the need for recursion.

Although __args__ of ((int, str), dict) is the cleanest IMO, the effort to achieve it versus just making both __args__ (int, str, dict) feels disproportionately high. Maybe this is why the original code author for typing (I'd assume Ivan) made a conscious decision to flatten the args.

Edit: Sorry about the force push, I'm not a git sensei :(.

@Fidget-Spinner Fidget-Spinner marked this pull request as draft November 19, 2020 09:35
@Fidget-Spinner Fidget-Spinner marked this pull request as ready for review November 19, 2020 16:06
1.  allow equality between typing._PosArgs and collections.abc._PosArgs
2.  allow equality checks between genericalias subclasses
3.  add support to genericalias subclasses for union type expressions
@Fidget-Spinner
Copy link
Member Author

@gvanrossum tests seem to be passing as of 327e1a5 with the _PosArgs implementation. Needs docs and whatsnew update (I'll do another PR for that if this gets accepted).

Personally, the _PosArgs approach has grown on me quite a bit: it's a nice compromise. I managed to hack things together such that typing._PosArgs[x] == collections.abc._PosArgs[x] too. So typing.Callable[[int], str].__args__ == Callable[[int], str].__args__. (Note that typing.Callable[[int], str] != Callable[[int], str])

@Fidget-Spinner Fidget-Spinner changed the title bpo-42195: Make GenericAlias subclassable and subclass for collections.abc.Callable bpo-42195: Ensure consistency of Callable's __args__ in collections.abc and typing Nov 29, 2020
Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for leaving so many trivial code formatting comments. There are also a few important suggestions.

In general I am still not completely comfortable with this. In particular the change to how typing.Callable changes the form of its __args__ makes me worried about backwards compatibility (that code has been the same for a long time, unlike collections.abc.Callable, which is new in 3.9.0).

I also worry that 3.9.1rc1 is already out, so the earliest we could get this in would be 3.9.2. But if we ever want to fix this I think 3.9.2 is still better than 3.10 (let alone never).

Something makes me think that we're probably better off keeping typing.Callable unchanged and changing collections.abc.Callable so that its __args__ is a flat tuple. The trickery here is quite hard to follow. (Since I procrastinated on the review I feel this all the stronger. :-)

@gvanrossum
Copy link
Member

gvanrossum commented Nov 30, 2020

[me]

Something makes me think that we're probably better off keeping typing.Callable unchanged and changing collections.abc.Callable so that its __args__ is a flat tuple. The trickery here is quite hard to follow. (Since I procrastinated on the review I feel this all the stronger. :-)

However, I also forgot that Shantanu warned in bpo-42195 that PEP 612 allows Callable[P, R] where P is a ParamSpec typevar. This could make a flat tuple ambiguous. (We should continue this discussion at the bpo issue.)

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very close for the Python code. For the C code I am still not sure that this is the best refactoring. Basically we have some shared code (if not tuple, tuplify), then some non-shared code (allocate object), then some more shared code (set attributes), then some non-shared code (gc-track).

I think I may have led you astray by asking to share the code between the two. Or maybe twe could change the order to

  1. allocate
  2. tuplify
  3. set attrs
  4. gc-track

and then only put (2) and (3) in the shared helper function.

What do you think?


@classmethod
def __getitem_type(cls, origin, args):
def __new__(cls, origin, args):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, for 3.10 I think this is the right solution.

For 3.9, I think we need to issue a DeprecationWarning(using the warnings module) with the same message (for both TypeErrors below) and return what was returned in 3.9.0.

return None
class C1(typing.Container[T]):
def __contains__(self, item):
return False
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in the 3.9 backport this file should be untouched.

@Fidget-Spinner
Copy link
Member Author

I think I may have led you astray by asking to share the code between the two. Or maybe twe could change the order to

  1. allocate
  2. tuplify
  3. set attrs
  4. gc-track

and then only put (2) and (3) in the shared helper function.

What do you think?

Ok, I did just that. This is really helpful and clear feedback. Thanks!

@gvanrossum
Copy link
Member

Hi @Fidget-Spinner, this LGTM, but before I land it I have one question -- how will the PEP 612 implementation affect this? A particular thing to look into would be to see if we would run into trouble if people took this (or the original Callable implementations) and started using it with ParamSpec and Concatenate imported from typing_extensions? I fear that's not going to end well unless we remove most of the type checks. I wonder if we would have to simplify things so that Callable[X, Y] is valid for any X and Y, and falls into two cases:

  • If X is a list, then __args__ becomes `(*X, Y)
  • In all other cases, __args__ becomes (X, Y)

@Fidget-Spinner
Copy link
Member Author

I wonder if we would have to simplify things so that Callable[X, Y] is valid for any X and Y, and falls into two cases:

  • If X is a list, then __args__ becomes `(*X, Y)
  • In all other cases, __args__ becomes (X, Y)

Yeah definitely agree here. I saw that ParamSpec in pyre_extensions already uses the inheriting from list hack to work with Callable.

So do you want me to do that for both typing and collections.abc, or just collections.abc's Callable?

@gvanrossum
Copy link
Member

gvanrossum commented Dec 11, 2020 via email

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's ready except for the commented-out blocks of code.

Also, I'd like to ask @pablogsal to see if he has time to review your C code (he has often caught things I missed in C code!).

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually @pablogsal may be busy, let's see if @brandtbucher is interested.

@pablogsal
Copy link
Member

Also, I'd like to ask @pablogsal to see if he has time to review your C code (he has often caught things I missed in C code!).

Sorry for the delay. I can give it a go this night :)

Copy link
Member

@pablogsal pablogsal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reviewed the C code and it looks good to me! Good job @Fidget-Spinner !

@Fidget-Spinner
Copy link
Member Author

Fidget-Spinner commented Dec 13, 2020

I have reviewed the C code and it looks good to me! Good job @Fidget-Spinner !

Thank you for taking time out of your busy schedules to review this pablo and guido :).

@gvanrossum gvanrossum merged commit 463c7d3 into python:master Dec 13, 2020
@gvanrossum
Copy link
Member

Okay, that's 3.10 down. @Fidget-Spinner, can you work on the backport to 3.9? There's one comment in typing.py indicating we need to do something different there.

@Fidget-Spinner
Copy link
Member Author

Okay, that's 3.10 down. @Fidget-Spinner, can you work on the backport to 3.9? There's one comment in typing.py indicating we need to do something different there.

Sure, give me some time :). I'll make another PR to update docs too after the backport lands.

@Fidget-Spinner Fidget-Spinner deleted the abc-callable-ga branch December 14, 2020 14:49
gvanrossum pushed a commit that referenced this pull request Dec 14, 2020
adorilson pushed a commit to adorilson/cpython that referenced this pull request Mar 13, 2021
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

Successfully merging this pull request may close these issues.

5 participants