0

I want to check if a list contains 1 or 2 or 3

    arr = [2,5,7]
    if (1 or 2 or 3) in arr:
      print("true")
    else: 
      print("false")

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?

I've tried any(), all() but they only seem to work for iterables

DOMINi04
  • 1
  • 2
  • @Kache I found a more precise duplicate. – Karl Knechtel Jul 06 '22 at 03:40
  • Voting to reopen. User is not asking about how to "test multiple variables for equality". User is asking a very beginner unfamiliar /w programming idea: whether `or` can be reconfigured to do something different, and the answer is "No, b/c operators tend to do only one thing, otherwise it would be confusing". – Kache Jul 06 '22 at 03:40
  • @Kache thank you, that was the answer I was looking for. – DOMINi04 Jul 06 '22 at 03:42
  • If this question is a duplicate at all, it's more a duplicate of https://stackoverflow.com/questions/62361656/what-is-the-dunder-method-for-or-operator – Kache Jul 06 '22 at 03:44
  • I can put it another way: If question context was for a different language where all those operators _could_ be redefined, then the answer to this question would be "Yes it's possible, this X is one way, but maybe try this more idiomatic Y instead...". – Kache Jul 06 '22 at 03:59
  • On second thought, that issue can be worked around to some extent. OP: would you accept an answer that uses `|` instead of `or`? Short-circuiting *does* prevent `or` from being used the way you describe, even though it isn't the cause of the actual code's behaviour. I have worked out how to deal with every other issue. – Karl Knechtel Jul 06 '22 at 04:42
  • @KarlKnechtel Yes, that would work. Thank you for your input. – DOMINi04 Jul 06 '22 at 08:46

2 Answers2

0

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.

  1. 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.

  2. 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.)

  3. 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.

  4. 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.

  5. 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.

  6. 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.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
0

One way to do this is with set.isdisjoint. Note that this only works if your elements are hashable (which integers are).

arr = [2, 5, 7]
if not {1, 2, 3}.isdisjoint(arr):
    print("true")
else: 
    print("false")

As the other answer has said, you can't easily make or do what you want here.

LeopardShark
  • 3,820
  • 2
  • 19
  • 33
  • Please consider providing an answer on the canonical question at https://stackoverflow.com/questions/6159313/ instead (the exact requirements are somewhat vague; it can easily be argued that this "tests the membership of multiple values at once). I don't see any mention of `.isdisjoint` in the answers, and it's worth showing how it's a counterpart to `.issubset`. The current question, however, is specifically about how to make it work with syntax as similar as possible to the non-working version. Otherwise it would just be a duplicate, as far as I can figure out. – Karl Knechtel Jul 06 '22 at 09:36
  • @KarlKnechtel That question seems to be primarily concerned with the *all* case, whereas this one seems to want *any*. – LeopardShark Jul 06 '22 at 09:41
  • There are answers there covering both, and OP didn't specify. Such is the pain of seeking canonicals on Stack Overflow. – Karl Knechtel Jul 06 '22 at 09:45