Вопрос

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?
Это было полезно?

Решение

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

Другие советы

In the code that returns an AttributeError, it's because if i is none, it goes on to the second part of the or statement and checks i.isupper(), but None doesn't have an isupper() method, hence the error.

May I suggest a different approach?

items = [None,'foo',None,'FOO',None,'bar']

def splitItems(items, test):
    trueItems = []
    falseItems = []
    for item in items:
        if test(item):
            trueItems.append(item)
        else:
            falseItems.append(item)
    return trueItems, falseItems

trueItems, falseItems = splitItems(items, lambda i:i is None or i.isupper())
print "uppercase or None:", trueItems
print "lowercase:", falseItems
# uppercase or None: [None, None, 'FOO', None]
# lowercase: ['foo', 'bar']

I like @Brionius' edited answer, here is an even more general form of it, returning a dict of items from a list, keyed by the value returned by a discriminating function (could return more than just True or False).

from collections import defaultdict

def groupByEval(seq, fn):
    ret = defaultdict(list)
    for item in seq:
        ret[fn(item)].append(item)
    return dict(ret.iteritems())


test = [None,'foo',None,'FOO',None,'bar']
print groupByEval(test, lambda x: x is not None)
print groupByEval(test, lambda x: 0 if x is None else len(x))
print groupByEval(test, lambda x: x is not None and x.isupper())
print groupByEval(test, lambda x: x if x is None else sum(x.lower().count(c) for c in 'aeiou'))

Giving:

{False: [None, None, None], True: ['foo', 'FOO', 'bar']}
{0: [None, None, None], 3: ['foo', 'FOO', 'bar']}
{False: [None, 'foo', None, None, 'bar'], True: ['FOO']}
{1: ['bar'], 2: ['foo', 'FOO'], None: [None, None, None]}

Brionius' solution could then be implemented as:

def splitItems(seq, condition):
    ret = groupByEval(seq, condition)
    return ret.get(True,[]), ret.get(False,[])
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top