[Python-checkins] bpo-44676: Serialize the union type using only public API (GH-27323) (GH-27340)

serhiy-storchaka webhook-mailer at python.org
Sat Jul 24 15:36:08 EDT 2021


https://github.com/python/cpython/commit/0aea99e44416f37c75e5540072156dbf90ef1659
commit: 0aea99e44416f37c75e5540072156dbf90ef1659
branch: 3.10
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: serhiy-storchaka <storchaka at gmail.com>
date: 2021-07-24T22:35:33+03:00
summary:

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 at gmail.com>

files:
M Lib/copyreg.py
M Lib/test/test_types.py
M Lib/typing.py
M Objects/unionobject.c

diff --git a/Lib/copyreg.py b/Lib/copyreg.py
index 7ab8c128eb044..356db6f083e39 100644
--- a/Lib/copyreg.py
+++ b/Lib/copyreg.py
@@ -36,6 +36,12 @@ def pickle_complex(c):
 
     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 3f491ee5108ed..b1218abc8af5e 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 @@ def eq(actual, expected, typed=True):
         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 0570b4e763396..ae31589dc4339 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 9804d87c66ccd..475311e598d7b 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 @@ union_repr(PyObject *self)
     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}};
 
 



More information about the Python-checkins mailing list