Add an operator for `not x`

Currently there are 2 main ways to check for not boolean.
The first is:

x = True
if x is False:
    ...

The second is:

x = True
if not x:
    ...

not x doesn’t have a unique dunder, instead it relies on the bool dunder.
I think making a unique dunder for not x would make sense, with a unique operator for it.
If the dunder wouldn’t be implemented then it’d resort to bool(x) is False perhaps.
Now what would that operator be? I think there’s already a suitable answer for that, and I’ll explain.

When doing x == y the eq dunder gets called, which goes for “equal”
When doing x != y the ne dunder gets called, which goes for “not equal”

So perhaps, the most logical operator would be !x.
Lucky for us, the ! operator isn’t used.

Technically, there is no issue with not, but having an operator and a dunder for it can be useful, as it is in != and ne.

How would it be useful?

2 Likes

Mostly for convenience.
And since it already exists in python, in the ne dunder, so there’s a suitable operator for it as well.
And that the ! operator isn’t used in general.

I don’t think ! is any more convenient than not, though.

I was wondering if there’s any use-case where I’d want to define a __not__ dunder in particular, instead of relying whatever the boolean value was. I don’t think this idea has any traction without a compelling use for that.

1 Like

What could be more convenient than if not x:, and what would be more convenient about it?

The ! operator was considered many, many years ago and quite consciously left out. Python isn’t about saving a few keystrokes; it’s about expressing simple ideas with simple, readable syntax.

Separate __eq__ and __ne__ mainly exist because we didn’t always have things like functools.total_ordering - the design has changed over time, more generally. It doesn’t really help with expressing the logic, and the same would be true of __bool__ vs. a hypothetical __not__. (Also, as Eryk says - I couldn’t think how to express it immediately - the split design is flawed in that it allows for inconsistent definitions; there should be a reason why that’s necessary and an acceptable trade-off.)

Also, this has nothing to do with typing. (Here, we don’t mean “pressing keys on a keyboard”; we mean “helping third-party tools attempt static type analysis”.)

2 Likes

My initial gut reaction is that I am against allowing an object to be logically inconsistent in the manner that the comparison operations == and != are allowed to be inconsistent with each other for a given object. There should only be __bool__().

4 Likes

