FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-87390: Fix starred tuple equality and pickling by mrahtz · Pull Request #92249 · python/cpython · GitHub

/ cpython Public
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .c  (1) .py  (1) .rst  (1) All 3 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
27 changes: 24 additions & 3 deletions Lib/test/test_genericalias.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ def test_equality(self):
self.assertEqual(list[int], list[int])
self.assertEqual(dict[str, int], dict[str, int])
self.assertEqual((*tuple[int],)[0], (*tuple[int],)[0])
self.assertNotEqual(tuple[int], (*tuple[int],)[0])
self.assertNotEqual(tuple[int], Unpack[tuple[int]])
self.assertEqual(
tuple[
tuple( # Effectively the same as starring; TODO
Expand Down Expand Up @@ -394,6 +396,9 @@ def test_pickle(self):
self.assertEqual(loaded.__origin__, alias.__origin__)
self.assertEqual(loaded.__args__, alias.__args__)
self.assertEqual(loaded.__parameters__, alias.__parameters__)
if isinstance(alias, GenericAlias):
self.assertEqual(loaded.__unpacked__, alias.__unpacked__)


def test_copy(self):
class X(list):
Expand All @@ -418,12 +423,28 @@ def __deepcopy__(self, memo):
self.assertEqual(copied.__args__, alias.__args__)
self.assertEqual(copied.__parameters__, alias.__parameters__)

def test_non_bool_to_third_constructor_argument_raises_typeerror(self):
with self.assertRaises(TypeError):
GenericAlias(tuple, int, 0)
with self.assertRaises(TypeError):
GenericAlias(tuple, int, 'foo')
with self.assertRaises(TypeError):
GenericAlias(tuple, int, list)

def test_unpack(self):
alias = tuple[str, ...]
self.assertIs(alias.__unpacked__, False)
unpacked = (*alias,)[0]
alias1 = tuple[str, ...]
self.assertIs(alias1.__unpacked__, False)
unpacked = (*alias1,)[0]
self.assertIs(unpacked.__unpacked__, True)

# The (optional) third positional argument should control unpackedness.
alias2 = GenericAlias(tuple, int)
self.assertIs(alias2.__unpacked__, False)
alias3 = GenericAlias(tuple, int, False)
self.assertIs(alias3.__unpacked__, False)
alias4 = GenericAlias(tuple, int, True)
self.assertIs(alias4.__unpacked__, True)

def test_union(self):
a = typing.Union[list[int], list[str]]
self.assertEqual(a.__args__, (list[int], list[str]))
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Give ``types.GenericAlias`` constructor an optional third ``bool`` argument that controls whether it represents an unpacked type (e.g. ``*tuple[int, str]``).
38 changes: 32 additions & 6 deletions Objects/genericaliasobject.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,9 @@ ga_richcompare(PyObject *a, PyObject *b, int op)

gaobject *aa = (gaobject *)a;
gaobject *bb = (gaobject *)b;
if (aa->starred != bb->starred) {
Py_RETURN_FALSE;
}
int eq = PyObject_RichCompareBool(aa->origin, bb->origin, Py_EQ);
if (eq < 0) {
return NULL;
Expand Down Expand Up @@ -604,8 +607,13 @@ static PyObject *
ga_reduce(PyObject *self, PyObject *Py_UNUSED(ignored))
{
gaobject *alias = (gaobject *)self;
return Py_BuildValue("O(OO)", Py_TYPE(alias),
alias->origin, alias->args);
PyObject *starred = PyBool_FromLong(alias->starred);
PyObject *value = Py_BuildValue("O(OOO)", Py_TYPE(alias),
alias->origin, alias->args, starred);
// Avoid double increment of reference count on Py_True/Py_False - once from
// PyBool_FromLong, and once from Py_BuildValue.
Py_CLEAR(starred);
return value;
Comment on lines +610 to +616

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

How about:

Suggested change
PyObject *starred = PyBool_FromLong(alias->starred);
PyObject *value = Py_BuildValue("O(OOO)", Py_TYPE(alias),
alias->origin, alias->args, starred);
// Avoid double increment of reference count on Py_True/Py_False - once from
// PyBool_FromLong, and once from Py_BuildValue.
Py_CLEAR(starred);
return value;
PyObject *starred = alias->starred ? Py_True : Py_False;
return Py_BuildValue("O(OOO)", Py_TYPE(alias),
alias->origin, alias->args, starred);

BTW, if you don't want to use the code above, you can avoid the double incref by using the format string O(OON). N doesn't incref. https://docs.python.org/3/c-api/arg.html#c.Py_BuildValue

}

static PyObject *
Expand Down Expand Up @@ -685,7 +693,7 @@ static PyGetSetDef ga_properties[] = {
* Returns 1 on success, 0 on failure.
*/
static inline int
setup_ga(gaobject *alias, PyObject *origin, PyObject *args) {
setup_ga(gaobject *alias, PyObject *origin, PyObject *args, bool starred) {
if (!PyTuple_Check(args)) {
args = PyTuple_Pack(1, args);
if (args == NULL) {
Expand All @@ -700,6 +708,7 @@ setup_ga(gaobject *alias, PyObject *origin, PyObject *args) {
alias->origin = origin;
alias->args = args;
alias->parameters = NULL;
alias->starred = starred;
alias->weakreflist = NULL;

if (PyVectorcall_Function(origin) != NULL) {
Expand All @@ -718,16 +727,33 @@ ga_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
if (!_PyArg_NoKeywords("GenericAlias", kwds)) {
return NULL;
}
if (!_PyArg_CheckPositional("GenericAlias", PyTuple_GET_SIZE(args), 2, 2)) {
if (!_PyArg_CheckPositional("GenericAlias", PyTuple_GET_SIZE(args), 2, 3)) {

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Please update the creation signature in the docs https://docs.python.org/3/library/types.html#types.GenericAlias.

Also, this doesn't break backwards compatibility right? IIUC, the two-argument form will still work.

return NULL;
}

PyObject *origin = PyTuple_GET_ITEM(args, 0);
PyObject *arguments = PyTuple_GET_ITEM(args, 1);

Comment on lines 734 to +736

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This code has gotten complex enough that I recommend using https://docs.python.org/3/c-api/arg.html#c.PyArg_ParseTuple or https://docs.python.org/3/c-api/arg.html#c.PyArg_UnpackTuple instead to parse our arguments for us.

The format value list is on that page. But off the top of my head, it should be OO|O:GenericAlias. Then you keep your bool checking/error raising code below.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Argument clinic can also be used here.

bool starred;
if (PyTuple_GET_SIZE(args) < 3) {
starred = false;
} else {
PyObject *py_starred = PyTuple_GET_ITEM(args, 2);
if (!PyBool_Check(py_starred)) {
PyErr_SetString(PyExc_TypeError,
"Third argument to constructor of GenericAlias "
"must be a bool (since it's a flag controlling "
"whether the alias is unpacked)");
Comment on lines +745 to +746

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality
Suggested change
"must be a bool (since it's a flag controlling "
"whether the alias is unpacked)");
"must be a bool");

We don't need the explanation.

return NULL;
}
starred = PyLong_AsLong(py_starred);
Comment thread
mrahtz marked this conversation as resolved.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I'm surprised you don't need to cast to (bool) here (or maybe it's implicit, I don't remember how C treats this).

}

gaobject *self = (gaobject *)type->tp_alloc(type, 0);
if (self == NULL) {
return NULL;
}
if (!setup_ga(self, origin, arguments)) {
if (!setup_ga(self, origin, arguments, starred)) {
Py_DECREF(self);
return NULL;
}
Expand Down Expand Up @@ -837,7 +863,7 @@ Py_GenericAlias(PyObject *origin, PyObject *args)
if (alias == NULL) {
return NULL;
}
if (!setup_ga(alias, origin, args)) {
if (!setup_ga(alias, origin, args, false)) {
Py_DECREF(alias);
return NULL;
}
Expand Down

Back | FazBrowse Home | New Git URL