해시 가능, 불변


81

최근의 SO 질문에서 ( 목록으로 색인되는 파이썬에서 사전 만들기 참조 ) 파이썬에서 해시 가능 및 불변 객체의 의미에 대한 잘못된 개념이 있음을 깨달았습니다.

  • 실제로 해시 가능은 무엇을 의미합니까?
  • 해시 가능과 불변의 관계는 무엇입니까?
  • 해시 할 수있는 변경 가능한 객체 또는 해시 할 수없는 변경 불가능한 객체가 있습니까?

답변:


85

해싱 은 많은 양의 데이터를 반복 가능한 방식으로 훨씬 적은 양 (일반적으로 단일 정수)으로 변환하여 일정한 시간 ( O(1)) 으로 테이블에서 조회 할 수 있도록하는 프로세스입니다 . 이는 고성능에 중요합니다. 알고리즘 및 데이터 구조.

불변성 은 객체가 생성 된 후에 특히 해당 객체의 해시 값을 변경할 수있는 어떤 방식 으로든 중요한 방식으로 변경되지 않는다는 아이디어입니다.

해시 키로 사용되는 객체는 일반적으로 불변이어야 해시 값이 변경되지 않기 때문에 두 가지 아이디어가 관련됩니다. 변경이 허용되면 해시 테이블과 같은 데이터 구조에서 해당 객체의 위치가 변경되고 효율성을위한 해싱의 전체 목적이 무효화됩니다.

아이디어를 제대로 이해하려면 C / C ++와 같은 언어로 고유 한 해시 테이블을 구현하거나 HashMap클래스 의 Java 구현을 읽어야합니다 .


1
또한 해시 테이블이 키의 해시가 변경되는시기를 감지하는 것은 불가능합니다 (적어도 효율적인 방법으로). 예를 들어 Java에서 HashMap키로 사용 된 객체를 수정하면 a 가 깨지는 일반적인 함정입니다 . 맵을 인쇄해도 해당 키를 볼 수 있지만 이전 키나 새 키를 찾을 수 없습니다.
14:55에 두 배

1
Hashable과 Immutable은 다소 관련이 있지만 동일하지는 않습니다. 예를 들어 사용자 정의 클래스 상속에서 생성 된 인스턴스 object는 해시 할 수 있지만 변경할 수는 없습니다. 이러한 인스턴스는 dict의 키를 사용할 수 있지만 전달되는 경우 여전히 수정할 수 있습니다.
Pranjal Mittal

13
  • 해시 할 수있는 변경 가능한 객체 또는 해시 할 수없는 변경 불가능한 객체가 있습니까?

Python에서 튜플은 불변이지만 모든 요소가 해시 가능한 경우에만 해시 할 수 있습니다.

>>> tt = (1, 2, (30, 40))
>>> hash(tt)
8027212646858338501
>>> tl = (1, 2, [30, 40])
>>> hash(tl)
TypeError: unhashable type: 'list'

해시 가능한 유형

  • 원자 불변 유형은 str, 바이트, 숫자 유형과 같이 모두 해시 가능합니다.
  • 고정 된 집합은 항상 해시 가능합니다 (요소는 정의에 따라 해시 가능해야 함).
  • 튜플은 모든 요소가 해시 가능한 경우에만 해시 가능합니다.
  • 사용자 정의 유형은 해시 값이 id ()이므로 기본적으로 해시 할 수 있습니다.

8

로부터 파이썬 용어 :

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

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

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

사전과 세트는 해시 테이블에서 효율적인 조회를 위해 해시를 사용해야합니다. 해시를 변경하면 데이터 구조가 엉망이되고 dict 또는 set이 실패하게되므로 해시 값은 불변이어야합니다. 해시 값을 불변으로 만드는 가장 쉬운 방법은 전체 객체를 불변으로 만드는 것이므로 두 가지가 종종 함께 언급되는 이유입니다.

내장 된 변경 가능 객체는 해시 할 수 없지만 변경 불가능한 해시 값을 사용하여 변경 가능한 객체를 만들 수 있습니다. 개체의 일부만 해당 ID를 나타내는 것이 일반적이며 나머지 개체에는 자유롭게 변경할 수있는 속성이 포함되어 있습니다. 해시 값과 비교 함수가 변경 가능한 속성이 아닌 ID를 기반으로하고 ID가 변경되지 않는 한 요구 사항을 충족 한 것입니다.


@Andrey : frozensets는 해시 가능하지만 세트는 그렇지 않습니다. 둘 다 해시 가능한 항목 만 포함 할 수 있습니다. Mark가 세트를 언급 한 곳에서 그는 옳았 기 때문에 그가 frozensets를 의미한다고 생각하지 않습니다.
tzot

