Search code examples
pythoniterationconditional-statementsternary

Confusing ternary in - Python


Assuming that my list has only str and None, and I want to check assign lowered strings into another variable but if it's uppered or None, it should be rendered as None.

The code to checking for None and str.isupper() works:

for i in [None,'foo',None,'FOO',None,'bar']:
  x = None if i is None or i.isupper() else i
  print x

but the negated condition didn't work:

for i in [None,'foo',None,'FOO',None,'bar']:
  x = i if i is not None or i.isupper() else None
  print x

It returns AttributeError:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'NoneType' object has no attribute 'isupper'
  • Why is that so?
  • Other than the ternary assignment in a loop, is there another way to perform the same operation?

Solution

  • You are testing if i is not None or i.isupper(). This doesn't work when i is None:

    (i is not None) or (i.isupper())
    

    is evaluated lazily; first the left is evaluated, and only if False is the second argument evaluated. The first is only False if i is None, so the right-hand expression is only ever evaluated as None.isupper().

    Use and instead, and negate the isupper() test:

    x = i if i is not None and not i.isupper() else None
    

    The above expression is the proper logical inversion of i is None or i.isupper().

    Alternatively use not (..) around your original expression:

    x = i if not (i is None or i.isupper()) else None
    

    Because you are using a regular for loop, there is no need to use a conditional expression here; the following, assigning to the existing loop variable i, suffices:

    for i in [None,'foo',None,'FOO',None,'bar']:
          if i and i.isupper():
              i = None
          print i