From 0aea99e44416f37c75e5540072156dbf90ef1659 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 24 Jul 2021 12:35:33 -0700 Subject: bpo-44676: Serialize the union type using only public API (GH-27323) (GH-27340) Remove also the _from_args() constructor. (cherry picked from commit 435a0334d341e5f8faed594d9f015746bb7845db) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> --- Lib/copyreg.py | 6 ++++++ Lib/test/test_types.py | 45 +++++++++++++++------------------------------ Lib/typing.py | 4 ++-- Objects/unionobject.c | 48 ------------------------------------------------ 4 files changed, 23 insertions(+), 80 deletions(-) diff --git a/Lib/copyreg.py b/Lib/copyreg.py index 7ab8c12..356db6f 100644 --- a/Lib/copyreg.py +++ b/Lib/copyreg.py @@ -36,6 +36,12 @@ else: pickle(complex, pickle_complex, complex) +def pickle_union(obj): + import functools, operator + return functools.reduce, (operator.or_, obj.__args__) + +pickle(type(int | str), pickle_union) + # Support for pickling new-style objects def _reconstructor(cls, base, state): diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 3f491ee..b1218ab 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -3,6 +3,7 @@ from test.support import run_with_locale, cpython_only import collections.abc from collections import namedtuple +import copy import gc import inspect import pickle @@ -807,36 +808,20 @@ class UnionTests(unittest.TestCase): eq(x[S], int | S | bytes) def test_union_pickle(self): - alias = list[T] | int - s = pickle.dumps(alias) - loaded = pickle.loads(s) - self.assertEqual(alias, loaded) - self.assertEqual(alias.__args__, loaded.__args__) - self.assertEqual(alias.__parameters__, loaded.__parameters__) - - def test_union_from_args(self): - with self.assertRaisesRegex( - TypeError, - r"^Each union argument must be a type, got 1$", - ): - types.Union._from_args((1,)) - - with self.assertRaisesRegex( - TypeError, - r"Union._from_args\(\) argument 'args' must be tuple, not int$", - ): - types.Union._from_args(1) - - with self.assertRaisesRegex(ValueError, r"args must be not empty"): - types.Union._from_args(()) - - alias = types.Union._from_args((int, list[T], None)) - - self.assertEqual(alias.__args__, (int, list[T], type(None))) - self.assertEqual(alias.__parameters__, (T,)) - - result = types.Union._from_args((int,)) - self.assertIs(int, result) + orig = list[T] | int + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + s = pickle.dumps(orig, proto) + loaded = pickle.loads(s) + self.assertEqual(loaded, orig) + self.assertEqual(loaded.__args__, orig.__args__) + self.assertEqual(loaded.__parameters__, orig.__parameters__) + + def test_union_copy(self): + orig = list[T] | int + for copied in (copy.copy(orig), copy.deepcopy(orig)): + self.assertEqual(copied, orig) + self.assertEqual(copied.__args__, orig.__args__) + self.assertEqual(copied.__parameters__, orig.__parameters__) def test_union_parameter_substitution_errors(self): T = typing.TypeVar("T") diff --git a/Lib/typing.py b/Lib/typing.py index 0570b4e..ae31589 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -321,7 +321,7 @@ def _eval_type(t, globalns, localns, recursive_guard=frozenset()): if isinstance(t, GenericAlias): return GenericAlias(t.__origin__, ev_args) if isinstance(t, types.Union): - return types.Union._from_args(ev_args) + return functools.reduce(operator.or_, ev_args) else: return t.copy_with(ev_args) return t @@ -1806,7 +1806,7 @@ def _strip_annotations(t): stripped_args = tuple(_strip_annotations(a) for a in t.__args__) if stripped_args == t.__args__: return t - return types.Union._from_args(stripped_args) + return functools.reduce(operator.or_, stripped_args) return t diff --git a/Objects/unionobject.c b/Objects/unionobject.c index 9804d87..475311e 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -235,21 +235,6 @@ is_unionable(PyObject *obj) _PyUnion_Check(obj)); } -static int -is_args_unionable(PyObject *args) -{ - Py_ssize_t nargs = PyTuple_GET_SIZE(args); - for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) { - PyObject *arg = PyTuple_GET_ITEM(args, iarg); - if (!is_unionable(arg)) { - PyErr_Format(PyExc_TypeError, - "Each union argument must be a type, got %.100R", arg); - return 0; - } - } - return 1; -} - PyObject * _Py_union_type_or(PyObject* self, PyObject* other) { @@ -362,47 +347,14 @@ error: return NULL; } -static PyObject * -union_reduce(PyObject *self, PyObject *Py_UNUSED(ignored)) -{ - unionobject *alias = (unionobject *)self; - PyObject* from_args = PyObject_GetAttrString(self, "_from_args"); - if (from_args == NULL) { - return NULL; - } - - return Py_BuildValue("N(O)", from_args, alias->args); -} - static PyMemberDef union_members[] = { {"__args__", T_OBJECT, offsetof(unionobject, args), READONLY}, {0} }; -static PyObject * -union_from_args(PyObject *cls, PyObject *args) -{ - if (!PyTuple_CheckExact(args)) { - _PyArg_BadArgument("Union._from_args", "argument 'args'", "tuple", args); - return NULL; - } - if (!PyTuple_GET_SIZE(args)) { - PyErr_SetString(PyExc_ValueError, "args must be not empty"); - return NULL; - } - - if (!is_args_unionable(args)) { - return NULL; - } - - return make_union(args); -} - static PyMethodDef union_methods[] = { - {"_from_args", union_from_args, METH_O | METH_CLASS}, {"__instancecheck__", union_instancecheck, METH_O}, {"__subclasscheck__", union_subclasscheck, METH_O}, - {"__reduce__", union_reduce, METH_NOARGS}, {0}}; -- cgit v0.12