X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions Lib/copyreg.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ def pickle_complex(c):

pickle(complex, pickle_complex, complex)

def pickle_union(obj):
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.

Off topic: I didn't know such a module existed 😮 . This is really cool! Thanks for teaching me something new today Serhiy.

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):
Expand Down
45 changes: 15 additions & 30 deletions Lib/test/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,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
Expand Down Expand Up @@ -1814,7 +1814,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

Expand Down
48 changes: 0 additions & 48 deletions Objects/unionobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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}};


Expand Down
X Tutup