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

gh-109218: Deprecate weird cases in the complex() constructor by serhiy-storchaka · Pull Request #119620 · python/cpython · GitHub

/ cpython Public

gh-109218: Deprecate weird cases in the complex() constructor - #119620

Merged
serhiy-storchaka merged 13 commits into
python:mainfrom
serhiy-storchaka:complex-constructor
May 30, 2024
Merged

gh-109218: Deprecate weird cases in the complex() constructor#119620
serhiy-storchaka merged 13 commits into
python:mainfrom
serhiy-storchaka:complex-constructor

Conversation

serhiy-storchaka commented May 27, 2024
edited by github-actions Bot
Loading

Copy link
Copy Markdown
Member
  • Passing a string as the "real" keyword argument is now an error; it should only be passed as a single positional argument.
  • Passing a complex number as the real or imag argument is now deprecated; it should only be passed as a single positional argument.

📚 Documentation preview 📚: https://cpython-previews--119620.org.readthedocs.build/

* Passing a string as the "real" keyword argument is now an error;
  it should only be passed as a single positional argument.
* Passing a complex number as the *real* or *imag* argument is now deprecated;
  it should only be passed as a single positional argument.
* Share common classes.
* Use exactly representable floats and exact tests.
* Check the sign of zero components.
* Remove duplicated tests (mostly left after merging int and long).
* Reorder tests in more consistent way.
* Test more error messages.
* Add tests for missed cases.

skirpichev left a comment

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

tmp == NULL condition on L1092 also now only partially covered.

Just checked coverage report after ./python -m test test_complex test_capi.test_complex

Comment thread Objects/complexobject.c
@@ -930,31 +998,23 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i)
if (r == NULL) {
r = _PyLong_GetZero();

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 inaccessible.

Copy link
Copy Markdown
Member Author

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

It is now covered by a new test added in #119635 for complex(imag=1.5).

Comment thread Objects/complexobject.c Outdated
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"complex() argument 'real' must be a real number, not %T",
r)) {
return NULL;

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

Nit: this is not tested. Ditto for other warning.

Comment thread Objects/complexobject.c Outdated

/* Special-case for a single argument when type(arg) is complex. */
if (PyComplex_CheckExact(r) && i == NULL &&
type == &PyComplex_Type) {

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

Can this be false? If so, this is not tested.

Copy link
Copy Markdown
Member Author

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

Yes, it can be false. Added tests.

Comment thread Objects/complexobject.c
}

tmp = try_complex_special_method(r);
if (tmp) {

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

else if (PyErr_Occurred()) branch is not tested.

Comment thread Objects/complexobject.c
@@ -970,9 +1030,8 @@ complex_new_impl(PyTypeObject *type, PyObject *r, PyObject *i)
(nbr->nb_float == NULL && nbr->nb_index == NULL && !PyComplex_Check(r)))

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

Hmm, it seems there is a coverage regression, not all branches are tested.

Copy link
Copy Markdown
Member Author

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 should be covered by new tests added in test added in #119635.

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

Now it's better, yet one branch seems to be missed:

   1029         [ +  - ]:       3869 :     if (nbr == NULL ||

Looks like it's nbr!=NULL.

Comment thread Objects/complexobject.c
Py_TYPE(r)->tp_name);
"complex() argument 'real' must be a real number, not %T",
r);
if (own_r) {

skirpichev May 28, 2024
edited
Loading

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

Actually, own_r condition here is inaccessible. If we are here - try_complex_special_method() call above was unsuccessful.

See #109642. Maybe it worth to include constructor-related changes from that pr.

Copy link
Copy Markdown
Member Author

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'll see.

Copy link
Copy Markdown
Member Author

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 will be removed after the end of the deprecation period. So it is not worth to spent effort to optimize it.

Copy link
Copy Markdown
Member Author

Thank you for your review @skirpichev. Much of coverage is added by test added in #119635.

Comment thread Objects/complexobject.c
Comment on lines +1072 to +1073
if (nbr == NULL ||
(nbr->nb_float == NULL && nbr->nb_index == NULL))

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 also looks untested:

    1072         [ +  - ]:         10 :         if (nbr == NULL ||
    1073   [ +  -  +  - ]:         10 :             (nbr->nb_float == NULL && nbr->nb_index == NULL))

Comment thread Objects/complexobject.c
Py_complex c = ((PyComplexObject*)arg)->cval;
res = complex_subtype_from_doubles(type, c.real, c.imag);
}
else if ((nbr = Py_TYPE(arg)->tp_as_number) != NULL &&

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

One branch missed:

     952         [ +  - ]:         55 :     else if ((nbr = Py_TYPE(arg)->tp_as_number) != NULL &&
     953   [ +  +  +  + ]:         55 :              (nbr->nb_float != NULL || nbr->nb_index != NULL))

Copy link
Copy Markdown
Member Author

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 to interpret this? It looks to me that all possible combinations are covered.

  • nbr == NULL: complex({})
  • nbr != NULL && nbr->nb_float != NULL: complex(MockFloat(4.25))
  • nbr != NULL && nbr->nb_float == NULL && nbr->nb_index != NULL: complex(MockIndex(42))
  • nbr != NULL && nbr->nb_float == NULL && nbr->nb_index == NULL: complex(MockInt())

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

Hmm, branch coverage output for C code looks cryptic. Try complex([]), i.e. nbr != NULL, but it has no nb_float/index, that works for me.

Copy link
Copy Markdown
Member Author

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 think complex(MyInt()) (where MyInt has the __int__ method already covers this).

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

No. Oh, I see: that should be nbr == NULL condition. So complex(object()) will work too.

Comment thread Objects/complexobject.c
return result;
}

