복붙노트

[PYTHON] 파이썬에서 기본 __hash__은 무엇입니까?

PYTHON

파이썬에서 기본 __hash__은 무엇입니까?

저는 꽤 자주 펑키 한 것을 사전에 대한 키로 사용하고 있습니다. 따라서 옳은 방법이 무엇일까 궁금해합니다. 이것은 내 객체에 대한 좋은 해시 메소드를 구현하는 것을 의미합니다. 해시를 구현하는 좋은 방법처럼 여기에 질문하는 다른 질문을 알고 있지만 사용자 지정 개체에 대해 기본 __hash__이 어떻게 작동하는지 이해하고 싶습니다.

해시 ({})가 오류를 발생시키기 때문에 mutables가 명백하게 unhashable하다는 것을 알았습니다 ... 그러나 이상하게도 사용자 정의 클래스는 해시 가능합니다.

>>> class Object(object): pass
>>> o = Object()
>>> hash(o)

그럼,이 기본 해쉬 함수가 어떻게 작동하는지 아무도 모른다. 이것을 이해함으로써 나는 알고 싶다 :

사전의 키와 같은 유형의 객체를두면이 기본 해시를 신뢰할 수 있습니까? 예 : :

key1 = MyObject()
key2 = MyObject()
key3 = MyObject()
{key1: 1, key2: 'blabla', key3: 456}

사전에 다른 유형의 객체를 키로 사용하면 그 객체에 의존 할 수 있습니까? 예 :

{int: 123, MyObject(10): 'bla', 'plo': 890}

그리고 마지막 경우에도, 내 사용자 정의 해시가 내장 해시와 충돌하지 않도록하는 방법은 무엇입니까? 예 :

{int: 123, MyObject(10): 'bla', MyObjectWithCustomHash(123): 890}

