Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2c4a297
Allow subclassing of GenericAlias, fix collections.abc.Callable's Gen…
Fidget-Spinner Oct 31, 2020
588d421
fix typing tests, add hash and eq methods
Fidget-Spinner Oct 31, 2020
050fa13
Fix pickling
Fidget-Spinner Oct 31, 2020
f60ea8a
whitespace
Fidget-Spinner Oct 31, 2020
2f3c6dc
Add test specifically for bpo
Fidget-Spinner Oct 31, 2020
2c4508e
update error message
Fidget-Spinner Oct 31, 2020
19d2973
Appease test_site
Fidget-Spinner Oct 31, 2020
3116c8e
Represent Callable __args__ via [tuple[args], result]
Fidget-Spinner Nov 19, 2020
f2b593a
add back tests for weakref, styling nits, add news
Fidget-Spinner Nov 19, 2020
93d51e4
remove redundant tuple checks leftover from old code
Fidget-Spinner Nov 19, 2020
327e1a5
Use _PosArgs instead of tuple
Fidget-Spinner Nov 28, 2020
e971ccb
Fix typo and news
Fidget-Spinner Nov 29, 2020
abd8b98
Refactor C code to use less duplication
Fidget-Spinner Nov 30, 2020
3ddca06
Address most of Guido's reviews (tests failing on purpose)
Fidget-Spinner Dec 1, 2020
1ab59c5
try to revert back to good old flat tuple __args__ days
Fidget-Spinner Dec 2, 2020
ee2d2e1
getting even closer
Fidget-Spinner Dec 2, 2020
2015738
finally done
Fidget-Spinner Dec 2, 2020
6704ffd
Update news
Fidget-Spinner Dec 4, 2020
598d29b
Address review partially
Fidget-Spinner Dec 5, 2020
c43ebcf
Address review fully, update news and tests, remove try-except block
Fidget-Spinner Dec 5, 2020
37ae3a9
Borrowed references don't need decref
Fidget-Spinner Dec 5, 2020
adbfcad
improve _PyArg_NoKwnames error handling, add union and subclass tests
Fidget-Spinner Dec 5, 2020
d1dd627
Don't change getargs, use _PyArg_NoKeywords instead
Fidget-Spinner Dec 5, 2020
2c21045
Merge remote-tracking branch 'upstream/master' into abc-callable-ga
Fidget-Spinner Dec 5, 2020
9f71667
remove stray whitespace
Fidget-Spinner Dec 5, 2020
a789620
refactor C code, add deprecation warning for 3.9
Fidget-Spinner Dec 6, 2020
1890b37
remove redundant check in C code, and try except in __new__
Fidget-Spinner Dec 6, 2020
4e928c6
remove check
Fidget-Spinner Dec 7, 2020
6b11d33
Loosen type checks for Callable args, cast to PyObject in genericalia…
Fidget-Spinner Dec 11, 2020
4215c3b
update news to mention about removing validation in argtypes
Fidget-Spinner Dec 11, 2020
585bf19
remove commented out code
Fidget-Spinner Dec 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 7 additions & 18 deletions Lib/_collections_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,32 +426,21 @@ class _CallableGenericAlias(GenericAlias):
__slots__ = ()

def __new__(cls, origin, args):

Copy link
Copy Markdown
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.

try:
return cls.__create_ga(origin, args)
except TypeError as exc:
if sys.version_info[:2] == (3, 9):
import warnings
warnings.warn(f'{str(exc)} '
f'(This will raise TypeError in Python 3.10)',
DeprecationWarning)
return GenericAlias(origin, args)
else:
raise exc
return cls.__create_ga(origin, args)

@classmethod
def __create_ga(cls, origin, args):
if not isinstance(args, tuple) or len(args) != 2:
raise TypeError(
"Callable must be used as Callable[[arg, ...], result].")
t_args, t_result = args
if not isinstance(t_args, (list, EllipsisType)):
raise TypeError(
f"Callable[args, result]: args must be a list or Ellipsis. "
f"Got {_type_repr(t_args)} instead.")
if t_args is Ellipsis:
ga_args = args
else:
if isinstance(t_args, list):
ga_args = tuple(t_args) + (t_result,)
# This relaxes what t_args can be on purpose to allow things like
# PEP 612 ParamSpec. Responsibility for whether a user is using
# Callable[...] properly is deferred to static type checkers.
else:
ga_args = args
return super().__new__(cls, origin, ga_args)

def __repr__(self):
Expand Down
19 changes: 11 additions & 8 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,14 +446,17 @@ def test_cannot_instantiate(self):
type(c)()

