Skip to content

gh-109218: Improve documentation for the complex() constructor #119687

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

serhiy-storchaka
Copy link
Member

@serhiy-storchaka serhiy-storchaka commented May 28, 2024

Copy link
Member

@skirpichev skirpichev left a comment

Choose a reason for hiding this comment

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

LGTM

Few remarks:
0) This touch also bool, float and int constructors; changes looks fine, but at least the commit message could be more verbose and mention that.

  1. Should be merged before gh-109218: Deprecate weird cases in the complex() constructor #119620
  2. cmath has yet another instance of z == z.real + z.imag*1j invariant, mentioned in the issue thread. I think this should be fixed too, probably just by removing that line.

Comment on lines 382 to 392
If the argument is a string, it should contain a decimal number, optionally
preceded by a sign, and optionally followed by the ``j`` or ``J`` suffix,
or a pair of decimal numbers, optionally preceded by a sign, separated by
the ``+`` or ``-`` operator, and followed by the ``j`` or ``J`` suffix.
A string representing a NaN (not-a-number), or infinity is acceptable
in place of a decimal number, like in :func:`float`.
The string can optionally be surrounded by whitespaces and the round
parenthesis ``(`` and ``)``, which are ignored.
The string must not contain whitespace between ``+``, ``-``, the ``j`` or
``J`` suffix, and the decimal number. For example, ``complex('1+2j')`` is fine, but
``complex('1 + 2j')`` raises :exc:`ValueError`.
Copy link
Member

Choose a reason for hiding this comment

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

This is a lot of detail for the second paragraph. Users might find it easier to understand how to create complex numbers if the REPL examples came before this full specification

Copy link
Member Author

Choose a reason for hiding this comment

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

I followed the example of float() which has even more detailed description of the input format in the second paragraph. I merged it with a note about whitespaces, so all information related to parsing string to complex is now in one place.

I would be happy to make the description shorter, even if less detailed. Do you have any suggestions?

Copy link
Member

Choose a reason for hiding this comment

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

Fair enough, though I would honestly also be inclined to move the float() examples higher up as well. I think it's probably correct to provide a full specification here; I just worry that it could overwhelmingly technical for Python beginners, who will probably learn best by looking at the examples.

In any event, I don't have a strong opinion; I'm happy to defer to you and Mark

Copy link
Member

Choose a reason for hiding this comment

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

I think @AlexWaygood has a good point here: for readability, it would be nice to see just three examples (one for each of the three signatures) following the initial "Convert a single string or number to a complex number, or ..." line, before we get into the heavy detail of exactly which strings are permitted.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have moved examples for complex() and float() and added examples for int().

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Copy link
Member

@mdickinson mdickinson left a comment

Choose a reason for hiding this comment

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

LGTM.

This is very much a judgement call and personal opinion, and I don't know how well it fits with the general direction of our documentation, but I do think it would be nice to have a small number of examples immediately following the initial "Convert a single string ..." doc line.

Comment on lines 382 to 392
If the argument is a string, it should contain a decimal number, optionally
preceded by a sign, and optionally followed by the ``j`` or ``J`` suffix,
or a pair of decimal numbers, optionally preceded by a sign, separated by
the ``+`` or ``-`` operator, and followed by the ``j`` or ``J`` suffix.
A string representing a NaN (not-a-number), or infinity is acceptable
in place of a decimal number, like in :func:`float`.
The string can optionally be surrounded by whitespaces and the round
parenthesis ``(`` and ``)``, which are ignored.
The string must not contain whitespace between ``+``, ``-``, the ``j`` or
``J`` suffix, and the decimal number. For example, ``complex('1+2j')`` is fine, but
``complex('1 + 2j')`` raises :exc:`ValueError`.
Copy link
Member

Choose a reason for hiding this comment

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

I think @AlexWaygood has a good point here: for readability, it would be nice to see just three examples (one for each of the three signatures) following the initial "Convert a single string or number to a complex number, or ..." line, before we get into the heavy detail of exactly which strings are permitted.

Comment on lines +407 to +409
If both arguments are complex numbers, return a complex number with the real
component ``real.real-imag.imag`` and the imaginary component
``real.imag+imag.real``.
Copy link
Member

Choose a reason for hiding this comment

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

Presumably this will mention the deprecation if/when #119620 is merged? Or is the plan to remove the mention of allowing complex altogether?

I'd be tempted to not even document this (mis)feature, on the basis that people who already know about it already know about it, and people who don't know about it shouldn't start using it.

Copy link
Member Author

Choose a reason for hiding this comment

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

The plan was to leave this until the end of the deprecation period.

I would be happy to remove this, but then we will receive another reports about wrong documentation or behavior. The old real + imag*1j was more correct for input with finite components (and ignoring corner cases with negative zero).

"Each argument may be any numeric type (including complex)." is already in the current documentation, so it is late to hide this without also changing the behavior.

Copy link
Member

Choose a reason for hiding this comment

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

Fair enough.

Copy link
Member

Choose a reason for hiding this comment

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

