대소 문자를 구분하지 않는 사전


78

내 사전이 대소 문자를 구분하지 않기를 바랍니다.

이 예제 코드가 있습니다.

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

출력 = 색상 변경 연습

동일한 출력을 제공하기 위해 다른 문자열, "practice changing the Color"( Color대문자 로 시작하는 곳 )을 원합니다 .

를 사용하여 소문자로 변환하는 일반적인 방법이 있다고 생각 mydictionary[key.lower()]하지만이를 기존 코드에 가장 잘 통합하는 방법을 모르겠습니다. (어쨌든 이것이 합리적이고 간단한 접근법이라면).


4
PEP-455 참조 : Python 3.5에 표준 라이브러리를 포함 할 예정입니다 ( collections.TransformDict, 변환이 str.casefold유사하거나 유사 하다면 )
Nick T

6
@NickT이 PEP는 거부되었습니다. python.org/dev/peps/pep-0455/#rejection
user1556435

답변:


43

내가 당신을 올바르게 이해하고 대소 문자를 구분하지 않는 방식으로 사전을 키우는 방법을 원한다면 한 가지 방법은 dict를 하위 클래스로 만들고 setter / getter를 오버로드하는 것입니다.

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

1
'in'이라고 부르는 특별한 내장 기능도 없나요?
Omnifarious

26
다음은 오버로딩이 필요할 수있는 메서드의 전체 목록입니다 : setitem , getitem , contains , get, has_key, pop, setdefault 및 update. init 및 fromkeys도 사전이 제대로 초기화되었는지 확인하기 위해 오버로드되어야합니다. 어쩌면 내가 틀렸을 수도 있고 파이썬이 get, hash_key, pop, setdefault, update 및 initgetitem , setitem 측면에서 구현 될 것이라고 약속 하고 오버로드 된 경우 포함 하지만 그렇게 생각하지는 않습니다.
Omnifarious

4
추가 __contains__, get, and has_key내가 :) 그들을 코딩 결국 때문에 대답에
마이클 상인

7
이 솔루션은 .NET Framework의 많은 일반적인 용도에서 작동하지 않으므로 매우 제한적 입니다 dict. 코드에서 사용하지 마십시오. 가장 단순한 용도를 제외하고는 모두 중단됩니다. 분명히 @MichaelMerchant가 누락 된 항목을 추가하려고 시도했지만 조정이 변경 사항을 반증했습니다 (같은 일이 나에게 일어났습니다). 여기에 드롭 인 dict대체물 로 사용할 수있는 새로운 답변을 추가했습니다 .
m000


68

현재 승인 된 대답은 작동하지 않습니다 많은 이 드롭 인으로 사용할 수 없습니다, 그래서 경우 dict교체. 적절한 dict교체 를위한 몇 가지 까다로운 점 :

  • 키와 관련된 모든 메서드 오버로드
  • 문자열이 아닌 키를 올바르게 처리
  • 클래스의 생성자를 적절하게 처리

다음이 훨씬 더 잘 작동합니다.

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)

3
이것은 훌륭하지만 한 가지 사소한 문제가 있습니다. 의 슈퍼 정의는 update입니다 update(self, E=None, **F). 의미 E는 선택 사항입니다. E필수 항목으로 다시 정의했습니다 . 추가 =None하면 완벽 할 것입니다.
Nick Williams

18
파이썬은 쉽다고 그들은 말했다. 파이썬은 재미 있다고 그들은 말했다.
rr- 2015

2
@ rr-. 완전히 공정하게 말하자면 C에서이 일을 상상
미친 물리학

1
Nitpick, 그러나 이것은 유니 코드 정규화에 대한 적절한 지원이 없습니다.
Mad Physicist

6
파이썬 3에서는 추상 유형 basestring이 제거되었습니다. str대체품으로 사용할 수 있습니다.
Jan Schatz

56

기록만을 위해서. Requests 에 대한 굉장한 추진력을 찾았습니다 .

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


10
from requests.structures import CaseInsensitiveDict
JimB

6
작동 할 수도 있지만 필요한 것이 대소 문자를 구분하지 않는 Dict 인 경우 요청을 종속성으로 추가하는 것은 어리석은 일입니다.
산티아고 바 술토

3
@santiagobasulto-(근무에 필요한 시간 / 기한까지) 비율이 무한대가 될 때까지 "어리석은"것입니다
qneill

15

특정 경우에는 대소 문자를 구분하지 않는 조회가 필요했지만 키의 원래 대소 문자를 수정하고 싶지 않았습니다. 예를 들면 :

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

사전에 여전히 원래 키가 있지만 대소 문자를 구분하지 않고 액세스 할 수 있음을 알 수 있습니다. 다음은 간단한 해결책입니다.

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

__hash__ 및 __eq__ 재정의는 사전에서 항목을 가져오고 설정하는 데 필요합니다. 이것은 대소 문자를 구분하지 않고 동일한 경우 사전의 동일한 위치로 해시하는 키를 생성합니다.

