'setdefault'dict 메소드의 사용 사례


192

collections.defaultdictPython 2.5에 추가 하면 dictsetdefault메소드에 대한 필요성이 크게 줄어 들었습니다 . 이 질문은 우리의 집단 교육을위한 것입니다.

  1. setdefault오늘날 Python 2.6 / 2.7에서 여전히 유용한 것은 무엇입니까 ?
  2. 어떤 일반적인 사용 사례 setdefault로 대체 collections.defaultdict되었습니까?

답변:


208

당신이 말할 수는 defaultdict설정 기본값에 유용 DICT를 작성하기 전에setdefault기본값을 설정하는 데 유용 동안 또는 DICT를 작성 후 .

아마도 가장 일반적인 유스 케이스 : 항목 그룹화 (정렬되지 않은 데이터, 그렇지 않으면 사용 itertools.groupby)

# really verbose
new = {}
for (key, value) in data:
    if key in new:
        new[key].append( value )
    else:
        new[key] = [value]


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # key might exist already
    group.append( value )


# even simpler with defaultdict 
from collections import defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append( value ) # all keys have a default already

때로는 dict를 만든 후 특정 키가 있는지 확인하려고합니다. defaultdict이 경우 명시 적 액세스에서만 키를 생성하기 때문에 작동하지 않습니다. 많은 헤더와 함께 HTTP를 사용한다고 생각하십시오. 일부는 선택 사항이지만 기본값을 원합니다.

headers = parse_headers( msg ) # parse the message, get a dict
# now add all the optional headers
for headername, defaultvalue in optional_headers:
    headers.setdefault( headername, defaultvalue )

1
실제로이 IMHO는에 의해 대체되는 주요 사용 사례입니다 defaultdict. 첫 번째 단락에서 무슨 뜻인지 예를 들어 줄 수 있습니까?
Eli Bendersky

2
Muhammad Alkarouri : 먼저해야 할 일은 dict을 복사 한 다음 일부 항목을 덮어 씁니다. 나도 그 일을 많이하고 실제로 그것이 가장 선호하는 관용구 인 것 같습니다 setdefault. 반면 defaultdict에 A 가 모두 defaultvalues같지 않으면 (즉, 일부는 0있고 일부는 []) 작동하지 않습니다 .
Jochen Ritzel

2
@ YHC4k입니다. 내가 사용한 이유 headers = dict(optional_headers)입니다. 기본값이 모두 같지 않은 경우. 그리고 최종 결과는 HTTP 헤더를 먼저 얻은 다음 얻지 못한 기본값을 설정하는 것과 같습니다. 그리고 이미 가지고 있다면 꽤 사용할 수 있습니다 optional_headers. 주어진 2 단계 코드를 시도하여 귀하의 코드와 비교하면 내가 무엇을 의미하는지 알 수 있습니다.
Muhammad Alkarouri

19
또는 그냥new.setdefault(key, []).append(value)
fmalina

2
가장 좋은 대답 defaultdict이 낫다는 것이 이상하다는 것을 알았 습니다 setdefault(지금 유스 케이스는 어디에 있습니까?). 또한 IMO 예제를 ChainMap더 잘 처리 할 수 ​​있습니다 http.
YvesgereY

29

setdefault이 함수와 같이 키워드 인수 dicts에 일반적으로 사용 합니다.

def notify(self, level, *pargs, **kwargs):
    kwargs.setdefault("persist", level >= DANGER)
    self.__defcon.set(level, **kwargs)
    try:
        kwargs.setdefault("name", self.client.player_entity().name)
    except pytibia.PlayerEntityNotFound:
        pass
    return _notify(level, *pargs, **kwargs)

키워드 인수를 취하는 함수 주위의 래퍼에서 인수를 조정하는 데 좋습니다.


16

defaultdict 새 목록처럼 기본값이 정적 일 때 좋지만 동적 인 경우에는 그다지 많지 않습니다.

예를 들어 문자열을 고유 한 정수로 매핑하려면 사전이 필요합니다. defaultdict(int)항상 기본값으로 0을 사용합니다. 마찬가지로 defaultdict(intGen())항상 1을 생성합니다.

대신, 나는 정규 dict을 사용했습니다.

nextID = intGen()
myDict = {}
for lots of complicated stuff:
    #stuff that generates unpredictable, possibly already seen str
    strID = myDict.setdefault(myStr, nextID())

참고 dict.get(key, nextID())나중에뿐만 아니라이 값을 참조 할 수 있어야하기 때문에 불충분하다.

