Wörterbuch ohne Berücksichtigung der Groß- und Kleinschreibung

78

Ich möchte, dass in meinem Wörterbuch die Groß- und Kleinschreibung nicht berücksichtigt wird.

Ich habe diesen Beispielcode:

text = "practice changing the color"

words = {'color': 'colour',
        'practice': 'practise'}

def replace(words,text):

    keys = words.keys()

    for i in keys:
        text= text.replace(i ,words[i])
    return  text

text = replace(words,text)

print text

Ausgabe = üben, die Farbe zu ändern

Ich möchte, dass eine andere Zeichenfolge "practice changing the Color"(wobei Colormit einem Großbuchstaben beginnt) dieselbe Ausgabe liefert.

Ich glaube, es gibt eine allgemeine Möglichkeit, mit Kleinbuchstaben zu konvertieren, mydictionary[key.lower()]aber ich bin mir nicht sicher, wie ich dies am besten in meinen vorhandenen Code integrieren kann. (Wenn dies sowieso ein vernünftiger, einfacher Ansatz wäre).

Kim
quelle
4
Siehe PEP-455 : Dies ist für die Aufnahme der Standardbibliothek in Python 3.5 geplant ( collections.TransformDictvorausgesetzt, die Transformation ist str.casefoldähnlich oder ähnlich)
Nick T
6
@NickT Dieser PEP wurde abgelehnt. python.org/dev/peps/pep-0455/#rejection
user1556435

Antworten:

43

Wenn ich Sie richtig verstehe und Sie eine Möglichkeit suchen, Wörterbücher ohne Berücksichtigung der Groß- und Kleinschreibung zu verwenden, besteht eine Möglichkeit darin, den Setter / Getter in Unterklassen zu diktieren und zu überladen:

class CaseInsensitiveDict(dict):
    def __setitem__(self, key, value):
        super(CaseInsensitiveDict, self).__setitem__(key.lower(), value)

    def __getitem__(self, key):
        return super(CaseInsensitiveDict, self).__getitem__(key.lower())
jkp
quelle
1
Gibt es nicht ein spezielles eingebautes Gerät, das auch "in" genannt wird?
Omnifarious
26
Hier ist eine vollständige Liste der Methoden, die möglicherweise überladen werden müssen: setitem , getitem , enthält , get, has_key, pop, setdefault und update. init und fromkeys sollten möglicherweise auch überladen werden, um sicherzustellen, dass das Wörterbuch ordnungsgemäß initialisiert wird. Vielleicht irre ich mich und irgendwo verspricht Python, dass get, hash_key, pop, setdefault, update und init in Bezug auf getitem , setitem und enthält implementiert werden , wenn sie überladen wurden, aber ich denke nicht.
Omnifarious
4
__contains__, get, and has_keyzur Antwort hinzugefügt , seit ich sie codiert habe :)
Michael Merchant
7
Diese Lösung ist sehr begrenzt, da sie für viele gängige Anwendungen von nicht funktioniert dict. Verwenden Sie es nicht in Ihrem Code - es bricht alle außer den einfachsten Verwendungen. Anscheinend hat @MichaelMerchant versucht, das fehlende Material hinzuzufügen, aber die Moderation hat die Änderungen abgelehnt (dasselbe ist mir passiert). Ich habe eine neue Antwort hinzugefügt, die hier als dictErsatz verwendet werden sollte .
m000
2
Besser UserDictals Unterklassen als dict docs.python.org/3.5/library/collections.html#userdict-objects
rite2hhh
68

Die derzeit genehmigte Antwort funktioniert in vielen Fällen nicht und kann daher nicht als dictErsatz verwendet werden. Einige knifflige Punkte bei der Suche nach einem geeigneten dictErsatz:

  • Überladen aller Methoden, die Schlüssel beinhalten
  • ordnungsgemäßer Umgang mit Nicht-String-Schlüsseln
  • ordnungsgemäßer Umgang mit dem Konstruktor der Klasse

Folgendes sollte viel besser funktionieren:

