Skip to content
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

gh-82012: Deprecate bitwise inversion (~) of bool #103487

Merged
merged 5 commits into from May 3, 2023

Conversation

timhoffm
Copy link
Contributor

@timhoffm timhoffm commented Apr 12, 2023

The bitwise inversion operator on bool returns the bitwise inversion of the underlying int value; i.e. ~True == -2 such that bool(~True) == True.

It's a common pitfall that users mistake ~ as negation operator and actually want not. Supporting ~ is an artifact of bool inheriting from int. Since there is no real use-case for the current behavior, let's deprecate ~ on bool and later raise an error. This removes a potential source errors for users.

Full reasoning: #82012 (comment)

Lib/test/test_bool.py Outdated Show resolved Hide resolved
@timhoffm timhoffm force-pushed the deprecate-bool-invert branch 2 times, most recently from b94d85b to 20dec73 Compare April 13, 2023 23:57
@hugovk
Copy link
Member

hugovk commented Apr 28, 2023

Please document this in What's New at https://docs.python.org/3.12/whatsnew/3.12.html#deprecated

And is this operator documented in the main reference? Let's also mention the deprecation there.

Is the plan to turn this into an error in 3.14, or keep it open ended?

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

LGTM. Let's just merge this.

@iritkatriel
Copy link
Member

We should probably add a comment about this to https://github.com/python/cpython/blob/main/Doc/whatsnew/3.12.rst.

@gvanrossum
Copy link
Member

We should probably add a comment about this to https://github.com/python/cpython/blob/main/Doc/whatsnew/3.12.rst.

Yes!

@timhoffm
Copy link
Contributor Author

I've added the deprecation notice to whatsnew.

Is the plan to turn this into an error in 3.14, or keep it open ended?

I suggest to turn this into an error (and have indicated so in the whatsnew). There is a real danger that some users write if ~condition (which is always True). Warnings are often overlooked, so it's better to eventually error on this. In contrast, I can hardly imagine any intended use for ~some_bool, so this error should not annoy any rightful users.

self.assertEqual(~True, -2)
with self.assertWarns(DeprecationWarning):
# We need to put the bool in a variable, because the constant
# ~False is evaluated at compile time due to constant folding;
Copy link
Member

Choose a reason for hiding this comment

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

We should test this behavior separately (doing something like with assertWarns(DeprecationWarning): exec("~False")).

Copy link
Contributor Author

@timhoffm timhoffm Apr 29, 2023

Choose a reason for hiding this comment

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

Do you mean instead of or in addition to the current test? We originally had eval("~True") but I figured it's clearer to have the warning context only around the operation and not around a comparably complex eval() or exec(). I'm happy to change if there's a benefit of these though.

Copy link
Member

Choose a reason for hiding this comment

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

We should have both. The existing tests check that the deprecation warning is emitted correctly at runtime. The new tests would ensure that if the operation occurs at compile time, we still emit the DeprecationWarning.

Copy link
Member

@JelleZijlstra JelleZijlstra left a comment

Choose a reason for hiding this comment

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

The new behavior should also be mentioned in https://docs.python.org/3.12/library/stdtypes.html#boolean-values

It currently says "In numeric contexts (for example when used as the argument to an arithmetic operator), they behave like the integers 0 and 1, respectively.", which will no longer be true in all contexts.

Doc/whatsnew/3.12.rst Outdated Show resolved Hide resolved
@timhoffm
Copy link
Contributor Author

timhoffm commented Apr 29, 2023

The new behavior should also be mentioned in https://docs.python.org/3.12/library/stdtypes.html#boolean-values

It currently says "In numeric contexts (for example when used as the argument to an arithmetic operator), they behave like the integers 0 and 1, respectively.", which will no longer be true in all contexts.

I suggest to not document this as it would become more confusing than helpful:

In my view, there should be no reason to use bitwise operators on bool; one should always use the boolean operators (or convert to int explicitly). I therefore consider all bitwise operators of bool an implementation detail. While this is public API, it's not something we should advertize.

In particular, if we document ~, we'd also need to leave some words on &, | and ^. And then it gets quite messy. If they operate on bool, they return bool. In contrast, ~ was returning int, will now warn and will error out in the future. Changing to returning bool for ~ meaning a logic negation was rejected in the original issue discussion as too much of an API break (still working but with changed behavior). If I had to document this correctly this would be:

The binary bitwise operators &, | and ^ on two bools return a bool and behave like their logical equivalents. When applying these binary bitwise operators to mixed bool and int arguments, they return an int and interpret the bool as the underlying int (i.e. 0 or 1). The bitwise negation ~ of a bool currently returns the bitwise complement of the underlying int. This is deprecated and will raise an error in 3.14. Generally, the use of bitwise operators on bools is discouraged. Use the logical operators and, or, not and xor instead, or convert to int explicitly.

But maybe you have better ideas what to document.

@timhoffm timhoffm force-pushed the deprecate-bool-invert branch 2 times, most recently from d599825 to edd07f2 Compare April 29, 2023 23:35
The bitwise inversion operator on bool returns the bitwise inversion of the
underlying int value; i.e. `~True == -2` such that `bool(~True) == True`.