해결법

  1. ==============================

    1.당신이 의지 할 수있는 것 : 커스텀 객체는 객체의 신원에 어떤 식 으로든 기반을 둔 기본 hash ()를 가지고 있습니다. 즉, 디폴트 해시를 사용하는 임의의 객체는 그 수명 동안 그 해시에 대한 상수 값을 가지며, 상이한 객체는 상이한 해시 값을 가질 수도 그렇지 않을 수도있다.

    당신이 의지 할 수있는 것 : 커스텀 객체는 객체의 신원에 어떤 식 으로든 기반을 둔 기본 hash ()를 가지고 있습니다. 즉, 디폴트 해시를 사용하는 임의의 객체는 그 수명 동안 그 해시에 대한 상수 값을 가지며, 상이한 객체는 상이한 해시 값을 가질 수도 그렇지 않을 수도있다.

    id ()에 의해 반환 된 값과 hash ()에 의해 반환 된 값 사이의 특정 관계에 의존 할 수 없습니다. Python 2.6 및 이전 버전의 표준 C 구현에서는 Python 2.7-3.2 hash (x) == id (x) / 16에서 동일했습니다.

    편집 : 원래 3.2.3 이후 버전 또는 2.7.3 이후 버전에서는 해시 값을 무작위로 지정할 수 있으며 Python 3.3에서는 관계가 항상 무작위로 지정됩니다. 사실 현재 무작위 화가 해싱 문자열에만 적용되므로 사실 16으로 나누는 관계는 지금도 계속 유지 될 수 있지만이를 기반으로하지 마십시오.

    해시 충돌은 일반적으로 중요하지 않습니다. 사전 조회는 개체를 찾기 위해 동일한 해시를 가져야하며 같은 것을 비교해야합니다. 충돌은 해시 계산을 무작위로 추출 할 수있는 Python의 최신 버전으로 이어지는 서비스 거부 공격과 같이 매우 높은 비율의 충돌이 발생하는 경우에만 중요합니다.

  2. ==============================

    2.문서에서는 사용자 정의 객체가 id ()를 hash () 구현으로 사용한다고 명시합니다.

    문서에서는 사용자 정의 객체가 id ()를 hash () 구현으로 사용한다고 명시합니다.

    사용자 정의 객체를 int와 같은 기본 유형과 혼합하면 해시 충돌이 발생할 수 있지만 균등하게 분배되면 아무런 문제가 없습니다. 성능 문제가 발생하지 않는 한 너무 많이 조사하지 마십시오.

  3. ==============================

    3.사용자 정의 클래스의 기본 해시는 해당 ID를 반환하는 것입니다. 이것은 종종 유용한 동작을 제공합니다. 사전 정의 키로 사용자 정의 클래스의 인스턴스를 사용하면 정확히 동일한 오브젝트가 다시 제공되어 값을 검색 할 때 연관된 값을 검색 할 수 있습니다. 예 :

    사용자 정의 클래스의 기본 해시는 해당 ID를 반환하는 것입니다. 이것은 종종 유용한 동작을 제공합니다. 사전 정의 키로 사용자 정의 클래스의 인스턴스를 사용하면 정확히 동일한 오브젝트가 다시 제공되어 값을 검색 할 때 연관된 값을 검색 할 수 있습니다. 예 :

    >>> class Foo(object):
        def __init__(self, foo):
            self.foo = foo
    
    
    >>> f = Foo(10)
    >>> d = {f: 10}
    >>> d[f]
    10
    

    이것은 사용자 정의 클래스의 기본 동등성과 일치합니다.

    >>> g = Foo(10)
    >>> f == g
    False
    >>> d[g]
    
    Traceback (most recent call last):
      File "<pyshell#9>", line 1, in <module>
        d[g]
    KeyError: <__main__.Foo object at 0x0000000002D69390>
    

    f와 g가 속성에 대해 같은 값을 가지고 있다고해도, 그것들은 동일하지 않고, d에서 g를 찾아 보면 f 아래에 저장된 값을 찾지 못한다. 게다가 f.foo의 값을 변경하더라도 f에서 d를 찾는 것은 여전히 ​​값을 찾습니다.

    >>> f.foo = 11
    >>> d[f]
    10
    

    프로그래머가 __eq__ 및 __hash__를 정의하여 두 인스턴스의 조건을 동등한 것으로 명시 적으로 선언하지 않는 한 임의의 새 클래스의 인스턴스는 비 동일하게 취급되어야합니다.

    그리고 이것은 거의 작동합니다. Car 클래스를 정의하면 동일한 속성을 가진 두 대의 자동차가 서로 다른 두 대의 자동차를 나타내는 것으로 간주 할 수 있습니다. 앨리스와 밥이 같은 자동차를 소유 한 경우에도 등록 된 소유자에게 자동차를 매핑하는 사전이 있다면 앨리스를 찾지 않고 싶습니다. OTOH, 우편 번호를 나타내는 클래스를 정의하면 같은 코드를 가진 두 개의 다른 객체를 "같은"표현으로 교환 할 수있는 것으로 간주하고이 경우 우편 코드를 상태에 매핑하는 사전이 있다면 , 분명히 동일한 우편 번호를 나타내는 두 개의 서로 다른 객체로 동일한 상태를 찾을 수 있기를 원합니다.

    이것을 "값 유형"과 "객체 유형"의 차이라고합니다. 값 유형은 일부 값을 나타내며 각 개별 객체의 ID가 아니라 내가 아끼는 값입니다. 동일한 값을 갖는 두 가지 다른 방법이 똑같이 우수하며, 값 유형을 전달하는 코드의 "계약"은 보통 특정 오브젝트를 지정하지 않고 값을 가진 오브젝트를 제공 할 것을 약속합니다. 오브젝트 유형 OTOH의 경우, 각 인스턴스는 다른 인스턴스와 정확히 동일한 데이터를 포함하더라도 고유 한 ID를가집니다. 객체 유형을 전달하는 코드의 "계약"은 대개 정확한 개별 객체를 추적 할 것을 약속합니다.

    그렇다면 왜 내장 된 변경 가능한 클래스가 자신의 ID를 해시로 사용하지 않습니까? 그것들은 모든 컨테이너이기 때문에 일반적으로 컨테이너는 값 유형과 거의 같은 것으로 간주되며, 그 값은 포함 된 요소에 의해 결정됩니다.

    >>> [1, 2, 3] == [1, 2, 3]
    True
    >>> {f: 10} == {f: 10}
    True
    

    그러나 변경 가능한 컨테이너에는 일시적인 값이 있습니다. 주어진리스트는 현재 값이 [1, 2, 3]이지만 값이 [4, 5, 6] 인 것으로 변형 될 수 있습니다. 목록을 사전 키로 사용할 수 있다면 조회가 목록의 (현재) 값 또는 해당 ID를 사용해야하는지 여부에 대한 판결을 내려야합니다. 어쨌든 우리는 현재 사전 키로 사용되는 객체의 값이 돌연변이에 의해 변경 될 때 (매우) 놀랄 수 있습니다. 오브젝트를 사전 키로 사용하면 오브젝트의 값이 ID 일 때 또는 오브젝트의 ID가 해당 값과 관련이없는 경우에만 잘 작동합니다. 그래서 파이썬이 선택한 대답은 가변적 인 컨테이너를 unhashable로 선언하는 것입니다.

    이제 귀하의 직접적인 질문에 대한 답변에 대한 구체적인 세부 정보 :

    1) CPython의이 기본 해시 (다른 답변 / 설명에 따르면 명백하게 <2.6)가 객체의 메모리 주소에 매핑되기 때문에 CPython에서 기본 해싱을 사용하는 두 객체는 ​​동시에 동시에 살 수 없습니다 관련된 클래스에 관계없이 해시 값을 가져올 수 있습니다 (사전 키로 저장되어있는 경우 라이브 임). 또한 메모리 주소를 해시로 사용하지 않는 다른 Python 구현에서는 기본 해싱을 사용하는 객체간에 괜찮은 해시 배포를 유지해야한다고 생각합니다. 네, 당신은 그것에 의지 할 수 있습니다.

    2) 사용자 지정 해시 값을 기존 개체의 해시 값과 정확히 일치하지 않는 한 비교적 양호하게 유지해야합니다. 제 생각에 파이썬의 해시 기반 컨테이너는 완전히 퇴화되지 않는 한 차선의 해시 함수에 비교적 관대합니다.

  4. ==============================

    4.파이썬 3에서 객체의 서브 클래스 (pyhash.c의 ​​객체)에 대해 다음 함수가 사용되었습니다.

    파이썬 3에서 객체의 서브 클래스 (pyhash.c의 ​​객체)에 대해 다음 함수가 사용되었습니다.

    Py_hash_t
    _Py_HashPointer(void *p)
    {
        Py_hash_t x;
        size_t y = (size_t)p;
        /* bottom 3 or 4 bits are likely to be 0; rotate y by 4 to avoid
           excessive hash collisions for dicts and sets */
        y = (y >> 4) | (y << (8 * SIZEOF_VOID_P - 4));
        x = (Py_hash_t)y;
        if (x == -1)
            x = -2;
        return x;
    }
    

    SIZEOF_VOID_P는 64 비트 Python에서는 8이고 32 비트 Python에서는 4입니다.

    >>> class test: pass
    ...
    >>> a = test()
    >>> id(a)
    4325845928
    >>> hash(a)
    -9223372036584410438
    

    해쉬는 공식 (id (a) >> 4)을 사용하여 id (a)에서 계산된다는 것을 알 수 있습니다. (id (a) << (8 * SIZEOF_VOID_P-4)). 여기서 비트 단위 연산은 C 부호가있는 정수에서 수행됩니다. 예를 들어 위에 정의 된 a의 경우 :

    >>> import numpy
    >>> y = numpy.array([4325845928], dtype='int64')
    >>> SIZEOF_VOID_P = 8
    >>> (y >> 4) | (y << (8 * SIZEOF_VOID_P - 4))
    array([-9223372036584410438])
    

    bitwise 연산이 C에서와 같은 방식으로 동작하도록 numpy.array (dtype = 'int64')를 사용하고 있습니다 (파이썬 ints에서 동일한 연산을 수행하면 오버플로가 발생하지 않으므로 다른 동작을 얻음). https://stackoverflow.com/a/5994397/161801을 참조하십시오.

  5. ==============================

    5.

    >>> class C(object):
    ...     pass
    ... 
    >>> c = C()
    >>> hash(c) == id(c)
    True
    

    기능 ID보기

  6. ==============================

    6.

    >>> class C(object):
    ...     pass
    ... 
    >>> c = C()
    >>> hash(c) == id(c)
    False
    >>> hash(c) == id(c)/16
    True
    

    16으로 나눈 값은 참값

  7. from https://stackoverflow.com/questions/11324271/what-is-the-default-hash-in-python by cc-by-sa and MIT license