intGen int를 자동으로 증가시키고 그 값을 반환하는 작은 클래스입니다.

class intGen:
    def __init__(self):
        self.i = 0

    def __call__(self):
        self.i += 1
    return self.i

누군가 이것을 할 수있는 방법이 있다면 defaultdict그것을보고 싶습니다.


(하위 클래스의) defaultdict로 수행하는 방법은 다음 질문을 참조하십시오. stackoverflow.com/questions/2912231/…
weronika

8
으로 교체 할 수 intGen있습니다 itertools.count().next.
안티몬

7
nextID()myDict.setdefault()반환되는 값이로 사용되지 않더라도 호출 될 때마다의 값이 증가 합니다 strID. 이것은 어쨌든 쓸모없는 것처럼 보이며 내가 setdefault()일반적으로 좋아하지 않는 것 중 하나를 보여줍니다. 즉 default, 실제로 사용되는지 여부를 항상 평가합니다 .
martineau

당신은 그것을 할 수 있습니다 defaultdict: myDict = defaultdict(lambda: nextID()). 나중에 strID = myDict[myStr]루프에서.
musiphil

3
defaultdict로 설명하는 동작을 얻으려면 왜 그렇지 myDict = defaultdict(nextID)않습니까?
forty_two

10

setdefault()에서 기본값을 원할 때 사용 합니다 OrderedDict. 두 가지 를 모두 수행하는 표준 Python 컬렉션은 없지만 이러한 컬렉션을 구현 하는 방법 이 있습니다.


10

대부분의 답변 상태 setdefault또는 defaultdict키가 존재하지 않을 때 기본값을 설정할 수 있습니다. 그러나의 사용 사례와 관련하여 작은주의 사항을 지적하고 싶습니다 setdefault. 파이썬 인터프리터가 실행될 때 setdefault키가 사전에 존재하더라도 항상 함수에 대한 두 번째 인수를 평가합니다. 예를 들면 다음과 같습니다.

In: d = {1:5, 2:6}

In: d
Out: {1: 5, 2: 6}

In: d.setdefault(2, 0)
Out: 6

In: d.setdefault(2, print('test'))
test
Out: 6

보시다시피 print, 사전에 2가 이미 존재하더라도 실행되었습니다. setdefault예를 들어와 같은 최적화 에 사용하려는 경우 특히 중요합니다 memoization. 에 대한 두 번째 인수로 재귀 함수 호출을 추가하면 setdefault파이썬이 항상 함수를 재귀 적으로 호출하므로 성능을 얻지 못합니다.

메모가 언급되었으므로 메모로 함수를 향상시키는 것을 고려하는 경우 functools.lru_cache 데코레이터를 사용하는 것이 더 나은 대안입니다. lru_cache는 재귀 함수에 대한 캐싱 요구 사항을보다 잘 처리합니다.


8

Muhammad가 말했듯이 때때로 기본값을 설정하려는 상황이 있습니다. 이에 대한 좋은 예는 데이터 구조가 먼저 채워지고 쿼리됩니다.

트라이를 고려하십시오. 단어를 추가 할 때 하위 노드가 필요하지만 존재하지 않는 경우 트리를 확장하기 위해 하위 노드를 만들어야합니다. 단어의 존재를 쿼리 할 때 누락 된 하위 노드는 해당 단어가 존재하지 않으며 단어를 만들지 않아야 함을 나타냅니다.

defaultdict는 이것을 할 수 없습니다. 대신 get 및 setdefault 메소드를 사용하는 일반 dict를 사용해야합니다.


5

이론적으로 말하면 때로는 기본값을 설정하고 때로는하지 않으려는 setdefault경우 여전히 유용합니다 . 실생활에서는 그런 유스 케이스를 보지 못했습니다.

그러나 흥미로운 사용 사례는 표준 라이브러리 (Python 2.6, _threadinglocal.py)에서 나옵니다.

>>> mydata = local()
>>> mydata.__dict__
{'number': 42}
>>> mydata.__dict__.setdefault('widgets', [])
[]
>>> mydata.widgets
[]

나는 사용 __dict__.setdefault이 꽤 유용한 경우 라고 말할 것 입니다.

편집 : 발생하는 것처럼 이것은 표준 라이브러리의 유일한 예이며 주석입니다. 따라서의 존재를 정당화하는 것만으로는 충분하지 않을 수 있습니다 setdefault. 여전히 여기에 설명이 있습니다.

