Huruf kecil 'dalam'

151

Saya suka menggunakan ekspresi

if 'MICHAEL89' in USERNAMES:
    ...

dimana USERNAMESdaftarnya.


Apakah ada cara untuk mencocokkan item dengan ketidakpekaan kasing atau apakah saya perlu menggunakan metode khusus? Hanya ingin tahu apakah ada kebutuhan untuk menulis kode tambahan untuk ini.

RadiantHex
sumber

Jawaban:

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

Kalau tidak:

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

Atau, ya, Anda dapat membuat metode khusus.

nmichaels
sumber
8
if 'CaseFudge'.lower() in [x.lower() for x in list]
fredley
44
[...]membuat seluruh daftar. (name.upper() for name in USERNAMES)hanya akan membuat generator dan satu string yang diperlukan sekaligus - penghematan memori yang besar jika Anda sering melakukan operasi ini. (bahkan lebih banyak penghematan, jika Anda cukup membuat daftar nama pengguna huruf kecil yang Anda gunakan kembali untuk mengeceknya setiap waktu)
viraptor
2
Lebih suka menurunkan semua kunci saat membuat dict, karena alasan kinerja.
Ryan
1
jika [x.lower () untuk x dalam daftar] adalah pemahaman daftar, apakah (name.upper () untuk nama dalam USERNAMES) merupakan pemahaman tuple? Atau apakah ada nama lain?
otocan
1
@otocan Ini ekspresi generator.
nmichaels
21

Saya akan membuat pembungkus sehingga Anda bisa menjadi non-invasif. Minimal, misalnya ...:

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

Sekarang, if CaseInsensitively('MICHAEL89') in whatever:harus berperilaku seperti yang diminta (apakah sisi kanan adalah daftar, dikt, atau ditetapkan). (Mungkin perlu lebih banyak upaya untuk mencapai hasil serupa untuk penyertaan string, menghindari peringatan dalam beberapa kasus yang melibatkan unicode, dll).

Alex Martelli
sumber
3
itu tidak berfungsi untuk coba dict jika CaseInsensitive ('MICHAEL89') di {'Michael89': True}: print "found"
Xavier Combelle
2
Xavier: Anda perlu CaseInsensitively('MICHAEL89') in {CaseInsensitively('Michael89'):True}melakukannya, yang mungkin tidak termasuk "berperilaku seperti yang diminta".
Gabe
Begitu banyak untuk hanya ada 1 cara yang jelas untuk melakukannya. Ini terasa berat kecuali itu akan banyak digunakan. Yang mengatakan, itu sangat lancar.
nmichaels
2
@Nathon, bagi saya tampaknya harus secara invasif mengubah wadah adalah operasi "terasa berat". Pembungkus yang sepenuhnya non-invasif: berapa banyak "lebih ringan" dari yang bisa didapat ?! Tidak banyak;-). @Xavier, RHS yang merupakan dikte atau set dengan kunci case campuran / item membutuhkan pembungkus non-invasif mereka sendiri (bagian dari bagian pendek etc.dan "memerlukan lebih banyak usaha" dari jawaban saya ;-).
Alex Martelli
Definisi berat saya adalah menulis sedikit kode untuk membuat sesuatu yang hanya akan digunakan sekali, di mana versi yang kurang kuat tetapi jauh lebih pendek akan dilakukan. Jika ini akan digunakan lebih dari sekali, itu masuk akal.
nmichaels
12

Biasanya (setidaknya dalam oop) Anda membentuk objek Anda untuk berperilaku seperti yang Anda inginkan. name in USERNAMEStidak peka huruf besar kecil, jadi USERNAMESperlu diubah:

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

Hal yang hebat tentang ini adalah ia membuka jalan untuk banyak perbaikan, tanpa harus mengubah kode apa pun di luar kelas. Misalnya, Anda bisa mengubah self.nameske satu set untuk pencarian yang lebih cepat, atau (n.lower() for n in self.names)hanya menghitung sekali dan menyimpannya di kelas dan seterusnya ...

Jochen Ritzel
sumber
10

str.casefolddirekomendasikan untuk pencocokan string yang case-insensitive. @ solusi nmichaels dapat dengan mudah diadaptasi.

Gunakan salah satu dari:

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

Atau:

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

Sesuai dokumen :

Casefolding mirip dengan huruf kecil tetapi lebih agresif karena dimaksudkan untuk menghapus semua perbedaan huruf dalam string. Misalnya, huruf kecil Jerman 'ß' setara dengan "ss". Karena sudah huruf kecil, lower()akan melakukan apa pun untuk 'ß'; casefold() mengubahnya menjadi "ss".

jpp
sumber
8

Ini salah satu caranya:

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

Agar ini berfungsi, keduanya string1dan string2objek harus bertipe string.

Pengguna
sumber
5
AttributeError: objek 'daftar' tidak memiliki atribut 'rendah'
Jeff
@ Jeff, itu karena salah satu elemen Anda adalah daftar, dan kedua objek harus berupa string. Objek mana yang merupakan daftar?
Pengguna
1
Saya akan memilih Anda, tetapi saya tidak bisa kecuali Anda mengedit jawaban Anda. Anda memang benar.
Jeff
@ Jeff saya menambahkan klarifikasi.
Pengguna
6

Saya pikir Anda harus menulis beberapa kode tambahan. Sebagai contoh:

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

Dalam hal ini kami membuat daftar baru dengan semua entri masuk USERNAMES dikonversi menjadi huruf besar dan kemudian membandingkannya dengan daftar baru ini.

Memperbarui

Seperti yang dikatakan @viraptor , bahkan lebih baik menggunakan generator map. Lihat @Nathon 's jawaban .

Manoj Govindan
sumber
Atau Anda bisa menggunakan itertoolsfungsi imap. Ini jauh lebih cepat daripada generator tetapi mencapai tujuan yang sama.
wheaties
5

Anda bisa melakukannya

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

Pembaruan: bermain-main sedikit dan saya pikir Anda bisa mendapatkan pendekatan tipe arus pendek yang lebih baik

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

The ifilterfungsi dari itertools, salah satu modul favorit saya dalam Python. Ini lebih cepat daripada generator tetapi hanya membuat item berikutnya dari daftar ketika dipanggil.

wheaties
sumber
Hanya untuk menambahkan, polanya mungkin perlu diloloskan, karena mungkin mengandung karakter seperti ".", "?", Yang memiliki arti tertentu dalam pola ekspresi reguler. gunakan re.escape (raw_string) untuk melakukannya
Iching Chang
0

5 sen saya (salah)

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

MEMPERBARUI

Aduh, setuju sepenuhnya @jpp, saya akan tetap sebagai contoh praktik buruk :(

GBrian
sumber
2
Ini salah. Pertimbangkan 'a' in "".join(['AB']).lower()pengembalian Trueketika ini bukan yang diinginkan OP.
jpp
0

Saya membutuhkan ini untuk kamus alih-alih daftar, solusi Jochen adalah yang paling elegan untuk kasus itu jadi saya modded sedikit:

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

sekarang Anda dapat mengonversi kamus seperti itu USERNAMESDICT = CaseInsensitiveDict(USERNAMESDICT)dan menggunakannyaif 'MICHAEL89' in USERNAMESDICT:

Megarushing
sumber
0

Untuk memilikinya dalam satu baris, inilah yang saya lakukan:

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

Saya tidak mengujinya secara waktu. Saya tidak yakin seberapa cepat / efisien itu.

MFA
sumber