[PYTHON] 파이썬 해시 가능 딕테이션
PYTHON파이썬 해시 가능 딕테이션
연습으로, 그리고 주로 내 자신의 즐거움을 위해서, 나는 백 트랙킹 팩트 파서를 구현하고있다. 이것에 대한 영감은 유전자 변형 매크로가 algol-like 언어에서 어떻게 작동하는지에 대해 더 잘 알고 싶습니다. (일반적으로 당신이 사용하는 syntax free lisp 방언과 유사합니다.) 이 때문에 입력을 통해 다른 패스가 다른 문법을 볼 수 있으므로 캐시 된 구문 분석 결과와 함께 현재 버전의 문법도 저장하지 않으면 캐시 된 구문 분석 결과가 유효하지 않습니다. (편집 :이 키 - 값 컬렉션의 사용의 결과는 그들이 불변해야한다는 것입니다,하지만 나는 변경할 수 있도록 인터페이스를 폭로하지 않으므로, 변경 또는 불변 컬렉션 중 하나입니다)
문제는 파이썬 사전이 다른 사전에 키로 나타날 수 없다는 것입니다. 튜플을 사용하더라도 (어쨌든 내가하고있는 것처럼) 도움이되지 않는다.
>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>>
나는 튜플이 끝까지 내려와 있어야한다고 생각한다. 이제 파이썬 표준 라이브러리는 대략 필요한 것, collections.namedtuple은 매우 다른 구문을 제공하지만 키로 사용할 수 있습니다. 상기 세션에서 계속 :
>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}
승인. 그러나 각 규칙은 사용하는 매개 변수를 정확히 알고 있기 때문에 클래스를 동시에 정의 할 수 있기 때문에 사용하고 싶은 규칙에서 가능한 모든 키 조합에 대해 클래스를 만들어야합니다. 규칙을 구문 분석하는 함수로
편집 : 명명 된 튜플의 또 다른 문제점은 엄격하게 위치 지정된다는 것입니다. 서로 달라야하는 것처럼 보이는 두 개의 튜플은 실제로 동일 할 수 있습니다.
>>> you = namedtuple("foo",["bar","baz"])
>>> me = namedtuple("foo",["bar","quux"])
>>> you(bar=1,baz=2) == me(bar=1,quux=2)
True
>>> bob = namedtuple("foo",["baz","bar"])
>>> you(bar=1,baz=2) == bob(bar=1,baz=2)
False
tl'dr : 다른 dicts에 대한 키로 사용될 수있는 dicts를 얻으려면 어떻게해야합니까?
해답을 조금씩 해킹 한 결과, 여기에 제가 사용하고있는보다 완벽한 해결책이 있습니다. 그 결과 dicts을 실제 목적을 위해 막연하게 불변으로 만드는 약간의 추가 작업을 수행합니다. 물론 dict .__ setitem __ (instance, key, value)를 호출하여 해킹하는 것은 여전히 쉽지만 우리는 모두 성인입니다.
class hashdict(dict):
"""
hashable dict implementation, suitable for use as a key into
other dicts.
>>> h1 = hashdict({"apples": 1, "bananas":2})
>>> h2 = hashdict({"bananas": 3, "mangoes": 5})
>>> h1+h2
hashdict(apples=1, bananas=3, mangoes=5)
>>> d1 = {}
>>> d1[h1] = "salad"
>>> d1[h1]
'salad'
>>> d1[h2]
Traceback (most recent call last):
...
KeyError: hashdict(bananas=3, mangoes=5)
based on answers from
http://stackoverflow.com/questions/1151658/python-hashable-dicts
"""
def __key(self):
return tuple(sorted(self.items()))
def __repr__(self):
return "{0}({1})".format(self.__class__.__name__,
", ".join("{0}={1}".format(
str(i[0]),repr(i[1])) for i in self.__key()))
def __hash__(self):
return hash(self.__key())
def __setitem__(self, key, value):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def __delitem__(self, key):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def clear(self):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def pop(self, *args, **kwargs):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def popitem(self, *args, **kwargs):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def setdefault(self, *args, **kwargs):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
def update(self, *args, **kwargs):
raise TypeError("{0} does not support item assignment"
.format(self.__class__.__name__))
# update is not ok because it mutates the object
# __add__ is ok because it creates a new object
# while the new object is under construction, it's ok to mutate it
def __add__(self, right):
result = hashdict(self)
dict.update(result, right)
return result
if __name__ == "__main__":
import doctest
doctest.testmod()
해결법
-
==============================
1.해시 가능 사전을 만드는 쉬운 방법은 다음과 같습니다. 확실한 이유로 다른 사전에 삽입 한 후 변형시키지 마십시오.
해시 가능 사전을 만드는 쉬운 방법은 다음과 같습니다. 확실한 이유로 다른 사전에 삽입 한 후 변형시키지 마십시오.
class hashabledict(dict): def __hash__(self): return hash(tuple(sorted(self.items())))
-
==============================
2.Hashables는 불변이어야한다. - 이것을 강요하지는 말고, 처음으로 키로 사용한 후에는 dict을 변형시키지 말라. 다음과 같은 접근 방식이 가능할 것이다 :
Hashables는 불변이어야한다. - 이것을 강요하지는 말고, 처음으로 키로 사용한 후에는 dict을 변형시키지 말라. 다음과 같은 접근 방식이 가능할 것이다 :
class hashabledict(dict): def __key(self): return tuple((k,self[k]) for k in sorted(self)) def __hash__(self): return hash(self.__key()) def __eq__(self, other): return self.__key() == other.__key()
당신의 딕트를 돌연변이 할 필요가 있고 여전히 그것들을 키로 사용하기를 원한다면, 복잡성은 백 배가됩니다. 말할 수는 없지만 그 놀라운 털갈이에 들어가기 전에 나는 아주 구체적인 표시가 나타날 때까지 기다릴 것입니다! -)
-
==============================
3.사전을 목적에 맞게 사용하는 데 필요한 모든 것은 __hash__ 메소드를 추가하는 것입니다.
사전을 목적에 맞게 사용하는 데 필요한 모든 것은 __hash__ 메소드를 추가하는 것입니다.
class Hashabledict(dict): def __hash__(self): return hash(frozenset(self))
모든 사전에 대해 frozenset 변환이 가능합니다 (즉, 키를 정렬 할 필요가 없음). 마찬가지로, 사전 값에는 제한이 없습니다.
동일한 키가 있지만 값이 다른 사전이 많은 경우 해시에 값을 고려해야합니다. 가장 빠른 방법은 다음과 같습니다.
class Hashabledict(dict): def __hash__(self): return hash((frozenset(self), frozenset(self.itervalues())))
이것은 두 가지 이유로 frozenset (self.iteritems ())보다 빠릅니다. 첫째, frozenset (self) 단계는 사전에 저장된 해시 값을 다시 사용하여 해시 (key)에 불필요한 호출을 저장합니다. 둘째, itervalues를 사용하면 직접 값에 액세스 할 수 있으며 조회를 할 때마다 메모리에 새로운 많은 키 / 값 튜플을 형성하기 위해 항목별로 사용하는 많은 메모리 할당 자 호출을 피할 수 있습니다.
-
==============================
4.주어진 답변은 괜찮지 만 튜플 (sorted (...)) 대신에 frozenset (...)을 사용하여 해시를 생성하여 향상시킬 수 있습니다.
주어진 답변은 괜찮지 만 튜플 (sorted (...)) 대신에 frozenset (...)을 사용하여 해시를 생성하여 향상시킬 수 있습니다.
>>> import timeit >>> timeit.timeit('hash(tuple(sorted(d.iteritems())))', "d = dict(a=3, b='4', c=2345, asdfsdkjfew=0.23424, x='sadfsadfadfsaf')") 4.7758948802947998 >>> timeit.timeit('hash(frozenset(d.iteritems()))', "d = dict(a=3, b='4', c=2345, asdfsdkjfew=0.23424, x='sadfsadfadfsaf')") 1.8153600692749023
성능 이점은 사전의 내용에 달려 있지만, 대부분 테스트 한 결과, frozenset의 해시는 적어도 2 배 더 빠릅니다 (주로 정렬 할 필요가 없기 때문에).
-
==============================
5.합리적으로 깨끗하고 간단하게 구현할 수 있습니다.
합리적으로 깨끗하고 간단하게 구현할 수 있습니다.
import collections class FrozenDict(collections.Mapping): """Don't forget the docstrings!!""" def __init__(self, *args, **kwargs): self._d = dict(*args, **kwargs) def __iter__(self): return iter(self._d) def __len__(self): return len(self._d) def __getitem__(self, key): return self._d[key] def __hash__(self): return hash(tuple(sorted(self._d.iteritems())))
-
==============================
6.나는 계속이 주제로 돌아 간다 ... 또 다른 변형이있다. 나는 __hash__ 메쏘드를 추가하기 위해 dict를 하위 클래스 화하는 것이 쉽지 않다. dict의 변경이 가능하고 변경되지 않는다고 믿는 것은 약한 아이디어처럼 보입니다. 그래서 나는 그 자체로 불변 인 내장 타입에 기반한 매핑을 만드는 것을 보았습니다. 튜플은 명백한 선택이지만, 값을 액세스하는 것은 일종의 양분을 의미한다. 문제는 아니지만 그것이 구축 한 유형의 힘을 최대한 활용하는 것 같지 않습니다.
나는 계속이 주제로 돌아 간다 ... 또 다른 변형이있다. 나는 __hash__ 메쏘드를 추가하기 위해 dict를 하위 클래스 화하는 것이 쉽지 않다. dict의 변경이 가능하고 변경되지 않는다고 믿는 것은 약한 아이디어처럼 보입니다. 그래서 나는 그 자체로 불변 인 내장 타입에 기반한 매핑을 만드는 것을 보았습니다. 튜플은 명백한 선택이지만, 값을 액세스하는 것은 일종의 양분을 의미한다. 문제는 아니지만 그것이 구축 한 유형의 힘을 최대한 활용하는 것 같지 않습니다.
열쇠, 가치 쌍을 frozenset에 넣으면 어떨까요? 그것이 필요한 것은 무엇이며 어떻게 작동할까요?
1 부, 당신은 'frozenset이 키에 의해 주로 그들을 다룰 것 같은 방식으로'항목을 인코딩하는 방법이 필요합니다. 나는 그것을 위해 약간의 서브 클래스를 만들 것이다.
import collections class pair(collections.namedtuple('pair_base', 'key value')): def __hash__(self): return hash((self.key, None)) def __eq__(self, other): if type(self) != type(other): return NotImplemented return self.key == other.key def __repr__(self): return repr((self.key, self.value))
이것만으로도 불변의 맵핑이 가능합니다.
>>> frozenset(pair(k, v) for k, v in enumerate('abcd')) frozenset([(0, 'a'), (2, 'c'), (1, 'b'), (3, 'd')]) >>> pairs = frozenset(pair(k, v) for k, v in enumerate('abcd')) >>> pair(2, None) in pairs True >>> pair(5, None) in pairs False >>> goal = frozenset((pair(2, None),)) >>> pairs & goal frozenset([(2, None)])
D' oh! 불행히도 집합 연산자를 사용할 때 요소는 동일하지만 같은 객체는 아닙니다. 어느 쪽이 반환 값으로 끝나는지는 정의되어 있지 않습니다. 우리는 더 많은 선회를해야 할 것입니다.
>>> pairs - (pairs - goal) frozenset([(2, 'c')]) >>> iter(pairs - (pairs - goal)).next().value 'c'
그러나 이러한 방식으로 값을 찾는 것은 번거롭고, 더 나쁜 경우 많은 중간 세트를 만듭니다. 그럴 수 없어! 우리는 주위를 둘러보기 위해 '가짜'키 - 값 쌍을 만듭니다.
class Thief(object): def __init__(self, key): self.key = key def __hash__(self): return hash(pair(self.key, None)) def __eq__(self, other): self.value = other.value return pair(self.key, None) == other
덜 문제가되는 결과는 다음과 같습니다.
>>> thief = Thief(2) >>> thief in pairs True >>> thief.value 'c'
그것은 모두 깊은 마법입니다. 나머지는 그 모든 것을 위와 같은 인터페이스를 가진 무언가로 싸고있다. 우리는 매우 다른 인터페이스를 가진 frozenset으로부터 서브 클래 싱을하기 때문에 꽤 많은 메소드가 있습니다. 우리는 콜렉션에서 약간의 도움을 얻습니다. 매핑. 그러나 대부분의 작업은 대신 dicts처럼 작동하는 버전의 frozenset 메소드를 오버라이드합니다.
class FrozenDict(frozenset, collections.Mapping): def __new__(cls, seq=()): return frozenset.__new__(cls, (pair(k, v) for k, v in seq)) def __getitem__(self, key): thief = Thief(key) if frozenset.__contains__(self, thief): return thief.value raise KeyError(key) def __eq__(self, other): if not isinstance(other, FrozenDict): return dict(self.iteritems()) == other if len(self) != len(other): return False for key, value in self.iteritems(): try: if value != other[key]: return False except KeyError: return False return True def __hash__(self): return hash(frozenset(self.iteritems())) def get(self, key, default=None): thief = Thief(key) if frozenset.__contains__(self, thief): return thief.value return default def __iter__(self): for item in frozenset.__iter__(self): yield item.key def iteritems(self): for item in frozenset.__iter__(self): yield (item.key, item.value) def iterkeys(self): for item in frozenset.__iter__(self): yield item.key def itervalues(self): for item in frozenset.__iter__(self): yield item.value def __contains__(self, key): return frozenset.__contains__(self, pair(key, None)) has_key = __contains__ def __repr__(self): return type(self).__name__ + (', '.join(repr(item) for item in self.iteritems())).join('()') @classmethod def fromkeys(cls, keys, value=None): return cls((key, value) for key in keys)
궁극적으로 내 자신의 질문에 대답합니다.
>>> myDict = {} >>> myDict[FrozenDict(enumerate('ab'))] = 5 >>> FrozenDict(enumerate('ab')) in myDict True >>> FrozenDict(enumerate('bc')) in myDict False >>> FrozenDict(enumerate('ab', 3)) in myDict False >>> myDict[FrozenDict(enumerate('ab'))] 5
-
==============================
7.허용 된 @Unknown의 응답과 @AlexMartelli의 응답은 완벽하게 정상적으로 작동하지만 다음 제약 조건 하에서 만 작동합니다.
허용 된 @Unknown의 응답과 @AlexMartelli의 응답은 완벽하게 정상적으로 작동하지만 다음 제약 조건 하에서 만 작동합니다.
@ ObenSonne에 의한 훨씬 빠른 대답은 제약 2와 제약 3을 들었지만 제약 1 (값은 해시 가능해야 함)에 여전히 묶여 있습니다.
@RaymondHettinger가 대답하는 해쉬 계산에서 .values ()를 포함하지 않기 때문에 더 빠르고 응답이 빠릅니다. 그러나 다음과 같은 경우에만 성능이 좋습니다.
이 조건이 충족되지 않으면 해시 함수는 계속 유효하지만 너무 많은 충돌이 발생할 수 있습니다. 예를 들어 모든 사전이 웹 사이트 템플릿 (키로 필드 이름, 값으로 사용자 입력)에서 생성되는 극단적 인 경우 키는 항상 같고 해시 함수는 모든 입력에 대해 동일한 값을 반환합니다 . 결과적으로 해시 함수에 의존하는 해시 테이블은 항목 (O (1) 대신 O (N))을 검색 할 때 목록만큼 느려집니다.
위의 4 가지 제약 조건을 모두 위반하더라도 다음 솔루션이 상당히 잘 작동한다고 생각합니다. 사전에 중첩 된 변경 가능한 컨테이너가 있더라도 해시 할 수있는 장점이 있습니다.
지금까지 가볍게 테스트 한 이후로 나는 이것에 대한 피드백을 많이 주셔서 감사합니다.
# python 3.4 import collections import operator import sys import itertools import reprlib # a wrapper to make an object hashable, while preserving equality class AutoHash: # for each known container type, we can optionally provide a tuple # specifying: type, transform, aggregator # even immutable types need to be included, since their items # may make them unhashable # transformation may be used to enforce the desired iteration # the result of a transformation must be an iterable # default: no change; for dictionaries, we use .items() to see values # usually transformation choice only affects efficiency, not correctness # aggregator is the function that combines all items into one object # default: frozenset; for ordered containers, we can use tuple # aggregator choice affects both efficiency and correctness # e.g., using a tuple aggregator for a set is incorrect, # since identical sets may end up with different hash values # frozenset is safe since at worst it just causes more collisions # unfortunately, no collections.ABC class is available that helps # distinguish ordered from unordered containers # so we need to just list them out manually as needed type_info = collections.namedtuple( 'type_info', 'type transformation aggregator') ident = lambda x: x # order matters; first match is used to handle a datatype known_types = ( # dict also handles defaultdict type_info(dict, lambda d: d.items(), frozenset), # no need to include set and frozenset, since they are fine with defaults type_info(collections.OrderedDict, ident, tuple), type_info(list, ident, tuple), type_info(tuple, ident, tuple), type_info(collections.deque, ident, tuple), type_info(collections.Iterable, ident, frozenset) # other iterables ) # hash_func can be set to replace the built-in hash function # cache can be turned on; if it is, cycles will be detected, # otherwise cycles in a data structure will cause failure def __init__(self, data, hash_func=hash, cache=False, verbose=False): self._data=data self.hash_func=hash_func self.verbose=verbose self.cache=cache # cache objects' hashes for performance and to deal with cycles if self.cache: self.seen={} def hash_ex(self, o): # note: isinstance(o, Hashable) won't check inner types try: if self.verbose: print(type(o), reprlib.repr(o), self.hash_func(o), file=sys.stderr) return self.hash_func(o) except TypeError: pass # we let built-in hash decide if the hash value is worth caching # so we don't cache the built-in hash results if self.cache and id(o) in self.seen: return self.seen[id(o)][0] # found in cache # check if o can be handled by decomposing it into components for typ, transformation, aggregator in AutoHash.known_types: if isinstance(o, typ): # another option is: # result = reduce(operator.xor, map(_hash_ex, handler(o))) # but collisions are more likely with xor than with frozenset # e.g. hash_ex([1,2,3,4])==0 with xor try: # try to frozenset the actual components, it's faster h = self.hash_func(aggregator(transformation(o))) except TypeError: # components not hashable with built-in; # apply our extended hash function to them h = self.hash_func(aggregator(map(self.hash_ex, transformation(o)))) if self.cache: # storing the object too, otherwise memory location will be reused self.seen[id(o)] = (h, o) if self.verbose: print(type(o), reprlib.repr(o), h, file=sys.stderr) return h raise TypeError('Object {} of type {} not hashable'.format(repr(o), type(o))) def __hash__(self): return self.hash_ex(self._data) def __eq__(self, other): # short circuit to save time if self is other: return True # 1) type(self) a proper subclass of type(other) => self.__eq__ will be called first # 2) any other situation => lhs.__eq__ will be called first # case 1. one side is a subclass of the other, and AutoHash.__eq__ is not overridden in either # => the subclass instance's __eq__ is called first, and we should compare self._data and other._data # case 2. neither side is a subclass of the other; self is lhs # => we can't compare to another type; we should let the other side decide what to do, return NotImplemented # case 3. neither side is a subclass of the other; self is rhs # => we can't compare to another type, and the other side already tried and failed; # we should return False, but NotImplemented will have the same effect # any other case: we won't reach the __eq__ code in this class, no need to worry about it if isinstance(self, type(other)): # identifies case 1 return self._data == other._data else: # identifies cases 2 and 3 return NotImplemented d1 = {'a':[1,2], 2:{3:4}} print(hash(AutoHash(d1, cache=True, verbose=True))) d = AutoHash(dict(a=1, b=2, c=3, d=[4,5,6,7], e='a string of chars'),cache=True, verbose=True) print(hash(d))
-
==============================
8.v2 pickling 프로토콜이 hashdict 인스턴스와 작동하도록하려면이 두 가지 메소드를 추가해야 할 수도 있습니다. 그렇지 않으면 cPickle은 TypeError를 발생시키는 hashdict .____ setitem____을 사용하려고 시도합니다. 흥미롭게도 프로토콜의 다른 두 버전에서는 코드가 잘 작동합니다.
v2 pickling 프로토콜이 hashdict 인스턴스와 작동하도록하려면이 두 가지 메소드를 추가해야 할 수도 있습니다. 그렇지 않으면 cPickle은 TypeError를 발생시키는 hashdict .____ setitem____을 사용하려고 시도합니다. 흥미롭게도 프로토콜의 다른 두 버전에서는 코드가 잘 작동합니다.
def __setstate__(self, objstate): for k,v in objstate.items(): dict.__setitem__(self,k,v) def __reduce__(self): return (hashdict, (), dict(self),)
-
==============================
9.사전에 숫자를 입력하지 않고 사전이 포함 된 변수를 잃지 않으면 다음을 수행 할 수 있습니다.
사전에 숫자를 입력하지 않고 사전이 포함 된 변수를 잃지 않으면 다음을 수행 할 수 있습니다.
캐시 [아이디 (규칙)] = "뭐든지"
id ()는 모든 사전에 대해 고유하므로
편집하다:
오, 죄송합니다. 다른 사람들이 말한 것이 더 낳을 것입니다. 나는 또한 당신이 사전처럼 문자열을 직렬화 할 수 있다고 생각한다.
캐시 [ 'fu : bar'] = '번개'
그래도 열쇠에서 사전을 복구해야한다면, 다음과 같이 더 못된 일을해야 할 것입니다.
캐시 [ 'fu : bar'] = ({ 'fu :'bar '},'번개 ')
이 코드의 장점은 많은 코드를 작성할 필요가 없다는 것입니다.
from https://stackoverflow.com/questions/1151658/python-hashable-dicts by cc-by-sa and MIT license
'PYTHON' 카테고리의 다른 글
[PYTHON] URL에서 최상위 도메인 이름 (TLD)을 추출하는 방법 (0) | 2018.10.03 |
---|---|
[PYTHON] 파이썬 / 팬더가 저장된 csv에서 색인을 생성하는 것을 피하는 방법? (0) | 2018.10.03 |
[PYTHON] virtualenv 또는 buildout을 사용하여 PIL을 설치하는 경우의 문제점 (0) | 2018.10.03 |
[PYTHON] 파이썬에서 알파벳순으로 유니 코드 문자열을 정렬하려면 어떻게해야합니까? (0) | 2018.10.03 |
[PYTHON] 수위가 낮은 배열에 항목을 추가하는 방법 (0) | 2018.10.03 |