본문 바로가기

Language/Python

RePythonOOP 12일차 Python Hash_table in Dictionary Structure, Dictionary Comprehension, Dictionary_Inheritance in Class

YSL*Mondrian

Insight

1.  해시 태이블 사용한다 ==  해시 테이블 내부는 딕셔너리 구조다 
->  코드에 딕셔너리는 mutable하다. 햇갈리지 말자
2.  mutable한 자료형이면 해시 테이블을 사용하지 않는 자료구조이다

3. 결국 이걸 따지는 것은 Immutable한 자료가 성능 향상에 좋다.

4. 딕셔너리의 키 벨류 체이닝에 대해 여러 풀이 방법을 생각해 보았다.

5. 딕셔너리를 실제 클래스로 상속 받아서 구현함으로 딕셔너리 내부를 재대로 이해하게 되었다.

Hash_table in Dictionary Structure

파이썬의 핵심엔진은 딕셔너리 형태로 이루어져 있다.

 

파이썬은 이 딕셔너리의 해시테이블(hashtable)을 사용 -> 해시 태이블내 해시값(숫자)로 같은지 아닌지 판별 ->

적은 리소스로 많은 데이터를 효율적으로 관리 -> 색인기능, 고성능

 

Dict자료형 -> Key만 중복 허용 하지 않는다

Set -> 중복 허용하지 않는다.

 

 

함수형들도 거의 대부분 딕셔너리(Dict) 구조 이다.

 

print(__builtins__.__dict__) # 로 확인 할수 있다.

 

그렇다면 튜플안의 튜플, 튜플안의 리스트면 어떨가?

 

hash() 해쉬값이 있는지 없는지 해쉬값 함수

# Hash 값 확인
t1 = (10, 20, (30, 40, 50)) # 튜플안의 튜플
t2 = (10, 20, [30, 40, 50]) # 튜플안에 리스트

print('hash : ',  hash(t1)) # hash :  5737367089334957572
# print(hash(t2)) # 에러난다 mutable한 리스트가 있으므로

 

 해쉬값은 중복을 허용하지 않으므로  튜플안의 리스트는 리스트값이 항상 변함으로(중복을 허용함으로(mutable)) 해쉬 값이 존재하지 않는다.

 

 

Dictionary Comprehension

이전의 컴프리 핸션의 구조와 거의 같아서 바로 예시로 설명하겠다. 

 

# 리스트안의  튜플구조로 있으면
ex_list = [('Afghanistan','AF'),('Åland Islands','AX'),('Albania','AL')]
tuple_  = { c: code for c, code in ex_list} 
# 지
print(tuple_) # {'Afghanistan': 'AF', 'Åland Islands': 'AX', 'Albania': 'AL'}

 

딕셔너리의 setdefault 사용하기

 

Dictionary_Inheritance in Class

딕셔너리(immutable)는 키가 hashable해야 함으로 중복이 허용이 안된다.

 

하지만 값만 중복을 허용하도록 저장하고 싶다면?

 

일단 튜플내 튜플형태로 중복되는 자료형을 만든다.

 

source = (  ('key_1', 'value1'),
            ('key_1', 'value2'),
            ('key_1', 'value1'),
            ('key_2', 'value3'),
            ('key_2', 'value4'),
            ('key_2', 'value5'),
            ('key_3', 'value5')
            )

empty_dict = {}

 

딕셔너리의 setdefault 미 사용시의 해결 법

 

values를 리스트 형태로 해결 (json형태)

 

for k, v in source:
    # 키가 이미 있다면 리스트로 만들어줄 append 메서드 사용
    if k in empty_dict:
        empty_dict[k].append(v)
        # 없으면 그냥 리스트로 추가
    else:
        empty_dict[k] = [v]

print(empty_dict) # {'key_1': ['value1', 'value2'], 'key_2': ['value3', 'value4', 'value5'], 'key_3': ['value5']}

 

딕셔너리의 setdefault 사용시

 

구조 : 딕셔너리.setdefault(키,벨류받을 자료형 형식).append(벨류)

 

empty_dict2 = {}

for k, v in source:
    empty_dict2.setdefault(k,[]).append(v)
print(empty_dict2) # {'key_1': ['value1', 'value2'], 'key_2': ['value3', 'value4', 'value5'], 'key_3': ['value5']}

 

데이터양이 많은 json타입의 데이터 처리시

setdefault에서의 분기문을 안쓰고 성능을 올릴 수 있다.

 

Dictionary_Inheritance in Class

사용자 정의 딕셔너리(파이썬의 기본 dict 상속 받기 )만들기

 

실제 딕셔너리가 어떻게 동작하는지 메소드 오버라이딩을 하며 알아 보자

 

class PrivateDict(dict):
    # 참조 
    # https://docs.python.org/3/library/stdtypes.html#dict
    # https://docs.python.org/3/library/collections.html#collections.defaultdict
    # __missing__은 딕셔너리의 서브클래스고 키가 없을시 키 에러를 발생 시키고 
    # 키가 있으면 str(key)로 감싸서 리턴해준다.
    # 사용자 정의 딕셔너리를 만들기 위해 꼭 선언해 주어야 한다.  

    def __missing__(self, key):
        print('__missing__ is working')
        if isinstance(key, str):
            raise KeyError(key)
        return self[str(key)]

    # __getitem__과 같은 역할 get을 사용할때
    def get(self, key, default=None):
        print('__getitem__ is working')
        try:
            return self[key]
        except KeyError:
            return default
    # in(연결연산자)연산자 사용시 포함이 되어 있는지 확인하는 메서드
    def __contains__(self, key):
        print('__contains__ is working')
        return key in self.keys() or str(key) in self.keys()

private_dict1 = PrivateDict(one=1, two=2) # 값을 할당해도 됨
private_dict2 = PrivateDict({'one': 1, 'two': 2}) # 딕셔너리 형태로 인자를 넣어도 됨
private_dict3 = PrivateDict([('one',1),('two',2)]) # 튜플 형태로 분리해서 넣어줘도 됨

# 출력
print(private_dict1, private_dict2, private_dict3) #{'one': 1, 'two': 2} {'one': 1, 'two': 2} {'one': 1, 'two': 2}

 

print('private_dict2.get', private_dict2.get('two'))

# 클래스내 get()으로 실행합니다.
# __getitem__ is working
# private_dict2.get 2

 

print('one in private_dict3', 'one' in private_dict3)

# in은  __contains__을 실행합니다.
# __contains__ is working
# one in private_dict3 True

 

print('private_dict3.get', private_dict3.get('three'))
# 일단 get을 실행합니다.
# __getitem__ is working
# 키가 없으므로  파이썬 내부적으로 __missing__(오버라이딩된)을 실행합니다.
# __missing__ is working
# 키값의 형태를 보고 return self[str(key)]이 실행됩니다.
# private_dict3.get None

 

print('three in private_dict3', 'three' in private_dict3)
# __contains__ is working
# three in private_dict3 False


print(private_dict3['three'])
# 키에러가 발생
# __missing__ is working
#     raise KeyError(key)
# KeyError: 'three'

 

여기서 중요한 것은

1. get('키')메서드 사용시 값이 있으면 값 리턴 없으면 None 리턴하지만

a['b']키 인덱스로 접근시 없으면 KeyError를 발생시키고

 

2. 오버라이딩으로 사용자가 재정의 하여 딕셔너리의 구조를 변경 시켜서 본인의 프로젝트에

맞게 수정이 가능 하다는 것 이다.