It's a common pitfall that users mistake `~` as negation operator and actually
want `not`. Supporting `~` is an artifact of bool inheriting from int. Since there
is no real use-case for the current behavior, let's deprecate `~` on bool and
later raise an error. This removes a potential source errors for users.

Full reasoning: python#82012 (comment)

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
@JelleZijlstra
Copy link
Member

The new behavior should also be mentioned in https://docs.python.org/3.12/library/stdtypes.html#boolean-values
It currently says "In numeric contexts (for example when used as the argument to an arithmetic operator), they behave like the integers 0 and 1, respectively.", which will no longer be true in all contexts.

I suggest to not document this as it would become more confusing than helpful:

In my view, there should be no reason to use bitwise operators on bool; one should always use the boolean operators (or convert to int explicitly). I therefore consider all bitwise operators of bool an implementation detail. While this is public API, it's not something we should advertize.

Whether we like it or not, these operators have worked this way for a long time in Python, and backwards compatibility alone means we can't just make them go away. So it's better to document clearly how they work and what is changing.

In particular, if we document ~, we'd also need to leave some words on &, | and ^. And then it gets quite messy. If they operate on bool, they return bool. In contrast, ~ was returning int, will now warn and will error out in the future. Changing to returning bool for ~ meaning a logic negation was rejected in the original issue discussion as too much of an API break (still working but with changed behavior).

I think the whole reason that we're deprecating only the ~ operator is that it's unlike the others: the results of &, |, and ^ make sense whether you think of bools or ints, it's only ~ that turns something from a bool into a non-bool int.

If I had to document this correctly this would be:

The binary bitwise operators &, | and ^ on two bools return a bool and behave like their logical equivalents. When applying these binary bitwise operators to mixed bool and int arguments, they return an int and interpret the bool as the underlying int (i.e. 0 or 1). The bitwise negation ~ of a bool currently returns the bitwise complement of the underlying int. This is deprecated and will raise an error in 3.14. Generally, the use of bitwise operators on bools is discouraged. Use the logical operators and, or, not and xor instead, or convert to int explicitly.

But maybe you have better ideas what to document.

Your wording sounds good to me!

This also pulls the bool type to top-level of the type
description page. Before it was only documented in the
section "Other Built-in Types / Boolean Values".
@timhoffm
Copy link
Contributor Author

@JelleZijlstra thanks for the feedback. I've rewritten and restructured the bool docs a bit so that it's hopefully more clear and still precise. Please check if this is ok. - It's in a seprate commit, so could be easily modified/reverted.

In particular, I've created a top-level section "Boolean Type - bool", which does the importance of the type more justice.
Before, the bool description was only in documented in the section "Other Built-in Types / Boolean Values". I've moved most of the description over from there, but left the section as a short stub; both because (1) I'm unclear whether we still need "Boolean Values" explicitly in addtion to "Boolean Type", and (2) because that keeps the link so that internal and external references are not broken..

Copy link
Member

@JelleZijlstra JelleZijlstra left a comment

Choose a reason for hiding this comment

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

Looks good, but I'd like to have another core dev look at the docs before we merge.

Copy link
Contributor

@hauntsaninja hauntsaninja left a comment

Choose a reason for hiding this comment

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

I think I'd prefer to remove the Boolean Values section. We can preserve the .. _bltin-boolean-values: and index roles, which should hopefully preserve links.

Doc/library/stdtypes.rst Outdated Show resolved Hide resolved
Doc/library/stdtypes.rst Outdated Show resolved Hide resolved
Doc/library/stdtypes.rst Outdated Show resolved Hide resolved
@timhoffm
Copy link
Contributor Author

I think I'd prefer to remove the Boolean Values section. We can preserve the .. _bltin-boolean-values: and index roles, which should hopefully preserve links.

You mean moving them to the new "Boolean Type" section?

@hauntsaninja
Copy link
Contributor

Yup!

@timhoffm
Copy link
Contributor Author

timhoffm commented May 1, 2023

I think I'd prefer to remove the Boolean Values section. We can preserve the .. _bltin-boolean-values: and index roles, which should hopefully preserve links.

HTML links to the section cannot be preseved the URL was https://docs.python.org/3.12/library/stdtypes.html#boolean-values and the anchor #boolean-values is constructed from the section title, not from the label.

I've rewritten internal references (because the label _booltype is more in line with the labeling conventation in the file than _bltin-boolean-values). And the moved index roll works.

Doc/library/stdtypes.rst Outdated Show resolved Hide resolved
Doc/library/stdtypes.rst Outdated Show resolved Hide resolved
timhoffm and others added 2 commits May 1, 2023 08:21
Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com>
Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com>
@gvanrossum
Copy link
Member

Is this waiting for anyone?

@hauntsaninja hauntsaninja merged commit fdb3ef8 into python:main May 3, 2023
19 checks passed
@hauntsaninja
Copy link
Contributor

Not anymore. Thanks all!

@timhoffm timhoffm deleted the deprecate-bool-invert branch May 3, 2023 07:22
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.

None yet

8 participants