Best we can do is to put the deprecation note as close to this sentence, as possible:)

Comment on lines +386 to +401
>>> complex('+1.23')
(1.23+0j)
>>> complex('-4.5j')
-4.5j
>>> complex('-1.23+4.5j')
(-1.23+4.5j)
>>> complex('\t( -1.23+4.5J )\n')
(-1.23+4.5j)
>>> complex('-Infinity+NaNj')
(-inf+nanj)
>>> complex(1.23)
(1.23+0j)
>>> complex(imag=-4.5)
-4.5j
>>> complex(-1.23, 4.5)
(-1.23+4.5j)
Copy link
Member

Choose a reason for hiding this comment

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

I suggest we reorder some of these so that the stranger ones come below the more common ones:

Suggested change
>>> complex('+1.23')
(1.23+0j)
>>> complex('-4.5j')
-4.5j
>>> complex('-1.23+4.5j')
(-1.23+4.5j)
>>> complex('\t( -1.23+4.5J )\n')
(-1.23+4.5j)
>>> complex('-Infinity+NaNj')
(-inf+nanj)
>>> complex(1.23)
(1.23+0j)
>>> complex(imag=-4.5)
-4.5j
>>> complex(-1.23, 4.5)
(-1.23+4.5j)
>>> complex('+1.23')
(1.23+0j)
>>> complex('-4.5j')
-4.5j
>>> complex('-1.23+4.5j')
(-1.23+4.5j)
>>> complex(-1.23, 4.5)
(-1.23+4.5j)
>>> complex(1.23)
(1.23+0j)
>>> complex(imag=-4.5)
-4.5j
>>> complex('\t( -1.23+4.5J )\n')
(-1.23+4.5j)
>>> complex('-Infinity+NaNj')
(-inf+nanj)

Copy link
Member Author

Choose a reason for hiding this comment

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

But this mixes examples for three fundamentally different roles: string parsing, numerical conversion and construction from components.

Copy link
Member

Choose a reason for hiding this comment

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

It wasn't immediately obvious to me that that was the reason behind your ordering, and I doubt it would be obvious to beginners either, which is why I suggested a different order of "most useful to least useful". But again, I don't have a strong opinion; your PR is certainly fine as-is :-)

Copy link
Member

Choose a reason for hiding this comment

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

I would also expect the most useful to the least useful examples even though it mixes the parsers.

Convert a single string or number to a complex number, or create a
complex number from real and imaginary parts.

Examples:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Examples:
.. rubric:: Examples

It may look a bit nicer, but I don't know whether you want a smaller heading here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Looking how it is used in Doc/library/asyncio-api-index.rst, I do not think that it is appropriate here.

Comment on lines +386 to +401
>>> complex('+1.23')
(1.23+0j)
>>> complex('-4.5j')
-4.5j
>>> complex('-1.23+4.5j')
(-1.23+4.5j)
>>> complex('\t( -1.23+4.5J )\n')
(-1.23+4.5j)
>>> complex('-Infinity+NaNj')
(-inf+nanj)
>>> complex(1.23)
(1.23+0j)
>>> complex(imag=-4.5)
-4.5j
>>> complex(-1.23, 4.5)
(-1.23+4.5j)
Copy link
Member

Choose a reason for hiding this comment

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

I would also expect the most useful to the least useful examples even though it mixes the parsers.

The string can optionally be surrounded by whitespaces and the round
parenthesis ``'('`` and ``')'``, which are ignored.
The string must not contain whitespace between ``'+'``, ``'-'``, the
``'j'`` or ``'J'`` suffix, and the decimal number.
Copy link
Member

Choose a reason for hiding this comment

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

Nit: this "j" vs "J" happens in several places. You could just mention (as in the float constructor) that parsing is case-insensitive:

>>> complex('(InFiNITY+1j)')
(inf+1j)

Copy link
Member Author

Choose a reason for hiding this comment

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

But "e" and "E" are mentioned separately in the float grammar. We can say also that all whitespaces except the leading and trailing are not acceptable, but I think it will be clearer to be a little more verbose here.

Convert a single string or number to a complex number, or create a
complex number from real and imaginary parts.

Examples:
Copy link
Member Author

Choose a reason for hiding this comment

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

Looking how it is used in Doc/library/asyncio-api-index.rst, I do not think that it is appropriate here.

The string can optionally be surrounded by whitespaces and the round
parenthesis ``'('`` and ``')'``, which are ignored.
The string must not contain whitespace between ``'+'``, ``'-'``, the
``'j'`` or ``'J'`` suffix, and the decimal number.
Copy link
Member Author

Choose a reason for hiding this comment

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

But "e" and "E" are mentioned separately in the float grammar. We can say also that all whitespaces except the leading and trailing are not acceptable, but I think it will be clearer to be a little more verbose here.

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
@serhiy-storchaka serhiy-storchaka enabled auto-merge (squash) May 30, 2024 19:26
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
@serhiy-storchaka serhiy-storchaka merged commit ec1ba26 into python:main May 30, 2024
36 checks passed
@miss-islington-app
Copy link