class CaseInsensitiveDict(dict):
    @classmethod
    def _k(cls, key):
        return key.lower() if isinstance(key, basestring) else key

    def __init__(self, *args, **kwargs):
        super(CaseInsensitiveDict, self).__init__(*args, **kwargs)
        self._convert_keys()
    def __getitem__(self, key):
        return super(CaseInsensitiveDict, self).__getitem__(self.__class__._k(key))
    def __setitem__(self, key, value):
        super(CaseInsensitiveDict, self).__setitem__(self.__class__._k(key), value)
    def __delitem__(self, key):
        return super(CaseInsensitiveDict, self).__delitem__(self.__class__._k(key))
    def __contains__(self, key):
        return super(CaseInsensitiveDict, self).__contains__(self.__class__._k(key))
    def has_key(self, key):
        return super(CaseInsensitiveDict, self).has_key(self.__class__._k(key))
    def pop(self, key, *args, **kwargs):
        return super(CaseInsensitiveDict, self).pop(self.__class__._k(key), *args, **kwargs)
    def get(self, key, *args, **kwargs):
        return super(CaseInsensitiveDict, self).get(self.__class__._k(key), *args, **kwargs)
    def setdefault(self, key, *args, **kwargs):
        return super(CaseInsensitiveDict, self).setdefault(self.__class__._k(key), *args, **kwargs)
    def update(self, E={}, **F):
        super(CaseInsensitiveDict, self).update(self.__class__(E))
        super(CaseInsensitiveDict, self).update(self.__class__(**F))
    def _convert_keys(self):
        for k in list(self.keys()):
            v = super(CaseInsensitiveDict, self).pop(k)
            self.__setitem__(k, v)
m000
quelle
3
Das ist großartig, aber es gibt ein kleines Problem. Die Superdefinition von updateist update(self, E=None, **F), was bedeutet, Eist optional. Sie haben es neu definiert, um es Eerforderlich zu machen . Fügen Sie in =Noneund das wird perfekt sein.
Nick Williams
18
Python ist einfach, sagten sie. Python macht Spaß, sagten sie.
rr-
2
@ rr-. Um ganz fair zu sein, stellen Sie sich vor, Sie tun dies in C.
Mad Physicist
1
Nitpick, aber dies unterstützt die Unicode-Normalisierung nicht richtig.
Mad Physicist
6
In Python 3 wurde der abstrakte Typ basestringentfernt. strkann als Ersatz verwendet werden.
Jan Schatz
55

Nur für das Protokoll. Ich habe eine großartige Umsetzung bei Anfragen gefunden :

https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37

Santiagobasulto
quelle
10
from requests.structures import CaseInsensitiveDict
JimB
6
Das mag funktionieren, aber wenn Sie nur ein Diktat ohne Berücksichtigung der Groß- und Kleinschreibung benötigen, ist es dumm, Anforderungen nur dafür als Abhängigkeit hinzuzufügen.
Santiagobasulto
2
@santiagobasulto - es ist "albern", bis das Verhältnis (Need-it-to-Work / Time-to-Deadline) auf unendlich geht
qneill
15

In meinem speziellen Fall benötigte ich eine Suche ohne Berücksichtigung der Groß- und Kleinschreibung, wollte jedoch den ursprünglichen Fall des Schlüssels nicht ändern. Zum Beispiel:

>>> d = {}
>>> d['MyConfig'] = 'value'
>>> d['myconfig'] = 'new_value'
>>> d
{'MyConfig': 'new_value'}

Sie können sehen, dass das Wörterbuch immer noch den Originalschlüssel enthält, jedoch ohne Berücksichtigung der Groß- und Kleinschreibung. Hier ist eine einfache Lösung:

class CaseInsensitiveKey(object):
    def __init__(self, key):
        self.key = key
    def __hash__(self):
        return hash(self.key.lower())
    def __eq__(self, other):
        return self.key.lower() == other.key.lower()
    def __str__(self):
        return self.key

Die Überschreibungen __hash__ und __eq__ sind erforderlich, um Einträge im Wörterbuch abzurufen und festzulegen. Dadurch werden Schlüssel erstellt, die an derselben Position im Wörterbuch gehasht werden, wenn die Groß- und Kleinschreibung nicht berücksichtigt wird.

Erstellen Sie nun entweder ein benutzerdefiniertes Wörterbuch, das einen CaseInsensitiveKey mit dem angegebenen Schlüssel initialisiert:

class CaseInsensitiveDict(dict):
    def __setitem__(self, key, value):
        key = CaseInsensitiveKey(key)
        super(CaseInsensitiveDict, self).__setitem__(key, value)
    def __getitem__(self, key):
        key = CaseInsensitiveKey(key)
        return super(CaseInsensitiveDict, self).__getitem__(key)

oder stellen Sie einfach sicher, dass Sie bei Verwendung des Wörterbuchs immer eine Instanz von CaseInsensitiveKey als Schlüssel übergeben.