이제 제공된 키를 사용하여 CaseInsensitiveKey를 초기화하는 사용자 지정 사전을 만들 수 있습니다.

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)

또는 단순히 사전을 사용할 때 항상 CaseInsensitiveKey 인스턴스를 키로 전달해야합니다.


감사합니다! : (주이 클래스는 당신이 그것을 추가해야 할 필요 그렇게하면 대소 문자를 구분하지 "DICT (반복 가능)"생성자를 구현하지 않습니다)
Joril

2
당신은 사용해야하는 .casefold()대신에 .lower(), 비교를 위해 self.key.casefold() == other.key.casefold(), 할 수 있도록 "ß"하고 "ss"다른 사람의 사이에서, 참으로 동일시 할 수 있습니다.
AJNeufeld

10

string.lower()입력에 사용하고 완전히 소문자 사전을 사용하는 것을 고려 하시겠습니까 ? 약간의 해키 솔루션이지만 작동합니다.


약간 엉망이긴한데 김씨가 쫓던 것과 같은 느낌이 든다.
John Y

이것은 해키가 아닙니다. 사실 이것은 딕셔너리 클래스를 재정의하는 것보다 오류가 덜 발생하는 방법입니다.
Saher Ahwal 2016-10-15

4
a 키를 처음 설정할 때 원래 대소 문자를 유지하지 않으려는 경우 유용합니다.
Daniel Roethlisberger 2016

4

pleasemorebacon (감사합니다!)에 의해 간단하지만 좋은 솔루션을 수정하여 프로토콜을 구성 {'a':1, 'B':2}하고 지원할 수 있도록 약간 더 컴팩트하고 독립적이며 사소한 업데이트로 만들었습니다 __contains__. 마지막으로 CaseInsensitiveDict.Key는 문자열이어야하므로 (그 밖의 것은 대소 문자를 구분할 수 있는지 여부)에서 Key클래스 를 파생하는 것이 좋습니다. str예를 들어 상자 CaseInsensitiveDict에서 json.dumps꺼내서 덤프 할 수 있습니다.

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

다음은 실제 상황을 확인하려는 사용자를위한 기본 테스트 스크립트입니다.

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

1
비교 .casefold().lower()위해 대신를 사용해야 self.casefold() == other.key.casefold()하며 hash(self.casefold())"ß"및 "ss"가 다른 것들 중에서 true와 동일하도록 허용 하려면을 사용해야 합니다.
AJNeufeld 19 년

3

대소 문자를 구분하지 않는 사전이 해결책이고이를 달성하는 방법에 대한 답변이 있지만이 경우에는 더 쉬운 방법이 있습니다. 대소 문자를 구분하지 않는 검색으로 충분합니다.

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

3
내장이 쉽게 처리 할 수있는 경우 정규 표현식 모듈보다 내장 문자열 메소드를 사용하는 것이 훨씬 낫습니다.이 경우에는 가능합니다.
John Y

고마워요. 나는 지금 시간이 부족하므로 빠르고 간단한 솔루션이 나에게 잘 어울립니다. 감사합니다
Kim

@John Y : 이것에 대한 정규식없는 해결책은 무엇일까요? 나는 그것을 보지 않는다.
Jakob Borg

Kim은 이미 언급했습니다. string.lower () 메서드를 사용하세요. 다른 답변도 언급했습니다. 댓글은 코드 게시에 적합하지 않으므로 내 답변을 게시 할 것 같습니다.
John Y

+1이 솔루션은 저에게 가장 효과적이었습니다. 제 경우에는 사전 키의 경우가 중요하고 세트에서 단순히 키를 낮추는 것만으로는 충분하지 않기 때문입니다.
pleasemorebacon

1

하나의 라이너로 dict 키 대소 문자를 구분하지 않는 검색을 수행 할 수 있습니다.

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

이를 함수에 넣을 수 있습니다.


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)


첫 번째 일치 만 반환됩니다.


0

코드에서이 작업을 한 번만 수행하면되는 경우 (따라서 함수를 가리 키지 않음) 문제를 처리하는 가장 간단한 방법은 다음과 같습니다.

lowercase_dict = {key.lower () : original_dict의 (key, value) 값}

나는 여기서 문제의 딕셔너리가 그다지 크지 않다고 가정하고 있습니다. 복제하는 것은 우아하지 않을 수 있지만 크지 않으면 아무것도 해치지 않을 것입니다.

@Fred의 대답에 비해 (그것도 작동하지만) 장점은 키가 없을 때 dict와 동일한 결과 인 KeyError를 생성한다는 것입니다.


-1

이 작업을 처리하는 함수를 설정했습니다.

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

myDict = {}

그래서 대신

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

다음을 수행 할 수 있습니다.

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

그런 다음 값을 찾기 전에 소문자로 입력하거나이를 수행하는 함수를 작성할 수 있습니다.

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

    myVal = lookupLCdict(myDict, 'a')

이 작업을 전역 적으로 수행하려는 경우 이상적이지 않지만 사용하려는 하위 집합이면 잘 작동합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.