Справа нечутлива "в"


151

Я люблю використовувати вираз

if 'MICHAEL89' in USERNAMES:
    ...

де USERNAMESсписок.


Чи є спосіб зіставити елементи з нечутливістю до регістру чи мені потрібно скористатися спеціальним методом? Просто цікаво, чи не потрібно для цього написати додатковий код.

Відповіді:


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

Як варіант:

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

Або так, ви можете зробити власний метод.


8
if 'CaseFudge'.lower() in [x.lower() for x in list]
Фредлі

44
[...]створює весь список. (name.upper() for name in USERNAMES)створив би лише генератор і одну необхідну рядок за один раз - велика економія пам’яті, якщо ви багато робите цю операцію. (ще більша економія, якщо ви просто створите список невеликих імен користувачів, які ви повторно використовуєте для перевірки кожен раз)
viraptor

2
Віддайте перевагу опусканню всіх клавіш під час складання диктату з міркувань продуктивності.
Райан

1
якщо [x.lower () для x у списку] - це розуміння списку, чи (name.upper () для імені в USERNAMES) розуміння кортежу? Або це має інше ім’я?
отокан

1
@otocan Це вираження генератора.
nmichaels

21

Я б зробив обгортку, щоб ви могли бути неінвазивними. Мінімально, наприклад ...:

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

Тепер if CaseInsensitively('MICHAEL89') in whatever:слід поводитись так, як потрібно (чи праворуч є список, диктант чи набір). (Можливо, для досягнення подібних результатів для включення рядків може знадобитися більше зусиль, уникати попереджень у деяких випадках, пов'язаних unicodeтощо).


3
це не спрацює, якщо спробувати CaseInsensitively ('MICHAEL89') у {'Michael89': Правда}: друк "знайдено"
Xavier Combelle

2
Ксав'є: Вам знадобиться CaseInsensitively('MICHAEL89') in {CaseInsensitively('Michael89'):True}це для роботи, яка, ймовірно, не підпадає під "поводитись так, як потрібно".
Гейб

Стільки, що існує лише один очевидний спосіб зробити це. Це важко, якщо його не використовуватимуть багато. Це було дуже гладко.
nmichaels

2
@Nathon, мені здається, що інвазивно змінювати контейнер - це "відчуває важку" операцію. Повністю неінвазивна обгортка: наскільки "легшою", ніж ця, можна отримати ?! Не багато;-). @Xavier, RHS, які є диктами або наборами зі змішаними регістровими ключами / елементами, потребують власних неінвазивних обгортків (частина коротких etc.та «вимагає більше зусиль» моєї відповіді ;-).
Алекс Мартеллі

Моє визначення важкого стосується написання зовсім небагато коду, щоб зробити те, що буде використано лише один раз, де буде менш надійною, але набагато коротшою версією. Якщо це буде використовуватися не один раз, це цілком розумно.
nmichaels

12

Зазвичай (принаймні, ооп) ви формуєте свій предмет, щоб він поводився так, як вам хочеться. name in USERNAMESне є чутливим до регістру, тому USERNAMESпотрібно змінити:

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

Чудова річ у тому, що це відкриває шлях для багатьох вдосконалень, не змінюючи жодного коду поза класом. Наприклад, ви можете змінити self.namesнабір для швидшого пошуку, або обчислити (n.lower() for n in self.names)єдиний раз і зберегти його у класі тощо.


10

str.casefoldрекомендується для невідповідності рядків рядків. Рішення @ nmichaels можна тривіально адаптувати.

Використовуйте або:

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

Або:

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

Відповідно до документів :

Складання корпусів подібне до нижнього корпусу, але більш агресивне, оскільки воно призначене для видалення всіх відмінків у рядку. Наприклад, німецька мала літера "ß" еквівалентна "ss". Оскільки це вже малі регістри, lower()нічого не зробить для 'Я'; casefold() перетворює його в "ss".


8

Ось один із способів:

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

Щоб це працювало, string1і string2об'єкти, і об'єкти повинні мати тип string.


5
AttributeError: об’єкт 'list' не має атрибута 'нижній'
Jeff

@Jeff це тому, що один із ваших елементів - це список, і обидва об’єкти повинні бути рядком. Який об’єкт є списком?
Користувач

1
Я б голосував за вас, але я не можу, якщо ви не редагуєте свою відповідь. Ти абсолютно правий.
Джефф

@Jeff Я додав роз'яснення.
Користувач

6

Я думаю, що вам доведеться написати додатковий код. Наприклад:

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

У цьому випадку ми формуємо новий список з усіма записами в USERNAMES перетвореними у верхній регістр, а потім порівнюємо з цим новим списком.

Оновлення

Як говорить @viraptor , навіть краще використовувати генератор замість map. Див @Nathon «s відповідь .


Або ви можете використовувати itertoolsфункцію imap. Це набагато швидше, ніж генератор, але досягає тієї ж мети.
пшениці

5

Ви могли б зробити

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

Оновлення: трохи пограли, і я думаю, що ви можете отримати кращий підхід до короткого замикання з використанням

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

ifilterФункція від itertools, один з моїх улюблених модулів в Python. Це швидше, ніж генератор, але створює лише наступний пункт списку, коли його викликають.


Для додання, можливо, потрібно буде уникнути шаблону, оскільки він може містити символи на зразок ".", "?", Які мають значення specail у звичайних шаблонах виразів. використовуйте для цього re.escape (raw_string)
Ічінг Чанг

0

Мої 5 (неправильних) центів

'a' in "" .join (['A']). нижній ()

ОНОВЛЕННЯ

Так, повністю згоден @jpp, я буду прикладом поганої практики :(


2
Це неправильно. Розгляньте 'a' in "".join(['AB']).lower()прибутки, Trueколи це не те, чого хоче ОП.
jpp

0

Мені це знадобилося для словника замість списку, рішення Йохана було найелегантнішим для цього випадку, тому я трохи його модифікував:

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

Тепер ви можете конвертувати словник як так USERNAMESDICT = CaseInsensitiveDict(USERNAMESDICT)і використанняif 'MICHAEL89' in USERNAMESDICT:


0

Щоб це було в одному рядку, ось що я зробив:

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

Я не перевіряв це часом, хоча. Я не впевнений, наскільки це швидко / ефективно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.