객체는 속성에 속성을 저장 __dict__합니다. 그와 같이, __dict__속성은 객체 생성 후 언제든지 쓸 수 있습니다. 또한 사전이 아닙니다 defaultdict. 일반적인 경우의 객체가 가지고하는 것은 분별없는 __dict__A와 defaultdict그 속성과 모든 법적 식별자를 가진 각 개체를 만들 것 때문이다. 따라서 __dict__.setdefault유용하지 않은 것으로 간주되면 완전히 삭제하는 것 외에는 제거하는 Python 객체의 변경을 예측할 수 없습니다 .


1
_dict .setdefault가 특히 유용한 이유는 무엇 입니까?
Eli Bendersky

1
@ Elli : 요점은 __dict__구현이 dict아니라 a defaultdict입니다.
Katriel

1
좋구나. setdefault파이썬 에 머무르는 것에 대해서는 신경 쓰지 않지만 이제는 거의 쓸모가 없다는 것이 궁금합니다.
Eli Bendersky

@Eli : 동의합니다. 나는 그것이 그것이 없다면 오늘 소개 될 충분한 이유가 있다고 생각하지 않습니다. 그러나 이미 존재한다면 모든 코드가 이미 사용 중이라면 제거를 주장하기가 어려울 것입니다.
Muhammad Alkarouri

1
방어 프로그래밍중인 파일. setdefault존재하거나 존재하지 않는 키를 통해 dict에 할당하고 있음을 명시하고 존재하지 않는 경우 기본값으로 작성하려고합니다 d.setdefault(key,[]).append(value). 예 : . 다른 곳에서 당신이 프로그램에서 alist=d[k]k는 계산되며, 당신이 K 경우 예외가 발생 싶어하지 않는 defaultdict로 필요할 수 있습니다 D (에 assert k in d또는if not ( k in d): raise KeyError
nigel222

3

의 한 가지 단점 defaultdict이상은 dict( dict.setdefault)는 점이다 defaultdict오브젝트가 새 항목 생성 매번 (로 예를 들어, 기존의 키가 주어진다 비 ==, print). 또한 defaultdict클래스는 일반적으로 클래스보다 덜 일반적 dict이며 IME를 직렬화하기가 더 어렵습니다.

PS IMO 기능은 대상을 돌연변이시키는 것은 아니며, 대상을 돌연변이해서는 안된다.


매번 새 객체를 만들 필요는 없습니다. defaultdict(lambda l=[]: l)대신 쉽게 할 수 있습니다 .
Artyer

6
@Artyer가 제안한 것을 절대로하지 마십시오.
Brandon Humpert

2

다음은 setdefault의 유용성을 보여주는 몇 가지 예입니다.

"""
d = {}
# To add a key->value pair, do the following:
d.setdefault(key, []).append(value)

# To retrieve a list of the values for a key
list_of_values = d[key]

# To remove a key->value pair is still easy, if
# you don't mind leaving empty lists behind when
# the last value for a given key is removed:
d[key].remove(value)

# Despite the empty lists, it's still possible to 
# test for the existance of values easily:
if d.has_key(key) and d[key]:
    pass # d has some values for key

# Note: Each value can exist multiple times!
"""
e = {}
print e
e.setdefault('Cars', []).append('Toyota')
print e
e.setdefault('Motorcycles', []).append('Yamaha')
print e
e.setdefault('Airplanes', []).append('Boeing')
print e
e.setdefault('Cars', []).append('Honda')
print e
e.setdefault('Cars', []).append('BMW')
print e
e.setdefault('Cars', []).append('Toyota')
print e

# NOTE: now e['Cars'] == ['Toyota', 'Honda', 'BMW', 'Toyota']
e['Cars'].remove('Toyota')
print e
# NOTE: it's still true that ('Toyota' in e['Cars'])

2

나는 받아 들여진 대답을 다시 쓰고 초보자를 위해 그것을 촉진시킵니다.

#break it down and understand it intuitively.
new = {}
for (key, value) in data:
    if key not in new:
        new[key] = [] # this is core of setdefault equals to new.setdefault(key, [])
        new[key].append(value)
    else:
        new[key].append(value)


# easy with setdefault
new = {}
for (key, value) in data:
    group = new.setdefault(key, []) # it is new[key] = []
    group.append(value)



# even simpler with defaultdict
new = defaultdict(list)
for (key, value) in data:
    new[key].append(value) # all keys have a default value of empty list []

또한 메소드를 참조로 분류했습니다.

dict_methods_11 = {
            'views':['keys', 'values', 'items'],
            'add':['update','setdefault'],
            'remove':['pop', 'popitem','clear'],
            'retrieve':['get',],
            'copy':['copy','fromkeys'],}

1

사전에 기본값 (!!!)을 설정할 때 setdefault를 자주 사용합니다. 다소 일반적으로 os.environ 사전 :

# Set the venv dir if it isn't already overridden:
os.environ.setdefault('VENV_DIR', '/my/default/path')

간결하게 말하면 다음과 같습니다.

# Set the venv dir if it isn't already overridden:
if 'VENV_DIR' not in os.environ:
    os.environ['VENV_DIR'] = '/my/default/path')