def test_callable_wrong_forms(self):
with self.assertRaises(TypeError):
Callable[[...], int]
with self.assertRaises(TypeError):
Callable[(), int]
with self.assertRaises(TypeError):
Callable[[()], int]
with self.assertRaises(TypeError):
Callable[[int, 1], 2]
## These no longer raise TypeError since Callable no longer type checks
## args. That responsiblity is deferred to static type checkers
## to simplify implementation of PEP 612 ParamSpec.
# with self.assertRaises(TypeError):
# Callable[[...], int]
# with self.assertRaises(TypeError):
# Callable[(), int]
# with self.assertRaises(TypeError):
# Callable[[()], int]
# with self.assertRaises(TypeError):
# Callable[[int, 1], 2]
Comment thread
Fidget-Spinner marked this conversation as resolved.
Resolved
with self.assertRaises(TypeError):
Callable[int]

Expand Down
32 changes: 20 additions & 12 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@
# namespace, but excluded from __all__ because they might stomp on
# legitimate imports of those modules.


def _type_convert(arg):
"""For converting None to type(None), and strings to ForwardRef."""
if arg is None:
return type(None)
if isinstance(arg, str):
return ForwardRef(arg)
return arg


def _type_check(arg, msg, is_argument=True):
"""Check that the argument is a type, and return it (internal helper).

Expand All @@ -136,10 +146,7 @@ def _type_check(arg, msg, is_argument=True):
if is_argument:
invalid_generic_forms = invalid_generic_forms + (ClassVar, Final)

if arg is None:
return type(None)
if isinstance(arg, str):
return ForwardRef(arg)
arg = _type_convert(arg)
if (isinstance(arg, _GenericAlias) and
arg.__origin__ in invalid_generic_forms):
raise TypeError(f"{arg} is not valid as type argument")
Expand Down Expand Up @@ -900,13 +907,13 @@ def __getitem__(self, params):
raise TypeError("Callable must be used as "
"Callable[[arg, ...], result].")
args, result = params
if args is Ellipsis:
params = (Ellipsis, result)
else:
if not isinstance(args, list):
raise TypeError(f"Callable[args, result]: args must be a list."
f" Got {args}")
# This relaxes what args can be on purpose to allow things like
# PEP 612 ParamSpec. Responsibility for whether a user is using
# Callable[...] properly is deferred to static type checkers.
if isinstance(args, list):
params = (tuple(args), result)
else:
params = (args, result)
return self.__getitem_inner__(params)

@_tp_cache
Expand All @@ -916,8 +923,9 @@ def __getitem_inner__(self, params):
result = _type_check(result, msg)
if args is Ellipsis:
return self.copy_with((_TypingEllipsis, result))
msg = "Callable[[arg, ...], result]: each arg must be a type."
args = tuple(_type_check(arg, msg) for arg in args)
if not isinstance(args, tuple):
args = (args,)
args = tuple(_type_convert(arg) for arg in args)
params = args + (result,)
return self.copy_with(params)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ for :class:`collections.abc.Callable` are now flattened while
:class:`types.GenericAlias` can now be subclassed and
``collections.abc.Callable``'s ``__class_getitem__`` will now return a subclass
of ``types.GenericAlias``. Tests for typing were also updated to not subclass
things like ``Callable[..., T]`` as that is not a valid base class. Patch by Ken Jin.
things like ``Callable[..., T]`` as that is not a valid base class. Finally,
both ``Callable``s no longer validate their ``argtypes``, in
``Callable[[argtypes], resulttype]`` to prepare for :pep:`612`. Patch by Ken Jin.

8 changes: 2 additions & 6 deletions Objects/genericaliasobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -569,9 +569,6 @@ static PyGetSetDef ga_properties[] = {
*/
static inline int
setup_ga(gaobject *alias, PyObject *origin, PyObject *args) {
if (alias == NULL) {
return 0;
}
if (!PyTuple_Check(args)) {
args = PyTuple_Pack(1, args);
if (args == NULL) {
Expand All @@ -593,7 +590,6 @@ setup_ga(gaobject *alias, PyObject *origin, PyObject *args) {
static PyObject *
ga_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
assert(type != NULL && type->tp_alloc != NULL);
if (!_PyArg_NoKeywords("GenericAlias", kwds)) {
return NULL;
}
Expand All @@ -607,7 +603,7 @@ ga_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
return NULL;
}
if (!setup_ga(self, origin, arguments)) {
type->tp_free(self);
type->tp_free((PyObject *)self);
return NULL;
}
return (PyObject *)self;
Expand Down Expand Up @@ -655,7 +651,7 @@ Py_GenericAlias(PyObject *origin, PyObject *args)
return NULL;
}
if (!setup_ga(alias, origin, args)) {
PyObject_GC_Del(alias);
PyObject_GC_Del((PyObject *)alias);
return NULL;
}
_PyObject_GC_TRACK(alias);
Expand Down