static PyObject *

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

Would it be worth adding a comment above this function explaining the purpose (and explaining why this is different from complex_new)?

mdickinson left a comment

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

LGTM in principle, and works as expected in manual testing. I won't claim to have examined all the branching possibilities, but it looks as though @skirpichev is on top of that. :-)

Comment thread Objects/complexobject.c
else if (PyErr_Occurred()) {
return NULL;
}
else if (PyComplex_Check(arg)) {

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

FYI, this is not tested. But I'm not sure if that's a right logic. Complex subclasses should be covered by try_complex_special_method(), c.f. PyNumber_Float().

Copy link
Copy Markdown
Member Author

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

try_complex_special_method is relatively expensive in comparison to PyNumber_Float(), because there is no nb_complex slot. And __complex__ is looked up even for exact complex, float and int. I planned to do something with this, but in a different PR. We can not also completely exclude complex subclasses without __complex__.

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

try_complex_special_method is relatively expensive in comparison to PyNumber_Float()

That seems to be an implementation detail. I wonder if we could add nb_complex slot: there is a reserved slot right now anyway.

And complex is looked up even for exact complex, float and int.

The current (i.e. in the main) code - uses here same logic as the float constructor: there is a case for exact complex (as for exact float in PyNumber_Float()).

We can not also completely exclude complex subclasses without complex.

People could break thing in very crazy ways, but should we support such cases? (Looks as a variant of #112636.)

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

Comment thread Objects/complexobject.c
Py_complex c = ((PyComplexObject*)arg)->cval;
res = complex_subtype_from_doubles(type, c.real, c.imag);
}
else if ((nbr = Py_TYPE(arg)->tp_as_number) != NULL &&

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

Hmm, branch coverage output for C code looks cryptic. Try complex([]), i.e. nbr != NULL, but it has no nb_float/index, that works for me.

Comment thread Objects/complexobject.c
}
PyObject *orig_r = r;

tmp = try_complex_special_method(r);

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

IIUIC, this logic will be dropped after a deprecation period as well. I'm not sure if this is obvious, maybe worth a comment.

skirpichev left a comment

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

LGTM, except for useless PyComplex_Check() branch in the actual_complex_new() and one missing branch coverage here.

The rest, probably, not worth for improving, as it will be eventually removed. Though, I think that removing inaccessible cases (various own_r branches) will make code more readable.

Comment thread Objects/complexobject.c
Py_complex c = ((PyComplexObject*)arg)->cval;
res = complex_subtype_from_doubles(type, c.real, c.imag);
}
else if ((nbr = Py_TYPE(arg)->tp_as_number) != NULL &&

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

No. Oh, I see: that should be nbr == NULL condition. So complex(object()) will work too.

Comment thread Objects/complexobject.c
Comment on lines +953 to +959
else if (PyComplex_Check(arg)) {
/* Note that if arg is of a complex subtype, we're only
retaining its real & imag parts here, and the return
value is (properly) of the builtin complex type. */
Py_complex c = ((PyComplexObject*)arg)->cval;
res = complex_subtype_from_doubles(type, c.real, c.imag);
}

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
else if (PyComplex_Check(arg)) {
/* Note that if arg is of a complex subtype, we're only
retaining its real & imag parts here, and the return
value is (properly) of the builtin complex type. */
Py_complex c = ((PyComplexObject*)arg)->cval;
res = complex_subtype_from_doubles(type, c.real, c.imag);
}

That seems redundant (and untested). Complex subclasses have __complex__() dunder, so they should be handled by try_complex_special_method() helper (even if the dunder is broken somehow).

serhiy-storchaka merged commit ef01e95 into python:main May 30, 2024
serhiy-storchaka deleted the complex-constructor branch May 30, 2024 20:31
noahbkim pushed a commit to hudson-trading/cpython that referenced this pull request Jul 11, 2024
…ythonGH-119620)

* Passing a string as the "real" keyword argument is now an error;
  it should only be passed as a single positional argument.
* Passing a complex number as the "real" or "imag" argument is now deprecated;
  it should only be passed as a single positional argument.
estyxx pushed a commit to estyxx/cpython that referenced this pull request Jul 17, 2024
…ythonGH-119620)

* Passing a string as the "real" keyword argument is now an error;
  it should only be passed as a single positional argument.
* Passing a complex number as the "real" or "imag" argument is now deprecated;
  it should only be passed as a single positional argument.
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants


Back | FazBrowse Home | New Git URL