Thanks @serhiy-storchaka for the PR 🌮🎉.. I'm working now to backport this PR to: 3.12, 3.13.
🐍🍒⛏🤖

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request May 30, 2024
…ythonGH-119687)

* Remove the equivalence with real+imag*1j which can be incorrect in corner
  cases (non-finite numbers, the sign of zeroes).
* Separately document the three roles of the constructor: parsing a string,
  converting a number, and constructing a complex from components.
* Document positional-only parameters of complex(), float(), int() and bool()
  as positional-only.
* Add examples for complex() and int().
* Specify the grammar of the string for complex().
* Improve the grammar of the string for float().
* Describe more explicitly the behavior when real and/or imag arguments are
  complex numbers. (This will be deprecated in future.)
(cherry picked from commit ec1ba26)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
@miss-islington-app
Copy link

Sorry, @serhiy-storchaka, I could not cleanly backport this to 3.12 due to a conflict.
Please backport using cherry_picker on command line.

cherry_picker ec1ba264607b2b7b98d2602f5536a1d02981efc6 3.12

@bedevere-app
Copy link

bedevere-app bot commented May 30, 2024

GH-119803 is a backport of this pull request to the 3.13 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.13 bugs and security fixes label May 30, 2024
@serhiy-storchaka serhiy-storchaka deleted the complex-constructor-docs branch May 30, 2024 20:25
serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this pull request May 30, 2024
…ructor (pythonGH-119687)

* Remove the equivalence with real+imag*1j which can be incorrect in corner
  cases (non-finite numbers, the sign of zeroes).
* Separately document the three roles of the constructor: parsing a string,
  converting a number, and constructing a complex from components.
* Document positional-only parameters of complex(), float(), int() and bool()
  as positional-only.
* Add examples for complex() and int().
* Specify the grammar of the string for complex().
* Improve the grammar of the string for float().
* Describe more explicitly the behavior when real and/or imag arguments are
  complex numbers. (This will be deprecated in future.)
(cherry picked from commit ec1ba26)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
@bedevere-app
Copy link

bedevere-app bot commented May 30, 2024

GH-119805 is a backport of this pull request to the 3.12 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.12 only security fixes label May 30, 2024
serhiy-storchaka added a commit that referenced this pull request May 30, 2024
…GH-119687) (GH-119803)

* Remove the equivalence with real+imag*1j which can be incorrect in corner
  cases (non-finite numbers, the sign of zeroes).
* Separately document the three roles of the constructor: parsing a string,
  converting a number, and constructing a complex from components.
* Document positional-only parameters of complex(), float(), int() and bool()
  as positional-only.
* Add examples for complex() and int().
* Specify the grammar of the string for complex().
* Improve the grammar of the string for float().
* Describe more explicitly the behavior when real and/or imag arguments are
  complex numbers. (This will be deprecated in future.)
(cherry picked from commit ec1ba26)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
serhiy-storchaka added a commit that referenced this pull request May 30, 2024
…GH-119687) (ПР-119805)

* Remove the equivalence with real+imag*1j which can be incorrect in corner
  cases (non-finite numbers, the sign of zeroes).
* Separately document the three roles of the constructor: parsing a string,
  converting a number, and constructing a complex from components.
* Document positional-only parameters of complex(), float(), int() and bool()
  as positional-only.
* Add examples for complex() and int().
* Specify the grammar of the string for complex().
* Improve the grammar of the string for float().
* Describe more explicitly the behavior when real and/or imag arguments are
  complex numbers. (This will be deprecated in future.)
(cherry picked from commit ec1ba26)
noahbkim pushed a commit to hudson-trading/cpython that referenced this pull request Jul 11, 2024
…ythonGH-119687)

* Remove the equivalence with real+imag*1j which can be incorrect in corner
  cases (non-finite numbers, the sign of zeroes).
* Separately document the three roles of the constructor: parsing a string,
  converting a number, and constructing a complex from components.
* Document positional-only parameters of complex(), float(), int() and bool()
  as positional-only.
* Add examples for complex() and int().
* Specify the grammar of the string for complex().
* Improve the grammar of the string for float().
* Describe more explicitly the behavior when real and/or imag arguments are
  complex numbers. (This will be deprecated in future.)
estyxx pushed a commit to estyxx/cpython that referenced this pull request Jul 17, 2024
…ythonGH-119687)

* Remove the equivalence with real+imag*1j which can be incorrect in corner
  cases (non-finite numbers, the sign of zeroes).
* Separately document the three roles of the constructor: parsing a string,
  converting a number, and constructing a complex from components.
* Document positional-only parameters of complex(), float(), int() and bool()
  as positional-only.
* Add examples for complex() and int().
* Specify the grammar of the string for complex().
* Improve the grammar of the string for float().
* Describe more explicitly the behavior when real and/or imag arguments are
  complex numbers. (This will be deprecated in future.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Documentation in the Doc dir skip news
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants