0

I have a certain string k that may or many not a key of a dict reversedmapping, and another dict spendDict where it may also be a key.

Why does the following check succeed (i.e. run to completion), for a value of k that is not a key of reversedmapping:

if (k not in reversedmapping) or (reversedmapping[k] not in spendDict)

whereas I get a KeyError (for k) when I change it to logical AND:

if (k not in reversedmapping) and (reversedmapping[k] not in spendDict):

And how do I rewrite the AND variant to avoid the KeyError?

Pyderman
  • 14,809
  • 13
  • 61
  • 106
  • Of course, I want both checks to succeed, but I'm surprised that a `KeyError` isn't thrown for *both*. – Pyderman Jul 10 '15 at 01:33

2 Answers2

4

If (k not in reversedmapping) is True, then

(k not in reversedmapping) or (reversedmapping[k] not in spendDict)

short-circuits, which means it returns True without evaluating

(reversedmapping[k] not in spendDict)

Since True or anything is True.

In contrast,

 (k not in reversedmapping) and (reversedmapping[k] not in spendDict)

must evaluate both parts before Python can determine that the total expression is True.


If (k not in reversedmapping) is True, then reversedmapping[k] will raise KeyError.

If (k not in reversedmapping) is False, then the expression short-circuits and returns False since False and anything is False.

So

(k not in reversedmapping) and (reversedmapping[k] not in spendDict)

will always either return False or raise a KeyError. There is no situation under which it could return True. Both conditions can not be True.

Community
  • 1
  • 1
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • I see. Is the only way to handle this then to flip this around and put a try/except around `reversedmapping[k] in spendDict`, and if I get a KeyError, pass `True` to the rest of the expression? – Pyderman Jul 10 '15 at 01:40
  • Uh, I'd just leave out the `and (reversedmapping[k] not in spendDict)`. If k in reversed mapping, the statement is already false. If k not in reversed mapping, then clearly reversedmapping[k] is not in spendDict, given that it doesn't exist, so the whole statement is true. Therefore, there is no need to check the second half of the statement at all. @Pyderman – NightShadeQueen Jul 10 '15 at 01:45
  • @Pyderman, `(k not in reversedmapping) and (reversedmapping[k] not in spendDict)` is always either `False` or a `KeyError`. There is no case when this expression could by `True`. When do you want it to be `True`? – unutbu Jul 10 '15 at 01:47
  • So some context: most (but not all) of the `keys` of `spendDict` will actually be the `values` of `reversedmapping` (and vice-versa). See here http://stackoverflow.com/questions/31260579/how-to-check-for-presence-of-the-key-of-a-value-as-defined-in-one-dict-in-anot for more context. Both checks are required. – Pyderman Jul 10 '15 at 01:52
  • @NightShadeQueen both checks are required (see one comment up). – Pyderman Jul 10 '15 at 01:54
2

Python's and and or are short-circuit operators. That means they only evaluate both parts of the expression when the answer isn't clear from the left hand operand. For and, if the first operand evaluates to False, then there's no way the result can be true, so the second operand isn't evaluated. For or, it's the opposite - if the first operand evaluates to True, the result is True no matter what the second operand is.

In your case, if k isn't in reversedmapping, or sees a true value on its left hand side and returns without evaluating the right hand sids, so no KeyError. If you use an and and the first test is true (k is not in reversedmapping), it's still forced to evaluate the second operand to see if they're both true, so it tries to pull k from the dict and throws an exception.

Troy
  • 1,599
  • 14
  • 28
  • OK. See my reply to @HappyLeapSecond. Is there a better way of writing this than reversing the logic with a try/except? – Pyderman Jul 10 '15 at 01:43
  • I've read your question a few times and I'm not sure I understand why the `or` based check is not sufficient. So you have three cases: 1. k is in reverseMapping, reverseMapping[k] is in spendDict 2. k is in reverseMapping, reverseMapping[k] is NOT in spendDict 3. k is NOT in reverseMapping, reverseMapping[k] does NOT exist The check with the `or` covers all of these cases. (sorry that comments have no line formatting) – Troy Jul 10 '15 at 02:00
  • @ See here for some background, and let's imagine that `reversedmapping` above is the {k,v} for v,k in `mapping`. http://stackoverflow.com/questions/31260579/how-to-check-for-presence-of-the-key-of-a-value-as-defined-in-one-dict-in-anot – Pyderman Jul 10 '15 at 02:03
  • Yes, that is what I assumed. Perhaps it would help if you would write out the cases for which you want the `if` and `else` blocks to be evaluated (e.g. "in these two cases I want the `if` block to be run. In these other cases I want the `else` block run) – Troy Jul 10 '15 at 02:04
  • How about: if `reversedmapping.get(k)` is not `None`, then go check if the returned value is in spendDict? – Pyderman Jul 10 '15 at 02:12
  • So: if reversedmapping.get(k) is not None: if (k not in reversedmapping) and (reversedmapping.get(k) not in spendDict): – Pyderman Jul 10 '15 at 02:18
  • That should read *if reversedmapping.get(k) is None: if (k not in reversedmapping) and (reversedmapping.get(k) not in spendDict): * – Pyderman Jul 10 '15 at 02:36
  • `reversedmapping.get(k) is None` is equivalent to `k not in reversedmapping` unless you have explicitly set `reversedmapping[k] = None`. As I suggested above I think you need to go back and think about what you're really trying to test for here. I still am not sure you have a clear picture of this based on the responses I'm getting. – Troy Jul 10 '15 at 03:22