Pleasemorebacon
quelle
Nett, danke! :) (Beachten Sie, dass diese Klasse den Konstruktor "dict (iterable)" ohne
Berücksichtigung
2
Sie sollten .casefold()statt .lower()für Vergleiche verwenden, self.key.casefold() == other.key.casefold()um unter anderem zuzulassen "ß"und "ss"als wahr gleichzusetzen.
AJNeufeld
10

Würden Sie in Betracht ziehen, string.lower()Ihre Eingaben zu verwenden und ein vollständig klein geschriebenes Wörterbuch zu verwenden? Es ist ein bisschen eine hackige Lösung, aber es funktioniert

inspectorG4dget
quelle
Es ist ein bisschen hackig, aber ich denke, es entspricht dem, was Kim wollte.
John Y
Das ist nicht hacky. Tatsächlich ist dies die weniger fehleranfällige Methode als das Überschreiben der Wörterbuchklasse.
Saher Ahwal
4
Dies ist großartig, es sei denn, Sie möchten den ursprünglichen Fall beibehalten, wenn Sie den a-Schlüssel zum ersten Mal setzen.
Daniel Roethlisberger
4

Ich habe die einfache, aber gute Lösung durch Pleasemorebacon (danke!) Modifiziert , um sie etwas kompakter, eigenständiger und mit geringfügigen Aktualisierungen zu gestalten, damit das Protokoll erstellt {'a':1, 'B':2}und unterstützt werden __contains__kann. Schließlich, da die CaseInsensitiveDict.Keyauf sein String erwartet (was sonst Fall empfindlich sein kann oder nicht), ist es eine gute Idee , herzuleiten KeyKlasse aus dem str, so ist es möglich, zum Beispiel, Dump CaseInsensitiveDictmit json.dumpsaus dem Kasten heraus .

# caseinsensitivedict.py
class CaseInsensitiveDict(dict):

    class Key(str):
        def __init__(self, key):
            str.__init__(key)
        def __hash__(self):
            return hash(self.lower())
        def __eq__(self, other):
            return self.lower() == other.lower()

    def __init__(self, data=None):
        super(CaseInsensitiveDict, self).__init__()
        if data is None:
            data = {}
        for key, val in data.items():
            self[key] = val
    def __contains__(self, key):
        key = self.Key(key)
        return super(CaseInsensitiveDict, self).__contains__(key)
    def __setitem__(self, key, value):
        key = self.Key(key)
        super(CaseInsensitiveDict, self).__setitem__(key, value)
    def __getitem__(self, key):
        key = self.Key(key)
        return super(CaseInsensitiveDict, self).__getitem__(key)

Hier ist ein grundlegendes Testskript für diejenigen, die Dinge in Aktion überprüfen möchten:

# test_CaseInsensitiveDict.py
import json
import unittest
from caseinsensitivedict import *

class Key(unittest.TestCase):
    def setUp(self):
        self.Key = CaseInsensitiveDict.Key
        self.lower = self.Key('a')
        self.upper = self.Key('A')

    def test_eq(self):
        self.assertEqual(self.lower, self.upper)

    def test_hash(self):
        self.assertEqual(hash(self.lower), hash(self.upper))

    def test_str(self):
        self.assertEqual(str(self.lower), 'a')
        self.assertEqual(str(self.upper), 'A')

class Dict(unittest.TestCase):
    def setUp(self):
        self.Dict = CaseInsensitiveDict
        self.d1 = self.Dict()
        self.d2 = self.Dict()
        self.d1['a'] = 1
        self.d1['B'] = 2
        self.d2['A'] = 1
        self.d2['b'] = 2

    def test_contains(self):
        self.assertIn('B', self.d1)
        d = self.Dict({'a':1, 'B':2})
        self.assertIn('b', d)

    def test_init(self):
        d = self.Dict()
        self.assertFalse(d)
        d = self.Dict({'a':1, 'B':2})
        self.assertTrue(d)

    def test_items(self):
        self.assertDictEqual(self.d1, self.d2)
        self.assertEqual(
            [v for v in self.d1.items()],
            [v for v in self.d2.items()])

    def test_json_dumps(self):
        s = json.dumps(self.d1)
        self.assertIn('a', s)
        self.assertIn('B', s)

    def test_keys(self):
        self.assertEqual(self.d1.keys(), self.d2.keys())

    def test_values(self):
        self.assertEqual(
            [v for v in self.d1.values()],
            [v for v in self.d2.values()])
Mloskot
quelle
1
Sie sollten .casefold()statt .lower()für Vergleiche verwenden self.casefold() == other.key.casefold()und hash(self.casefold())unter anderem "ß" und "ss" als wahr gleichsetzen.
AJNeufeld
3

Während ein Wörterbuch ohne Berücksichtigung der Groß- und Kleinschreibung eine Lösung darstellt und es Antworten gibt, wie dies erreicht werden kann, gibt es in diesem Fall möglicherweise einen einfacheren Weg. Eine Suche ohne Berücksichtigung der Groß- und Kleinschreibung ist ausreichend:

import re

text = "Practice changing the Color"
words = {'color': 'colour', 'practice': 'practise'}

def replace(words,text):
        keys = words.keys()
        for i in keys:
                exp = re.compile(i, re.I)
                text = re.sub(exp, words[i], text)
        return text

text = replace(words,text)
print text
Jakob Borg
quelle
3
Es ist weitaus besser, die integrierten Zeichenfolgenmethoden als das Modul für reguläre Ausdrücke zu verwenden, wenn die integrierten Funktionen problemlos damit umgehen können, was in diesem Fall der Fall ist.
John Y
danke ruhig. Ich habe momentan wenig Zeit, daher passt Ihre schnelle und einfache Lösung gut zu mir. danke
Kim
@ John Y: Was wäre die Regexp-freie Lösung dafür? Ich sehe es nicht
Jakob Borg
Kim hat es bereits erwähnt: Verwenden Sie die Methode string.lower (). Andere Antworten erwähnten es auch. Kommentare sind nicht gut für den Posting-Code, daher werde ich wahrscheinlich meine eigene Antwort posten.
John Y
+1 Diese Lösung hat für mich am besten funktioniert, da in meinem Fall der Fall des Wörterbuchschlüssels wichtig ist und es nicht ausreicht, den Schlüssel am Set einfach zu verkleinern.
Pleasemorebacon
0

Sie können mit einem Einzeiler eine Suche ohne Groß- und Kleinschreibung durchführen.

>>> input_dict = {'aBc':1, 'xyZ':2}
>>> search_string = 'ABC'
>>> next((value for key, value in input_dict.items() if key.lower()==search_string.lower()), None)
1
>>> search_string = 'EFG'
>>> next((value for key, value in input_dict.items() if key.lower()==search_string.lower()), None)
>>>

Sie können das in eine Funktion einfügen:


def get_case_insensitive_key_value(input_dict, key):
    return next((value for dict_key, value in input_dict.items() if dict_key.lower() == key.lower()), None)


Beachten Sie, dass nur die erste Übereinstimmung zurückgegeben wird.

Fred
quelle
0

Wenn Sie dies nur einmal in Ihrem Code tun müssen (daher kein Hinweis auf eine Funktion), ist der einfachste Weg, um das Problem zu lösen, folgender:

Kleinbuchstaben_dict = {key.lower (): Wert für (Schlüssel, Wert) in original_dict}

Ich gehe hier davon aus, dass das fragliche Diktat nicht allzu groß ist - es mag unelegant sein, es zu duplizieren, aber wenn es nicht groß ist, wird es nichts schaden.

Der Vorteil gegenüber der Antwort von @ Fred (obwohl das auch funktioniert) ist, dass es das gleiche Ergebnis wie ein Diktat erzeugt, wenn der Schlüssel nicht vorhanden ist: ein KeyError.

MTKnife
quelle
-1

Ich habe gerade eine Funktion eingerichtet, um dies zu handhaben:

def setLCdict(d, k, v):
    k = k.lower()
    d[k] = v
    return d

myDict = {}

Also statt

myDict['A'] = 1
myDict['B'] = 2

Sie können:

myDict = setLCdict(myDict, 'A', 1)
myDict = setLCdict(myDict, 'B', 2)

Sie können den Wert dann entweder vor dem Nachschlagen in Kleinbuchstaben schreiben oder eine entsprechende Funktion schreiben.

    def lookupLCdict(d, k):
        k = k.lower()
        return d[k]

    myVal = lookupLCdict(myDict, 'a')

Wahrscheinlich nicht ideal, wenn Sie dies global tun möchten, aber es funktioniert gut, wenn es nur eine Teilmenge ist, für die Sie es verwenden möchten.

SFox
quelle