파이썬에서 목록을 dict 키로 사용할 수없는 이유는 무엇입니까?


103

나는 파이썬 dict의 키로 사용할 수 있거나 사용할 수없는 것에 대해 약간 혼란 스럽습니다.

dicked = {}
dicked[None] = 'foo'     # None ok
dicked[(1,3)] = 'baz'    # tuple ok
import sys
dicked[sys] = 'bar'      # wow, even a module is ok !
dicked[(1,[3])] = 'qux'  # oops, not allowed

그래서 튜플은 불변 유형이지만 그 안에 목록을 숨기면 키가 될 수 없습니다. 모듈 안에 목록을 쉽게 숨길 수는 없나요?

키가 "해시 가능"해야한다는 막연한 생각이 있었지만 기술적 인 세부 사항에 대한 내 자신의 무지를 인정할 것입니다. 여기서 무슨 일이 일어나는지 모르겠습니다. 메모리 위치와 같은 해시를 사용하여 목록을 키로 사용하려고하면 어떻게 될까요?


1
여기에 좋은 토론이 있습니다 : stackoverflow.com/questions/2671211/…
Hernan

50
변수 이름에서 웃음이 생겼습니다.
kindall

답변:


35

Python 위키의 주제에 대한 좋은 기사가 있습니다. Why Lists Ca n't Be Dictionary Keys . 거기에 설명 된대로 :

메모리 위치와 같은 해시를 사용하여 목록을 키로 사용하려고하면 어떻게 될까요?

실제로 요구 사항을 위반하지 않고 수행 할 수 있지만 예기치 않은 동작이 발생합니다. 목록은 일반적으로 (in-) equality를 확인할 때와 같이 해당 값이 콘텐츠의 값에서 파생 된 것처럼 처리됩니다. 많은 사람들은-당연히-당신이 [1, 2]정확히 같은 목록 객체를 유지해야하는 동일한 키를 얻기 위해 어떤 목록 을 사용할 수 있다고 기대 합니다. 그러나 키로 사용 된 목록이 수정 되 자마자 값별 조회가 중단되고, ID로 조회하려면 정확히 동일한 목록을 유지해야합니다. 다른 일반적인 목록 작업에는 필요하지 않습니다 (적어도 생각할 수있는 항목은 없습니다). ).

모듈과 같은 다른 객체 object는 어쨌든 객체 ID에서 훨씬 더 큰 거래를하고 (마지막으로 sys? 라고 불리는 두 개의 별개의 모듈 객체가 있었을 때 ) 어쨌든 비교됩니다. 따라서 딕셔너리 키로 사용될 때 그 경우에도 ID로 비교된다는 것은 놀랍지 않거나 예상치 못한 일입니다.


32

파이썬에서 목록을 dict 키로 사용할 수없는 이유는 무엇입니까?

>>> d = {repr([1,2,3]): 'value'}
{'[1, 2, 3]': 'value'}

(이 문제를 해결하는 방법을 찾는 모든 사람을 위해)

여기에서 다른 사람들이 설명했듯이 실제로는 할 수 없습니다. 그러나 목록을 실제로 사용하려면 대신 문자열 표현을 사용할 수 있습니다.


6
죄송합니다. 요점을 잘 모르겠습니다. 문자열 리터럴을 키로 사용하는 것과 다르지 않습니다.
WIM

12
진실; 나는 실제로 '키는 해시 가능해야합니다'라는 측면에서 목록을 사용할 수없는 이유를 실제로 설명하는 많은 답변을 보았습니다. ...
Remi

5
목록을 튜플로 변환하지 않는 이유는 무엇입니까? 왜 그것을 문자열로 변환합니까? 튜플을 사용하는 경우 사용자 지정 비교 메서드가있는 클래스에서 올바르게 작동합니다 __eq__. 그러나 그것들을 문자열로 변환하면 모든 것이 문자열 표현으로 비교됩니다.
Aran-Fey

좋은 지적 @ Aran-Fey. 튜플의 모든 요소 자체가 해시 가능한지 확인하십시오. 예를 들어 tuple ([[1,2], [2,3]])을 키로 사용하면 튜플의 요소가 여전히 목록이므로 작동하지 않습니다.
Remi

