Case insensitive 'in'

156,620

Solution 1

username = 'MICHAEL89'
if username.upper() in (name.upper() for name in USERNAMES):
    ...

Alternatively:

if username.upper() in map(str.upper, USERNAMES):
    ...

Or, yes, you can make a custom method.

Solution 2

str.casefold is recommended for case-insensitive string matching. @nmichaels's solution can trivially be adapted.

Use either:

if 'MICHAEL89'.casefold() in (name.casefold() for name in USERNAMES):

Or:

if 'MICHAEL89'.casefold() in map(str.casefold, USERNAMES):

As per the docs:

Casefolding is similar to lowercasing but more aggressive because it is intended to remove all case distinctions in a string. For example, the German lowercase letter 'ß' is equivalent to "ss". Since it is already lowercase, lower() would do nothing to 'ß'; casefold() converts it to "ss".

Solution 3

I would make a wrapper so you can be non-invasive. Minimally, for example...:

class CaseInsensitively(object):
    def __init__(self, s):
        self.__s = s.lower()
    def __hash__(self):
        return hash(self.__s)
    def __eq__(self, other):
        # ensure proper comparison between instances of this class
        try:
           other = other.__s
        except (TypeError, AttributeError):
          try:
             other = other.lower()
          except:
             pass
        return self.__s == other

Now, if CaseInsensitively('MICHAEL89') in whatever: should behave as required (whether the right-hand side is a list, dict, or set). (It may require more effort to achieve similar results for string inclusion, avoid warnings in some cases involving unicode, etc).

Solution 4

Usually (in oop at least) you shape your object to behave the way you want. name in USERNAMES is not case insensitive, so USERNAMES needs to change:

class NameList(object):
    def __init__(self, names):
        self.names = names

    def __contains__(self, name): # implements `in`
        return name.lower() in (n.lower() for n in self.names)

    def add(self, name):
        self.names.append(name)

# now this works
usernames = NameList(USERNAMES)
print someone in usernames

The great thing about this is that it opens the path for many improvements, without having to change any code outside the class. For example, you could change the self.names to a set for faster lookups, or compute the (n.lower() for n in self.names) only once and store it on the class and so on ...

Solution 5

Here's one way:

if string1.lower() in string2.lower(): 
    ...

For this to work, both string1 and string2 objects must be of type string.

Share:
156,620

Related videos on Youtube

RadiantHex
Author by

RadiantHex

hello! :)

Updated on June 19, 2020

Comments

  • RadiantHex
    RadiantHex almost 4 years

    I love using the expression

    if 'MICHAEL89' in USERNAMES:
        ...
    

    where USERNAMES is a list.


    Is there any way to match items with case insensitivity or do I need to use a custom method? Just wondering if there is a need to write extra code for this.

  • fredley
    fredley over 13 years
    if 'CaseFudge'.lower() in [x.lower() for x in list]
  • viraptor
    viraptor over 13 years
    [...] creates the whole list. (name.upper() for name in USERNAMES) would create only a generator and one needed string at a time - massive memory savings if you're doing this operation a lot. (even more savings, if you simply create a list of lowercase usernames that you reuse for checking every time)
  • wheaties
    wheaties over 13 years
    Or you could use itertools function imap. It's much faster than a generator but accomplishes the same goal.
  • Xavier Combelle
    Xavier Combelle over 13 years
    that doesn't work for dict try if CaseInsensitively('MICHAEL89') in {'Michael89':True}:print "found"
  • Gabe
    Gabe over 13 years
    Xavier: You would need CaseInsensitively('MICHAEL89') in {CaseInsensitively('Michael89'):True} for that to work, which probably doesn't fall under "behave as required".
  • nmichaels
    nmichaels over 13 years
    So much for there being only 1 obvious way to do it. This feels heavy unless it's going to be used a lot. That said, it's very smooth.
  • Alex Martelli
    Alex Martelli over 13 years
    @Nathon, it seems to me that having to invasively alter the container is the "feels heavy" operation. A completely non-invasive wrapper: how much "lighter" than this could one get?! Not much;-). @Xavier, RHS's that are dicts or sets with mixed-case keys/items need their own non-invasive wrappers (part of the short etc. and "require more effort" parts of my answer;-).
  • nmichaels
    nmichaels over 13 years
    My definition of heavy involves writing quite a bit of code to make something that will only be used once, where a less robust but much shorter version would do. If this is going to be used more than once, it's perfectly sensible.
  • Ryan
    Ryan about 11 years
    Prefer to lower all keys when building the dict, for performance reasons.
  • Iching Chang
    Iching Chang over 7 years
    Just to add, the pattern might need to be escaped, since it might contain characters like ".","?", which has specail meaning in regular expression patterns. use re.escape(raw_string) to do it
  • Jeff
    Jeff over 6 years
    AttributeError: 'list' object has no attribute 'lower'
  • otocan
    otocan about 6 years
    if [x.lower() for x in list] is a list comprehension, is (name.upper() for name in USERNAMES) a tuple comprehension? Or does it have another name?
  • nmichaels
    nmichaels about 6 years
    @otocan It's a generator expression.
  • otocan
    otocan about 6 years
    @nmichaels thanks, just wanted to know what to google
  • User
    User over 5 years
    @Jeff that's because one of your elements is a list, and both objects should be a string. Which object is a list?
  • Jeff
    Jeff over 5 years
    I would up vote you, but I cannot unless you edit your answer. You are absolutely right.
  • User
    User over 5 years
    @Jeff I added clarification.
  • jpp
    jpp about 5 years
    This is wrong. Consider 'a' in "".join(['AB']).lower() returns True when this isn't what OP wants.