defaultdict의 default_factory에 키를 전달하는 영리한 방법이 있습니까?


93

클래스에는 하나의 매개 변수를 취하는 생성자가 있습니다.

class C(object):
    def __init__(self, v):
        self.v = v
        ...

코드 어딘가에서 dict의 값이 키를 알고 있으면 유용합니다.
신생아 기본값에 전달 된 키와 함께 defaultdict를 사용하고 싶습니다.

d = defaultdict(lambda : C(here_i_wish_the_key_to_be))

어떤 제안?

답변:


127

그것은 거의 자격 없다 영리한 -하지만 서브 클래스는 당신의 친구입니다 :

class keydefaultdict(defaultdict):
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError( key )
        else:
            ret = self[key] = self.default_factory(key)
            return ret

d = keydefaultdict(C)
d[x] # returns C(x)

16
그것이 바로 제가 피하려는 추악함입니다 ... 단순한 dict를 사용하고 키 존재를 확인하는 것조차 훨씬 더 깔끔합니다.
Benjamin Nitlehoo

1
@Paul : 그러나 이것이 당신의 대답입니다. 추함? 어서!
tzot

4
이 코드를 개인화 된 일반 유틸리티 모듈에 넣어서 원할 때마다 사용할 수 있도록 할 것입니다. 그렇게 못
생기지 않고

24
+1 OP의 질문을 직접 처리하고 나에게 "추악"해 보이지 않습니다. 또한 많은 사람들이 defaultdict__missing__()메서드가 재정의 될 수 있다는 것을 인식하지 못하는 것 같기 때문에 좋은 대답입니다 ( dict버전 2.5 이후 의 내장 클래스의 모든 하위 클래스에서 가능 ).
martineau

7
+1 __missing__의 전체 목적은 누락 된 키에 대한 동작을 사용자 지정하는 것입니다. @silentghost가 언급 한 dict.setdefault () 접근 방식도 작동합니다 (플러스 쪽에서는 setdefault ()가 짧고 이미 존재합니다. 마이너스 쪽에서는 효율성 문제가 발생하고 "setdefault"라는 이름을 좋아하는 사람은 없습니다) .
Raymond Hettinger 2016

26

아니 없어.

defaultdict누락 된 항목 key을 기본적 으로 전달하도록 구현을 구성 할 수 없습니다 default_factory. 유일한 옵션은 직접 구현하는 것입니다.defaultdict 위의 @JochenRitzel이 제안한대로 하위 클래스 입니다.

그러나 이것은 "영리"하거나 표준 라이브러리 솔루션만큼 깔끔하지도 않습니다 (존재한다면). 따라서 귀하의 간결한 예 / 아니오 질문에 대한 대답은 분명히 "아니오"입니다.

표준 라이브러리에 자주 필요한 도구가 없다는 것은 너무 나쁩니다.


네, 공장이 키를 가져 오도록하는 것이 더 나은 설계 선택이었을 것입니다 (널리가 아닌 단항 함수). 상수를 반환하고 싶을 때 인수를 버리는 것은 쉽습니다.
YvesgereY

6

나는 당신이 defaultdict여기에 전혀 필요하지 않다고 생각합니다 . 왜 그냥 dict.setdefault방법을 사용하지 않습니까?

>>> d = {}
>>> d.setdefault('p', C('p')).v
'p'

물론 많은 인스턴스가 생성 C됩니다. 문제인 경우 더 간단한 접근 방식이 효과가 있다고 생각합니다.

>>> d = {}
>>> if 'e' not in d: d['e'] = C('e')

defaultdict내가 볼 수있는 한 다른 대안 보다 빠를 것 입니다.

in테스트 속도 와 try-except 절 사용 에 관한 ETA :

>>> def g():
    d = {}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(g)
0.19638929363557622
>>> def f():
    d = {}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(f)
0.6167065411074759
>>> def k():
    d = {'a': 2}
    if 'a' in d:
        return d['a']


>>> timeit.timeit(k)
0.30074866358404506
>>> def p():
    d = {'a': 2}
    try:
        return d['a']
    except KeyError:
        return


>>> timeit.timeit(p)
0.28588609450770264

7
이는 d에 여러 번 액세스하고 키가 거의 누락되는 경우 매우 낭비 적입니다. 따라서 C (key)는 GC가 수집 할 불필요한 개체를 생성합니다. 또한 제 경우에는 새로운 C 객체를 만드는 것이 느리기 때문에 추가적인 고통이 있습니다.
Benjamin Nitlehoo

@ 폴 : 맞습니다. 나는 더 간단한 방법을 제안하고 내 편집을 참조하십시오.
SilentGhost

나는 그것이 defaultdict보다 빠르다는 것을 확신하지 못하지만 이것은 내가 일반적으로하는 일입니다 (THC4k의 답변에 대한 내 의견 참조). 코드를 약간 더 우아하게 유지하기 위해 default_factory가 인수를 취하지 않는다는 사실을 해킹하는 간단한 방법이 있기를 바랐습니다.
Benjamin Nitlehoo

5
@SilentGhost : 이해가 안 돼요-이것이 OP의 문제를 어떻게 해결하나요? 나는 영업 이익은 읽을 시도 원한다고 생각 d[key]반환하는 d[key] = C(key)경우를 key not in d. 그러나 당신의 솔루션은 그가 실제로 가서 미리 미리 설정 d[key]해야합니까? key그가 필요한 것이 무엇 인지 어떻게 알 수 있습니까?
최대

2
setdefault는 지옥처럼 추하고 컬렉션의 defaultdict는 키를받는 공장 기능을 지원해야하기 때문입니다. 파이썬 디자이너들에게 낭비되는 기회입니다!
jgomo3

0

다음은 자동으로 값을 추가하는 사전의 작동 예입니다. / usr / include에서 중복 파일을 찾는 데모 작업. 사용자 지정 사전 PathDict 에는 네 줄만 필요합니다.

class FullPaths:

    def __init__(self,filename):
        self.filename = filename
        self.paths = set()

    def record_path(self,path):
        self.paths.add(path)

class PathDict(dict):

    def __missing__(self, key):
        ret = self[key] = FullPaths(key)
        return ret

if __name__ == "__main__":
    pathdict = PathDict()
    for root, _, files in os.walk('/usr/include'):
        for f in files:
            path = os.path.join(root,f)
            pathdict[f].record_path(path)
    for fullpath in pathdict.values():
        if len(fullpath.paths) > 1:
            print("{} located in {}".format(fullpath.filename,','.join(fullpath.paths)))
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.