19

List를 튜플로 변경 한 다음 키로 사용할 수 있습니다.

d = {tuple([1,2,3]): 'value'}

매력처럼 작동했습니다!
Tabz

16

문제는 튜플은 불변이고 목록은 그렇지 않다는 것입니다. 다음을 고려하세요

d = {}
li = [1,2,3]
d[li] = 5
li.append(4)

무엇을 d[li]반환 해야 합니까? 같은 목록입니까? 어때요 d[[1,2,3]]? 값은 동일하지만 다른 목록입니까?

궁극적으로 만족스러운 대답은 없습니다. 예를 들어 작동하는 유일한 키가 원래 키인 경우 해당 키에 대한 참조가 없으면 값에 다시 액세스 할 수 없습니다. 허용 된 다른 모든 키를 사용하여 원본에 대한 참조없이 키를 구성 할 수 있습니다.

내 제안이 모두 작동한다면 동일한 값을 반환하는 매우 다른 키가있는 것입니다. 원본 내용 만 작동하면 목록이 수정되기 때문에 키가 빠르게 손상됩니다.


예, 동일한 목록이므로 d[li]5로 유지 될 것으로 예상 d[[1,2,3]]됩니다. 다른 목록 개체를 키로 참조하므로 KeyError가됩니다. 키에 가비지 수집을 허용하면 일부 dict 값에 액세스 할 수 없게 될 수 있다는 점을 제외하고는 아직 아무런 문제가 없습니다. 그러나 그것은 논리적 문제가 아닌 실제적인 문제입니다.
wim

@wim : d[list(li)]KeyError가되는 것도 문제의 일부입니다. 거의 모든 다른 사용 사례 에서는 li동일한 콘텐츠를 가진 새 목록과 구별 할 수 없습니다. 작동하지만 많은 사람들에게 반 직관적입니다. 마지막 시간을 때 게다가, 당신은 정말 DICT 키로 목록을 사용할 수있다? 신원로있는 거 해싱 모든 어쨌든, 그 경우 당신은 그냥 대신에 의존 그렇게해야 할 때 내가 상상할 수있는 유일한 사용 사례입니다 __hash____eq__ID 기반이 될 수 있습니다.

@delnan 문제는 단순히 그러한 합병증으로 인해 그다지 유용하지 않을 것이라는 점입니까? 아니면 실제로 딕셔너리를 깨뜨릴 수있는 이유가 있습니까?
wim

1
@wim : 후자. 내 답변에서 언급했듯이 dict 키에 대한 요구 사항을 실제로 위반하지는 않지만 해결하는 것보다 더 많은 문제를 일으킬 가능성이 있습니다.

1
@delnan - 당신은 '이전'말을 의미
제이슨

9

다음은 답변입니다. http://wiki.python.org/moin/DictionaryKeys

메모리 위치와 같은 해시를 사용하여 목록을 키로 사용하려고하면 어떻게 될까요?

동일한 내용의 다른 목록을 검색하면 동일한 내용의 목록을 비교하면 동일한 것으로 표시 되더라도 다른 결과가 생성됩니다.

사전 조회에서 목록 리터럴을 사용하는 것은 어떻습니까?


4

목록은 변경 가능하기 때문에 dict키 (및 set멤버)는 해시 가능해야하며 변경 가능한 객체를 해싱하는 것은 해시 값 인스턴스 속성을 기반으로 계산 입니다.

이 답변에서는 기존 답변에 가치를 더하는 구체적인 예를 제공합니다. 모든 통찰력은set 데이터 구조 .

예제 1 : 해시 값이 객체의 변경 가능한 특성을 기반으로하는 변경 가능한 객체를 해싱합니다.

>>> class stupidlist(list):
...     def __hash__(self):
...         return len(self)
... 
>>> stupid = stupidlist([1, 2, 3])
>>> d = {stupid: 0}
>>> stupid.append(4)
>>> stupid
[1, 2, 3, 4]
>>> d
{[1, 2, 3, 4]: 0}
>>> stupid in d
False
>>> stupid in d.keys()
False
>>> stupid in list(d.keys())
True

mutating 후에 stupid는 해시가 변경 되었기 때문에 더 이상 dict에서 찾을 수 없습니다. dict의 키 목록에 대한 선형 스캔 만stupid .