12
사용자 정의 클래스는 기본적으로 해시 가능한 유형을 정의합니다 (해시는 객체의 id). 이것은 객체의 수명 동안 변경할 수 없으므로 해시 가능하지만 변경 가능한 유형을 정의 할 수 없다는 의미는 아닙니다! 죄송합니다. 해시 가능성이 불변성을 의미하지는 않습니다.
Scott Griffiths

1
@ScottGriffiths 귀하의 의견을 보는 데 왜 6 년이 걸 렸는지 모르겠지만 결코 늦는 것이 좋습니다. 변경 가능한 객체를 C ++ 세트에 넣을 수 없다는 점을 감안할 때 내가 어떻게 그렇게 멀리 떨어져있을 수 있었는지 모르겠습니다. 내 편집으로 문제가 해결되기를 바랍니다.
Mark Ransom

7

기술적으로 해시 가능이란 클래스가 __hash__(). 문서에 따르면 :

__hash__()정수를 반환해야합니다. 유일한 필수 속성은 동일하게 비교되는 객체가 동일한 해시 값을 갖는 것입니다. 객체와 비교하여 역할을 수행하는 객체의 구성 요소에 대한 해시 값을 어떻게 든 함께 혼합 (예 : 배타적 또는 사용)하는 것이 좋습니다.

파이썬 내장 유형의 경우 모든 해시 가능한 유형도 불변이라고 생각합니다.

그럼에도 불구하고 정의 된 가변 객체를 갖는 것은 어렵지만 아마도 불가능하지는 않을 것 __hash__()입니다.


1
__hash__기본적으로 객체를 반환하도록 정의되어 있다는 점 은 주목할 가치가 있습니다 id. 당신은 __hash__ = None그것을 unhashable 로 설정 하기 위해 당신의 길을 벗어나야합니다. 또한 Mark Ransom이 언급했듯이 해시 값을 변경할 수없는 경우에만 해시 할 수있는 추가 조건이 있습니다!
Scott Griffiths

5
나는 대답이 약간 오해의 소지가 있다고 생각하고 반환 하는 의미에서 list정의 하지만 호출 하면 (Python 3)이 발생하므로 정확하게 해시 할 수 없습니다. 의 존재에 의존하는 것은 무언가가 a) 해시 가능 b) 불변인지 판단하기에 충분하지 않습니다__hash__hasattr([1,2,3], "__hash__")Truehash([1,2,3])TypeError__hash__
Matti Lyra

4

불변과 해시 사이의 상호 작용으로 인해 불변과 해시 사이에 명시적인 관계가 없어도 암시 적입니다.

  1. 동일하게 비교되는 해시 가능한 객체는 동일한 해시 값을 가져야합니다.
  2. 객체는 수명 동안 절대 변경되지 않는 해시 값이있는 경우 해시 할 수 있습니다.

__eq__객체 클래스가 가치에 대한 동등성을 정의 하도록 재정의하지 않는 한 여기에는 문제가 없습니다 .

이 작업을 마치면 동일한 값 (예 : where __eq__) 을 나타내는 객체에 대해 항상 동일한 값을 반환하는 안정적인 해시 함수를 찾아야합니다 (예 : where )는 True를 반환하고 객체의 수명 동안 절대 변경되지 않습니다.

이것이 가능한 응용 프로그램을 찾기가 어렵습니다 . 이러한 요구 사항을 충족 하는 가능한 클래스 A 를 고려하십시오 . __hash__상수를 반환하는 명백한 퇴화 사례가 있지만 .

지금:-

>>> a = A(1)
>>> b = A(1)
>>> c = A(2)
>>> a == b
True
>>> a == c
False
>>> hash(a) == hash(b)
True
>>> a.set_value(c)
>>> a == c
True
>>> assert(hash(a) == hash(c)) # Because a == c => hash(a) == hash(c)
>>> assert(hash(a) == hash(b)) # Because hash(a) and hash(b) have compared equal 
                                 before and the result must stay static over the objects lifetime.

실제로 이것은 생성시 hash (b) == hash (c)를 의미합니다. 어쨌든 __hash__값으로 비교를 정의하는 변경 가능한 객체에 대해 () 를 유용하게 정의하는 데 어려움을 겪습니다 .

참고 : __lt__, __le__, __gt____ge__당신은 여전히 가변하거나 자신의 가치를 기반으로 해쉬 객체의 순서를 정의 할 수 있도록 비교 식에는 영향을주지 않습니다.


3

이것이 Google 인기 히트작이기 때문에 변경 가능한 객체를 해시 가능하게 만드는 간단한 방법은 다음과 같습니다.

