I've tried any()
, all()
but they only seem to work for iterables
If you simply want to make these work, please just see How to test the membership of multiple values in a list as I originally offered.
What follows is an extremely bad idea, presented purely for educational purposes.
I know this will print false, because of short-circuit evaluation. However, is there a way I can tell Python to stop this style of evaluation, without laying out each condition?
There are many things we have to understand before we can write code laid out similarly to this to do the job.
We cannot use or
, because it is not redefinable. We can use |
, which is defined using the special method __or__
. As a bonus, this also has more convenient operator precedence for our purposes.
We do have to avoid short-circuit behaviour in order for our code to consider everything that appears on the left-hand side. Fortunately, |
- doesn't short-circuit - neither do any of the other operators we can implement. (They couldn't possibly, because we implement them with methods, and the arguments have to be evaluated before the method can be called.)
However, short-circuit behaviour does not explain why the original code fails. Operator precedence also does not explain why the original code fails. The original code would fail no matter what the result of 1 or 2 or 3
was. The only things that can be found in [2, 5, 7]
are 2
, 5
and 7
, and the evaluation of 1 or 2 or 3
would not have access to that information. So even if we came up with some rule that made 1 or 2 or 3
evaluate to (say) 2
, that rule would fail when we change the list to [3, 5, 7]
- we would have no way to know whether we need the result to be 2
or 3
in order for the code to work.
Since __or__
is a method, we need our own class to define it on. That means that at least one element on the left-hand side needs to be an instance of that class. Then we can define __or__
such that it returns another instance.
The behaviour of the in
operator is determined by __contains__
. However, this can only be implemented by the right-hand side - only the container can decide whether it contains something; the element cannot decide that it is in the container. Therefore, we also need a wrapper class for the right-hand side. It can, however, be the same class, if we're clever enough.
That __contains__
implementation needs to be aware that it's receiving an instance of the left-hand-side-wrapper class, and implement the necessary logic. (That logic is, realistically, going to use any
, which is what we should have just done directly in the first place. It could also use an explicit for
loop with or
, which isn't really any better.)
Putting those pieces together: we will define a class AnyOf
, which represents a collection of candidates for some operation. We will define __contains__
in an abusive way: x
contains y
(equivalently, y
is in x
) if and only if at least one of the candidates represented by x
, contains at least one of the candidates represented by y
. We will define __or__
to make a new AnyOf
instance, essentially adding the right-hand side to the candidates. This is minimally functional, just for demonstration purposes.
It looks like:
class AnyOf:
def __init__(self, first):
self._candidates = [first]
def __or__(self, another):
result = AnyOf(another)
result._candidates.extend(self._candidates)
return result
def __contains__(self, lhs):
if not isinstance(lhs, AnyOf): # testing a single item
lhs = AnyOf(lhs) # wrap it so the main logic can be used
return any(
contained in container
for contained in lhs._candidates
for container in self._candidates
)
Let's test it:
>>> AnyOf(1) | 2 | 3 in AnyOf([2, 5, 7])
True
Notice here that I defined things in such a way that the right-hand side wraps a single candidate, which is a list - it does not wrap three separate candidates. (If we want it to work like AnyOf(1) | 2 | 3 in AnyOf(2) | 5 | 7
instead, then we should not iterate for container in self._candidates
, because now self._candidates
is the unique container we care about looking in
.)
There are other possible enhancements, depending on what relational operators (==
, <
etc.) are needed. Similarly, one could define a complementary AllOf
to represent logical-and logic rather than logical-or. All of these possibilities are left as exercises.