예 2 : ...하지만 왜 상수 해시 값이 아닌가?

>>> class stupidlist2(list):
...     def __hash__(self):
...         return id(self)
... 
>>> stupidA = stupidlist2([1, 2, 3])
>>> stupidB = stupidlist2([1, 2, 3])
>>> 
>>> stupidA == stupidB
True
>>> stupidA in {stupidB: 0}
False

그것은 좋은 생각이 아닙니다. 동일한 객체는 동일하게 해시하여 dict또는set .

예 3 : ... 좋아, 모든 인스턴스에서 상수 해시는 어떻습니까?!

>>> class stupidlist3(list):
...     def __hash__(self):
...         return 1
... 
>>> stupidC = stupidlist3([1, 2, 3])
>>> stupidD = stupidlist3([1, 2, 3])
>>> stupidE = stupidlist3([1, 2, 3, 4])
>>> 
>>> stupidC in {stupidD: 0}
True
>>> stupidC in {stupidE: 0}
False
>>> d = {stupidC: 0}
>>> stupidC.append(5)
>>> stupidC in d
True

예상대로 작동하는 것 같지만 무슨 일이 일어나고 있는지 생각해보십시오. 클래스의 모든 인스턴스가 동일한 해시 값을 생성하면 두 개 이상의 인스턴스가 하나의 키로 존재할 때마다 해시 충돌이 발생합니다. dict 합니다 set.

my_dict[key]또는 key in my_dict(또는 item in my_set)을 사용 하여 올바른 인스턴스를 찾으려면stupidlist3 찾으려면 dict의 키에있는 (최악의 경우) . 이 시점에서 사전의 목적인 O (1) 조회는 완전히 실패합니다. 이것은 다음 타이밍에서 시연됩니다 (IPython으로 수행됨).

예제 3의 몇 가지 타이밍

>>> lists_list = [[i]  for i in range(1000)]
>>> stupidlists_set = {stupidlist3([i]) for i in range(1000)}
>>> tuples_set = {(i,) for i in range(1000)}
>>> l = [999]
>>> s = stupidlist3([999])
>>> t = (999,)
>>> 
>>> %timeit l in lists_list
25.5 µs ± 442 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit s in stupidlists_set
38.5 µs ± 61.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit t in tuples_set
77.6 ns ± 1.5 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

보시다시피, 우리의 멤버십 테스트 stupidlists_set는 전체에 대한 선형 스캔보다 훨씬 느리지 만 lists_list해시 충돌 부하없이 세트에서 예상되는 초고속 조회 시간 (팩터 500)이 있습니다.


TL; DR : 당신은 사용할 수 있습니다 tuple(yourlist)dict튜플 불변 해쉬 때문에, 키.


>>> x = (1,2,3321321321321,) >>> id (x) 139936535758888 >>> z = (1,2,3321321321321,) >>> id (z) 139936535760544 >>> id ((1, 2,3321321321321,)) 139936535810768이 3 개의 튜플 값은 동일하지만 ID는 다릅니다. 그렇다면 키 x가있는 사전은 키 z에 대한 값을 갖지 않을까요?
Ashwani

@Ashwani 당신은 그것을 시도 했습니까?
timgeb

예, 예상대로 작동합니다. 내 의심은 동일한 값을 가진 모든 튜플이 다른 ID를 가지고 있다는 것입니다. 그렇다면이 해시는 어떤 기준으로 계산됩니까?
Ashwani

해쉬 @Ashwani xz동일하다. 불명확 한 점이 있으면 새 질문을여십시오.
timgeb

1
@Ashwani hash(x)hash(z).
timgeb

3

awnser는 여기에서 찾을 수 있습니다.

목록이 사전 키가 될 수없는 이유

Python을 처음 접하는 사람들은 언어에 튜플과 목록 유형이 모두 포함되어 있지만 튜플은 사전 키로 사용할 수있는 반면 목록은 사용할 수없는 이유가 궁금합니다. 이것은 의도적 인 디자인 결정이었고, 파이썬 사전이 어떻게 작동하는지 먼저 이해함으로써 가장 잘 설명 될 수 있습니다.

