~bool deprecation

I was the one to propose the deprecation and authored the PR. I believe it’s helpful for the discussion to summarize the motivation and considerations:

TL;DR: ~ on bool is prone to misuse. Changing behavior to logical negation would be too risky as an API change, but disallowing it can be done without significant user impact.

Most of this is guided by “practicality beats purity”

  • For better or worse a number of users associate ~ with negation because many downstream libraries have that notion (numpy, sympy, …). This leads to code like if ~condition, which is a hard-to-spot bug, because the code runs, but bool(~condition) is True no matter whether condition is True or False.

    The fundamental question is, can we get rid of this potential footgun? Or do we tell users to RTFM and understand that bools are ints (having more than one bit) and ~ is the bitwise inversion of the underlying int?

  • If we decide to remove the footgun, this is is an API change: We have two options

    1. change the behavior to logical negation - I regard this as too risky. There is no good migration path. Basically we’d have to hard-switch the behavior. This is problematic for the users. If they have used it in the buggy unintended way above silently fixing is not ideal, it would be better to inform them that their code has not been working as intended so far. Additionally, we cannot exclude that there are very few users, who used that behavior in a non-broken way (in fact there are https://github.com/python/cpython/pull/103487#issuecomment-1953913848).
      Side-remark: I would not be opposed to reintroducing ~ on bool as logical negation in some far future, but I believe that could only happen after it raised for several versions. Users do not update with every Python release (sometimes they step up two or three versions) and I would want to make they still touch a version that raises.
    2. prohibit ~ on bool (i.e. deprecate and eventually raise) - Technically, this breaks the Liskov Substrituion Principle. However, I claim that in practice this is not an issue. The logical negation operator is not. Practically, we do not need I should be very rare to want the bitwise inversion of the underlying int represenation of a bool (i.e. map ~False → -1; ~True → -2). But if as user really wants this, one can always write ~int(b) explicitly, which is easier to understand than ~b.
      Note also that we already have not as the logical negation operator. In practice that’s enough and we do not necessarily need ~ with the same semantics.
17 Likes