결과 변수를 사용할 수도 있습니다.

venv_dir = os.environ.setdefault('VENV_DIR', '/my/default/path')

그러나 불이행이 존재하기 전에 필요한 것보다 덜 필요합니다.


1

내가 생각하지 않는 또 다른 유스 케이스는 위에서 언급했다. 때로는 기본 인스턴스가 캐시에 있고 ID가 없을 때 캐시를 설정하려는 ID로 객체의 캐시 dict를 유지합니다.

return self.objects_by_id.setdefault(obj.id, obj)

매번 obj를 얻는 방법에 관계없이 항상 고유 한 ID마다 단일 인스턴스를 유지하려는 경우에 유용합니다. 예를 들어 객체 속성이 메모리에서 업데이트되고 스토리지에 대한 저장이 지연되는 경우.


1

방금 우연히 발견 한 하나의 매우 중요한 유스 케이스 : dict.setdefault()단일 표준 객체 만 원할 때 멀티 스레드 코드에 적합합니다 (동일한 여러 객체가 아닌).

예를 들어 (Int)FlagPython 3.6.0Enum에는 버그가 있습니다 . 여러 스레드가 복합 (Int)Flag멤버를 놓고 경쟁하는 경우 둘 이상의 결과가 발생할 수 있습니다.

from enum import IntFlag, auto
import threading

class TestFlag(IntFlag):
    one = auto()
    two = auto()
    three = auto()
    four = auto()
    five = auto()
    six = auto()
    seven = auto()
    eight = auto()

    def __eq__(self, other):
        return self is other

    def __hash__(self):
        return hash(self.value)

seen = set()

class cycle_enum(threading.Thread):
    def run(self):
        for i in range(256):
            seen.add(TestFlag(i))

threads = []
for i in range(8):
    threads.append(cycle_enum())

for t in threads:
    t.start()

for t in threads:
    t.join()

len(seen)
# 272  (should be 256)

해결 방법은 setdefault()계산 된 복합 멤버를 저장하는 마지막 단계로 사용하는 것입니다. 이미 저장된 다른 멤버는 새 멤버 대신 사용되어 고유 한 Enum 멤버를 보장합니다.


0

[편집] 매우 잘못되었습니다! setdefault는 항상 long_computation을 트리거하여 Python을 열망합니다.

터틀의 답변을 확장합니다. 나에게 가장 좋은 사용 사례는 캐시 메커니즘입니다. 대신에:

if x not in memo:
   memo[x]=long_computation(x)
return memo[x]

3 줄과 2 ~ 3 개의 조회를 사용 하면 행복하게 쓸 것입니다 .

return memo.setdefault(x, long_computation(x))

좋은 예입니다. 나는 여전히 3 줄이 더 이해하기 쉽다고 생각하지만, 아마도 내 두뇌가 기본 설정 값을 높이기 위해 자랄 것입니다.
Bob Stein

5
그것들은 동등하지 않습니다. 첫 번째 long_computation(x)는 if 만 호출됩니다 x not in memo. 두 번째 long_computation(x)는 항상 호출됩니다. 대입 만 조건부이며 동등한 코드 setdefault는 다음과 같습니다. v = long_computation(x)/ if x not in memo:/ memo[x] = v.
Dan D.


0

다른 사용 사례 는 이미 설정된 키의 값 을 덮어 쓰지 않으려setdefault()경우 입니다. defaultdict덮어 쓰지만 setdefault()그렇지 않습니다. 중첩 된 사전의 경우 현재 하위 사전을 제거하지 않기 위해 키가 아직 설정되지 않은 경우에만 기본값을 설정하려는 경우가 더 많습니다. 당신이 사용할 때 setdefault()입니다.

defaultdict:

>>> from collection import defaultdict()
>>> foo = defaultdict()
>>> foo['a'] = 4
>>> foo['a'] = 2
>>> print(foo)
defaultdict(None, {'a': 2})

setdefault 덮어 쓰지 않습니다 :

>>> bar = dict()
>>> bar.setdefault('a', 4)
>>> bar.setdefault('a', 2)
>>> print(bar)
{'a': 4}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.