Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

Python 함수는 왜 해시 가능하고, 어떻게 작동할까?

파이썬에서 객체의 해시 값이 객체의 생애 주기 동안 변하지 않고, 다른 객체와 비교될 수 있다면 그 객체를 해시 가능(hashable)하다고 말합니다. 이를 위해서는 __hash__() 메서드가 필요하며, 객체 간 비교를 위해 __eq__() 또는 __cmp__() 메서드가 요구됩니다. 해시 가능한 객체끼리 서로 같다고 판정되면, 두 객체는 반드시 동일한 해시 값을 가져야 합니다.

해시 가능하다는 성질은 곧 해당 객체를 딕셔너리의 키나 집합(set)의 원소로 사용할 수 있음을 의미합니다. 이러한 자료 구조들은 내부적으로 해시 값을 사용하기 때문입니다.

해시 가능한 객체와 그렇지 않은 객체

파이썬의 모든 불변(immutable) 내장 객체는 해시 가능합니다. 반면 리스트나 딕셔너리처럼 가변(mutable)인 컨테이너는 해시할 수 없지만, 불변 컨테이너인 튜플(tuple)은 해시 가능합니다.

사용자가 직접 정의한 클래스의 인스턴스는 기본적으로 해시 가능합니다. 기본 상태에서는 자기 자신과만 같다고 비교되며, 해시 값은 객체의 id()에서 파생됩니다.

함수의 해시 값은 반드시 id()와 같을까?

흥미로운 점은 해시 값이 반드시 함수의 ID와 일치하지 않는다는 사실입니다. 다음 람다 함수 예제를 통해 확인해 보겠습니다.

예제 1: 람다 함수

m = lambda x: 1
print(hash(m))
print(id(m))
print(m.__hash__())

실행 결과:

1265925722
3074942372
1265925722

hash(m)m.__hash__()의 결과가 일치하는 것으로 보아, 람다 함수 역시 해시 가능한 객체임을 알 수 있습니다. 참고로 실제 출력되는 수치는 실행 환경마다 달라질 수 있습니다.

예제 2: 일반 함수

이번에는 다음과 같이 일반 함수 f()를 정의해 확인해 보겠습니다.

def f(): pass
print(type(f))
print(f.__hash__())
print(hash(f))

실행 결과:

<class 'function'>
1265925978
1265925978

결론

함수는 해시 값이 생애 주기 동안 변하지 않는 객체이므로 해시 가능합니다. 덕분에 함수를 딕셔너리의 키나 집합의 원소로 활용할 수 있으며, 이는 콜백 등록이나 디스패치 테이블(dispatch table) 구성 등 실무에서 유용하게 쓰이는 특성입니다.