开发者

Case insensitive 'in'

I love using the expression

if 'MICHAEL89' in USERNAMES:
    ...

where USERNAMES is a list开发者_如何学C.


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.


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.


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


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


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


Here's one way:

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

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


I think you have to write some extra code. For example:

if 'MICHAEL89' in map(lambda name: name.upper(), USERNAMES):
   ...

In this case we are forming a new list with all entries in USERNAMES converted to upper case and then comparing against this new list.

Update

As @viraptor says, it is even better to use a generator instead of map. See @Nathon's answer.


You could do

matcher = re.compile('MICHAEL89', re.IGNORECASE)
filter(matcher.match, USERNAMES) 

Update: played around a bit and am thinking you could get a better short-circuit type approach using

matcher = re.compile('MICHAEL89', re.IGNORECASE)
if any( ifilter( matcher.match, USERNAMES ) ):
    #your code here

The ifilter function is from itertools, one of my favorite modules within Python. It's faster than a generator but only creates the next item of the list when called upon.


To have it in one line, this is what I did:

if any(([True if 'MICHAEL89' in username.upper() else False for username in USERNAMES])):
    print('username exists in list')

I didn't test it time-wise though. I am not sure how fast/efficient it is.


My 5 (wrong) cents

'a' in "".join(['A']).lower()

UPDATE

Ouch, totally agree @jpp, I'll keep as an example of bad practice :(


I needed this for a dictionary instead of list, Jochen solution was the most elegant for that case so I modded it a bit:

class CaseInsensitiveDict(dict):
    ''' requests special dicts are case insensitive when using the in operator,
     this implements a similar behaviour'''
    def __contains__(self, name): # implements `in`
        return name.casefold() in (n.casefold() for n in self.keys())

now you can convert a dictionary like so USERNAMESDICT = CaseInsensitiveDict(USERNAMESDICT) and use if 'MICHAEL89' in USERNAMESDICT:


Example from this tutorial:

list1 = ["Apple", "Lenovo", "HP", "Samsung", "ASUS"]

s = "lenovo"
s_lower = s.lower()

res = s_lower in (string.lower() for string in list1)

print(res)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