>>> class HashableList(list):
...  instancenumber = 0  # class variable
...  def __init__(self, initial = []):
...   super(HashableList, self).__init__(initial)
...   self.hashvalue = HashableList.instancenumber
...   HashableList.instancenumber += 1
...  def __hash__(self):
...   return self.hashvalue
... 
>>> l = [1,2,3]
>>> m = HashableList(l)
>>> n = HashableList([1,2,3])
>>> m == n
True
>>> a={m:1, n:2}
>>> a[l] = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> m.hashvalue, n.hashvalue
(0, 1)

SQLAlchemy 레코드를 변경 가능하고 나에게 더 유용한 것으로 변환하는 클래스를 만들 때 실제로 이와 같은 용도를 찾았지만 딕셔너리 키로 사용하기위한 해시 성을 유지했습니다.


3

불변성은 객체가 수명 동안 중요한 방식으로 변경되지 않음을 의미합니다. 프로그래밍 언어에서 모호하지만 일반적인 아이디어입니다.

해시 가능성은 약간 다르며 비교를 나타냅니다.

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

모든 사용자 정의 클래스에는 __hash__기본적으로 개체 ID 만 반환하는 메서드가 있습니다. 따라서 해시 가능성 기준을 충족하는 객체가 반드시 변경 불가능한 것은 아닙니다.

선언 한 새 클래스의 객체는 예를 들어 다음과 같은 방법으로 방지하지 않는 한 사전 키로 사용할 수 있습니다. __hash__

모든 불변 객체는 해시 가능하다고 말할 수 있습니다. 왜냐하면 객체의 수명 동안 해시가 변경되면 객체가 변형되었음을 의미하기 때문입니다.

하지만 정답은 아닙니다. 목록 (변경 가능)이있는 튜플을 고려하십시오. 어떤 사람들은 튜플이 불변이라고 말하지만 동시에 다소 해시 할 수 없습니다.

d = dict()
d[ (0,0) ] = 1    #perfectly fine
d[ (0,[0]) ] = 1  #throws

해시 가능성과 불변성은 유형이 아닌 객체 인스턴스를 참조합니다. 예를 들어, 튜플 유형의 객체는 해시 가능하거나 그렇지 않을 수 있습니다.


1
"동일하게 비교되는 해시 가능한 객체는 동일한 해시 값을 가져야합니다." 왜? 동일하게 비교되지만 해시 값이 동일하지 않은 객체를 만들 수 있습니다.
endolith 2014-08-26

1
그러한 객체를 생성하는 것은 가능하지만 파이썬 문서에 정의 된 개념을 위반하는 것입니다. 사실,이 요구 사항을 사용하여 논리적으로 동등한 의미를 도출 할 수 있습니다. 해시가 같지 않으면 객체가 같지 않습니다. 매우 유용한. 많은 구현, 컨테이너 및 알고리즘이 이러한 의미에 의존하여 작업 속도를 높입니다.
user2622016 14:44에

comparison != identity와 같이 "잘못된"값을 함께 비교할 때 일반적인 경우 float("nan") == float("nan")또는 슬라이스의 인턴 된 문자열 : "apple" is "apple"vs."apple" is "crabapple"[4:]
sleblanc

1

파이썬에서는 대부분 상호 교환이 가능합니다. 해시는 내용을 나타 내기 때문에 객체만큼 변경 가능하고 객체가 해시 값을 변경하면 dict 키로 사용할 수 없게됩니다.

다른 언어에서 해시 값은 (필수적으로) 값이 아닌 개체 'ID'와 더 관련이 있습니다. 따라서 변경 가능한 객체의 경우 포인터를 사용하여 해싱을 시작할 수 있습니다. 물론 객체가 메모리에서 이동하지 않는다고 가정합니다 (일부 GC처럼). 예를 들어 이것은 Lua에서 사용되는 접근 방식입니다. 이렇게하면 변경 가능한 객체를 테이블 키로 사용할 수 있습니다. 그러나 초보자들에게 몇 가지 (불쾌한) 놀라움을 만듭니다.

결국 불변의 시퀀스 유형 (튜플)을 사용하면 '다중 값 키'에 더 적합합니다.


3
@javier : 'Python에서는 대체로 상호 교환이 가능합니다.'내 의심은 'mostly'에 포함되지 않은 작은 부분을
가리 킵니다

0

Hashable은 변수의 값이 문자열, 숫자 등 상수로 표현 (또는 인코딩) 될 수 있음을 의미합니다. 이제 변경 (변경 가능) 대상은 그렇지 않은 것으로 표현 될 수 없습니다. 따라서 변경 가능한 변수는 해시 할 수 없으며 동일한 토큰에 의해 변경 불가능한 변수 만 해시 할 수 있습니다.

도움이 되었기를 바랍니다 ...

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