출처 및 추가 정보 : http://wiki.python.org/moin/DictionaryKeys


1

귀하의 질문에 대한 간단한 대답은 클래스 목록이 사전에서 키로 사용하려는 모든 객체에 필요한 메서드 해시 를 구현하지 않는다는 것 입니다. 그러나 해시 하는 이유 가 동일한 방식으로 구현되지 않는 는 (컨테이너의 내용을 기반으로 한) 튜플 클래스가 목록이 변경 가능하기 때문에 목록을 편집하려면 해시를 다시 계산해야합니다. 이제 기본 해시 테이블 내의 잘못된 버킷에 있습니다. 튜플 (불변)을 수정할 수 없기 때문에이 문제가 발생하지 않습니다.

참고로, dictobjects 조회의 실제 구현은 Knuth Vol.의 알고리즘 D를 기반으로합니다. 3, Sec. 6.4. 그 책을 사용할 수 있다면 읽어 볼 가치가있을 것입니다. 또한 정말로 관심이 있다면 여기에서 dictobject 의 실제 구현 에 대한 개발자 의견을 살펴볼 수 있습니다. 정확히 어떻게 작동하는지 자세히 설명합니다. 여러분이 관심을 가질만한 사전 구현에 대한 파이썬 강의 도 있습니다. 그들은 키의 정의와 처음 몇 분 동안 해시가 무엇인지에 대해 다룹니다.


-1

Python 2.7.2 문서에 따르면 :

객체는 수명 동안 변경되지 않는 해시 값 ( 해시 () 메서드 필요)이있는 경우 해시 가능하고 다른 객체와 비교할 수 있습니다 ( eq () 또는 cmp () 메서드 필요). 동일하게 비교되는 해시 가능한 객체는 동일한 해시 값을 가져야합니다.

해시 가능성은 이러한 데이터 구조가 내부적으로 해시 값을 사용하기 때문에 객체를 사전 키 및 집합 멤버로 사용할 수있게합니다.

파이썬의 모든 불변 내장 객체는 해시 가능하지만 (목록 또는 사전과 같은) 변경 가능한 컨테이너는 없습니다. 사용자 정의 클래스의 인스턴스 인 객체는 기본적으로 해시 할 수 있습니다. 그들은 모두 같지 않은 것을 비교하고 해시 값은 id ()입니다.

튜플은 요소를 추가, 제거 또는 교체 할 수 없다는 점에서 변경 불가능하지만 요소 자체는 변경 가능할 수 있습니다. List의 해시 값은 해당 요소의 해시 값에 따라 다르므로 요소를 변경하면 변경됩니다.

목록 해시에 id를 사용하면 모든 목록이 다르게 비교된다는 것을 의미하므로 놀랍고 불편할 것입니다.


1
그 질문에 대한 답이 아니죠? hash = id첫 번째 단락의 끝에서 불변성을 깨뜨리지 않습니다. 문제는 왜 그렇게되지 않았는지입니다.

@delnan : 명확히하기 위해 마지막 단락을 추가했습니다.
Nicola Musatti 2011 년

-1

Dictionary는 HashMap으로 키 맵, 해시 된 새 키 및 값 매핑으로 변환 된 값을 저장합니다.

(의사 코드) :

{key : val}  
hash(key) = val

사전의 키로 사용할 수있는 사용 가능한 옵션이 무엇인지 궁금하다면. 그때

아무것도 해쉬는 (해시로 변환 할 수 있고, 홀드 정적 값, 즉 불변 위에서 언급 한 바와 같이 해시 키를 만들려면도록) 자격이 있지만, 목록 또는 일련의 객체가 될 수있는 해시 (키) 그렇게해야 또한 필요가 이동 변화 목록이나 세트와 동기화하기 위해 다양합니다.

당신은 시도 할 수 있습니다 :

hash(<your key here>)

잘 작동하면 사전의 키로 사용하거나 해시 가능한 것으로 변환 할 수 있습니다.


간단히 :

  1. 해당 목록을 tuple(<your list>).
  2. 해당 목록을 str(<your list>).

-1

dict키는 해시 가능해야합니다. 목록은 변경 가능하며 유효한 해시 방법을 제공하지 않습니다 .

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