The value is not x is defined as not bool(x). The special method for bool() is __bool__. Makes no sense to add a 2nd special method. The same consideration applies to and and or plus the fact that in Python, their short-circuit behavior means that they are not proper operators. x and y would not be equivalent to `x.and(y). The analogy with true operators and their special methods does not hold.

2 Likes

If you want to have an “invert” operation for a custom object, use ~ and it’s existing dunder __invert__.

4 Likes

I thought about bringing up ~. It is used extensively in the numpy ecosystem when one wants to invert a boolean array. e.g. my_array[~array_of_bools].

So introducing another operator would be confusing there.

The __invert__() operation is a bitwise arithmetic inversion, so using it for logical negation is stretching the intended use case.

2 Likes

Although it seems that there has not yet been sufficient need to actually implement them, but logical operator overloading is a valid and useful idea in general.

E.g. I know numpy and https://data-apis.org/array-api/ in general would benefit from it. Currently logical operations are done via

def logical_<op_name>(self, other):
    ...

methods, which is in disharmony with how these are performed for scalar objects in python.

There is a rejected PEP on this, but it has been a long time ago:

From my own experience of emulating various objects there are 2 painful areas in current design:

  1. logical operations
  2. containment

And I understand why - these simply are not as straight forward to sort out in comparison to many others. This is due to:

  1. Their unique nature. e.g. short-circuiting of logical operations
  2. current situation of how things are. e.g. asymmetric nature of containment: ASTs(ast.In() operator) and (__contains__).

I adapted and I can do what I need to do (same as many others I imagine), but it does not necessarily mean that improvements should not be considered.

One could argue by mentioning the override of __truediv__ by pathlib.Path :stuck_out_tongue:

What’s you actual use case?

There are six independent comparison dunders to allow for things like
numpy arrays where comparisons don’t return booleans. In cases like
that, ‘a != b’ is not the same as ‘not a == b’, etc.

3 Likes

not eq isn’t always ending up in the same result as ne.
You can see that in numpy.
Same logic can be applied in here.

Also, I see no reason as to why not implement it.
I’ve showed valid points as for why to add it, but I haven’t seen a point in the comments as for why not to add it.

This is true but it already happens e.g. in SymPy:

In [11]: from sympy import *

In [12]: x, y, z = symbols('x, y, z')

In [13]: condition = x & (y | ~z)

In [14]: condition
Out[14]: x ∧ (y ∨ ¬z)

And NumPy:

In [16]: cond = np.array([1, 2, 3]) > 2

In [17]: cond
Out[17]: array([False, False,  True])

In [18]: ~cond
Out[18]: array([ True,  True, False])

I expect that other libraries use this as well. There is a common desire for a not operator that does not coerce to bool along with operators like & for other logical operations.

It isn’t people’s job to convince you that this shouldn’t be added, but it’s your job to make a convincing proposal, if you want this to be added. Python is a mature language and changes require a lot more evidence than “I think it might be useful”.

In the absence of compelling evidence the status quo always wins, especially when it comes to adding new syntax. You want the change, so you need to put in the work.

Although in this case I probably would not bother, I don’t think there’s any chance of this ever being added, it would add a confusing new unary operator with unclear purpose outside of APIs emulating a DSL, where generally those APIs can already be implemented today using the bitwise operators ~, & and |.

7 Likes

Every addition has some cost, so needs sufficient positive benefit. Since you have not specified exactly how you would have x.not be used to implement not x, it is difficult to specify. But let me be more explicit than I was above. Changing the definition of not will almost certainly break code. We core developers consider this a high cost.

Any cost is unnecessary because

a) ~ and __invert__ are available and used. I have no problem with their use.

The builtin collections set, frozenset, and dict similarly stretch the meaning of the binary ‘bitwise’ operators to collection items. The analogy is that an int is a sequence collection of bits, so bits are to ints as items are to sets.

b) named methods are available. Nothing wrong with, for instance, x.ynot() instead of not x for a custom negation.

There’s the ne dunder, and for what i know it didn’t cause breaking code.
I agree with your point in b, that there’s already ways to implement custom not.
About your point in a, I think it’s not valid here, since invert of a boolean results in a bitwise inversion, ~True isn’t False, it might make sense to change this specific attitude and make the invert dunder different in booleans, there’s a slight chance that it would break code, although it probably wouldn’t, still, the point in a becomes invalid in that case.

Edit:
And I appreciate your will for looking into it.

I think the problem with your proposal is that its entire rationale is simply “The eq method and ‘==’ operator has a corresponding ne method and '!='operator. Ergo, to maintain consistency, there should be a dunder method and operator that acts as the inverse of the bool method.”.

In general, consistency in the design of a a programming language will usually but NOT always enable it to have more use cases.

If a maintainable inconsistency in the design of a programming language will have more use cases than trying to make them consistent, then what utility is there to proceed with the latter option?

Consider PEP 207 which allowed for the independently overloading of the dunder methods associated with the <, >, <=, >=, ==, != operators and how that allowed for operations that were before considered to be opposite and symmetric to have asymmetric implementations. There were use cases for that inconsistency (check the PEP itself) and as such it was implemented.

For a more recent example, consider how PEP 635 specifies that the str, bytes, and bytearray classes shouldn’t have their instances be pattern matched with cases that are looking for sequence patterns, despite them being also being Sequence subclasses. The use cases of those classes as ‘atomic’ types were so widespread that users were confused when the instances of those classes were matched and as such that inconsistency was developed.

Consistency isn’t a hard rule to be adhered to and corrected for at all times. It’s first and foremost a DESIGN GUIDELINE, and a motivation to implement a new feature second.
So if you want this feature in Python, specify use cases that are worthy enough to warrant the implementation of your feature. As right now, you haven’t provided any use cases that would warrant the implementation of a not bool method and operator.

(The below section was written before the typing tag was removed)
Also please get rid of the typing tag you marked on this post. It’s could be very misleading for other users as the subject matter doesn’t involve type checking whatsoever. Other users have already pointed it out and may continue to do so. Failure to remove the tag could convince them that you are willing to not only clickbait them but also act in bad faith, thereby discouraging users to consider and engage with your post. I’m not saying you are acting in bad faith right now, just that it may come off that way if you don’t correct for it.