반복하는 동안 Python dict 수정


87

Python 사전이 d있고 다음과 같이 반복 한다고 가정 해 보겠습니다 .

for k,v in d.iteritems():
    del d[f(k)] # remove some item
    d[g(k)] = v # add a new item

( f그리고 g일부 블랙 박스 변환입니다.)

즉,을 d사용하여 반복 하는 동안 항목을 추가 / 제거하려고합니다 iteritems.

잘 정의되어 있습니까? 답변을 뒷받침 할 참고 자료를 제공해 주시겠습니까?

(깨진 경우이 문제를 해결하는 방법은 매우 분명하므로 이것은 내가 추구하는 각도가 아닙니다.)




나는 이것을 시도했고 초기 dict 크기를 변경하지 않은 채로두면-예를 들어 키 / 값을 제거하는 대신 교체하면이 코드는 예외를 던지지 않을 것입니다
Artsiom Rudzenka

나는이 주제 (나 자신을 포함하여)를 검색하는 모든 사람들에게“깨졌을 때 이것을 고치는 방법이 매우 명백하다”는 것에 동의하지 않으며, 받아 들여진 대답이 적어도 이것에 대해 건 드리기를 바랍니다.
Alex Peters

답변:


53

Python 문서 페이지 ( Python 2.7 용 ) 에 명시 적으로 언급되어 있습니다.

iteritems()사전에 항목을 추가하거나 삭제하는 동안 사용하면 a가 발생 RuntimeError하거나 모든 항목을 반복하지 못할 수 있습니다.

Python 3과 유사합니다 .

iter(d), d.iterkeys()및 에도 동일 d.itervalues()하게 적용됩니다.for k, v in d.items(): (내가 정확히 기억하지 못하는 for구현 호출하면 않지만, 나는 놀라지 않을 것이다 iter(d)).


48
나는 바로 코드 스 니펫을 사용했다고 말함으로써 커뮤니티를 위해 자신을 당황하게 할 것입니다. RuntimeError가 발생하지 않았기 때문에 모든 것이 좋다고 생각했습니다. 그리고 그것은 잠시 동안이었습니다. 분석적으로 유지되는 단위 테스트는 나에게 엄지 손가락을줬고 출시되었을 때도 잘 실행되었습니다. 그러다 기괴한 행동이 시작되었습니다. 무슨 일이 있었는지 사전에있는 항목을 건너 뛰고 사전에있는 모든 항목이 스캔되지는 않았습니다. 아이들, 내 인생에서 저지른 실수로부터 배우고 그냥 거절하십시오! )
앨런 카브레라

3
현재 키의 값을 변경하는 경우 문제가 발생할 수 있습니까 (키를 추가하거나 제거하지 않습니까?). 이것이 문제를 일으키지 않아야한다고 생각하지만 알고 싶습니다!
Gershom 2015

@GershomMaes 나는 아무것도 모르지만 루프 본문이 값을 사용하고 값이 변경 될 것으로 예상하지 않으면 여전히 지뢰밭에 뛰어들 수 있습니다.
Raphaël Saint-Pierre

3
d.items()Python 2.7 (Python 3으로 게임이 변경됨)에서 안전해야합니다. 기본적으로을 복사 d하므로 반복하는 내용을 수정하지 않습니다.
Paul Price

이것이 또한 사실인지 아는 것은 흥미로울 것입니다viewitems()
jlh

50

Alex Martelli가 여기 에 무게를두고 있습니다. .

컨테이너를 반복하는 동안 컨테이너 (예 : dict)를 변경하는 것은 안전하지 않을 수 있습니다. 따라서 del d[f(k)]안전하지 않을 수 있습니다. 아시다시피 해결 방법은 d.items()대신 사용하는 것입니다 (컨테이너의 독립적 인 복사본을 반복 하기 위해 ).d.iteritems() 동일한 기본 컨테이너를 사용하는 ( 사용하는 것입니다.

dict 의 기존 인덱스 에서 값을 수정하는 것은 괜찮지 만 새 인덱스 (예 :)에 값을 삽입하면 d[g(k)]=v작동하지 않을 수 있습니다.


3
이것이 저에게 중요한 대답이라고 생각합니다. 많은 사용 사례에서 하나의 프로세스가 항목을 삽입하고 다른 프로세스가 항목을 정리 / 삭제하므로 d.items () 사용에 대한 조언이 작동합니다. 파이썬 3주의 사항 견딜 수 없습니다
easytiger

4
Python 3 경고에 대한 자세한 내용은 앞서 언급 한 Python 2 dict 메서드의 의미 론적 동등 항목이 열거 된 PEP 469 에서 찾을 수 있습니다 .
Lionel Brooks

1
"딕셔너리의 기존 인덱스에서 값을 수정해도 괜찮습니다." -이에 대한 참조가 있습니까?
Jonathon Reinhart

1
@JonathonReinhart : 아니요, 이에 대한 참조는 없지만 Python에서는 꽤 표준이라고 생각합니다. 예를 들어 Alex Martelli는 Python 핵심 개발자였으며 여기에서 사용법을 보여줍니다 .
unutbu

27

적어도 d.iteritems(). 나는 그것을 시도했지만 Python이 실패합니다.

RuntimeError: dictionary changed size during iteration

대신을 사용 d.items()하면 작동합니다.

Python 3에서는 Python 2 d.items()와 같이 사전에 대한보기 d.iteritems()입니다. Python 3에서이 작업을 수행하려면 대신 d.copy().items(). 이것은 우리가 반복하는 데이터 구조의 수정을 피하기 위해 딕셔너리의 복사본을 반복 할 수있게합니다.


2
내 대답에 Python 3을 추가했습니다.
murgatroid99

2
참고로, (에 의해 사용과 같은 예를 들어 직역 2to3Py2의의) d.items()Py3으로는 list(d.items()), 비록 d.copy().items()아마 비교 효율성이다.
Søren Løvborg 2013

2
dict 객체가 매우 큰 경우 d.copy (). items () 효율적입니까?
dragonfly

11

나는 Numpy 배열을 포함하는 큰 사전을 가지고 있으므로 @ murgatroid99가 제안한 dict.copy (). keys () 것은 실현 가능하지 않았습니다 (작동했지만). 대신 keys_view를 목록으로 변환했고 제대로 작동했습니다 (Python 3.4에서).

for item in list(dict_d.keys()):
    temp = dict_d.pop(item)
    dict_d['some_key'] = 1  # Some value

나는 이것이 위의 답변과 같은 Python의 내부 작동의 철학적 영역으로 뛰어 들지 않는다는 것을 알고 있지만 명시된 문제에 대한 실질적인 해결책을 제공합니다.


6

다음 코드는 이것이 잘 정의되지 않았 음을 보여줍니다.

def f(x):
    return x

def g(x):
    return x+1

def h(x):
    return x+10

try:
    d = {1:"a", 2:"b", 3:"c"}
    for k, v in d.iteritems():
        del d[f(k)]
        d[g(k)] = v+"x"
    print d
except Exception as e:
    print "Exception:", e

try:
    d = {1:"a", 2:"b", 3:"c"}
    for k, v in d.iteritems():
        del d[f(k)]
        d[h(k)] = v+"x"
    print d
except Exception as e:
    print "Exception:", e

첫 번째 예제는 g (k)를 호출하고 예외를 발생시킵니다 (반복 중에 사전 변경된 크기).

두 번째 예제는 h (k)를 호출하고 예외를 발생시키지 않지만 다음을 출력합니다.

{21: 'axx', 22: 'bxx', 23: 'cxx'}

코드를 보면 잘못된 것 같습니다. 다음과 같은 것을 예상했을 것입니다.

{11: 'ax', 12: 'bx', 13: 'cx'}

나는 당신이 왜 기대할 수 있는지 이해할 수 {11: 'ax', 12: 'bx', 13: 'cx'}있지만 21,22,23은 실제로 무슨 일이 일어 났는지에 대한 단서를 제공해야합니다. 귀하의 루프는 항목 1, 2, 3, 11, 12, 13을 통과했지만 두 번째 항목을 선택하지 못했습니다. 이미 반복 한 항목 앞에 삽입 된 새 항목의 라운드. 변경 h()반환 x+5하고 다른 X 얻을 : 'axxx'등 또는 'X + 3'하고 웅장한 얻을'axxxxx'
던컨

네, 제 실수는 두렵습니다. 제 예상 결과는 {11: 'ax', 12: 'bx', 13: 'cx'}당신이 말한 것과 같았 기 때문에 이에 대한 게시물을 업데이트하겠습니다. 어느 쪽이든, 이것은 명확하게 정의 된 행동이 아닙니다.
combatdave

1

나는 같은 문제가 있었고 다음 절차를 사용 하여이 문제를 해결했습니다.

Python List는 반복하는 동안 수정하더라도 반복 될 수 있습니다. 따라서 다음 코드의 경우 1을 무한대로 인쇄합니다.

for i in list:
   list.append(1)
   print 1

따라서 목록과 사전을 함께 사용하면이 문제를 해결할 수 있습니다.

d_list=[]
 d_dict = {} 
 for k in d_list:
    if d_dict[k] is not -1:
       d_dict[f(k)] = -1 # rather than deleting it mark it with -1 or other value to specify that it will be not considered further(deleted)
       d_dict[g(k)] = v # add a new item 
       d_list.append(g(k))

반복하는 동안 목록을 수정하는 것이 안전한지 확실하지 않습니다 (경우에 따라 작동 할 수도 있음). 예를
Roman

@Roman 목록의 요소를 삭제하려는 경우 정상적인 순서로 다음 요소의 색인이 삭제시 변경되므로 목록을 역순으로 안전하게 반복 할 수 있습니다. 이 예를 참조하십시오.
mbomb007

1

Python 3에서는 다음을 수행해야합니다.

prefix = 'item_'
t = {'f1': 'ffw', 'f2': 'fca'}
t2 = dict() 
for k,v in t.items():
    t2[k] = prefix + v

또는 다음을 사용하십시오.

t2 = t1.copy()

원본 사전을 수정해서는 안되며, 잠재적 인 버그 또는 RunTimeErrors뿐만 아니라 혼란을 야기합니다. 새 키 이름으로 사전에 추가하지 않는 한.


0

오늘 나는 비슷한 유스 케이스를 가지고 있었지만, 루프의 시작 부분에서 단순히 사전의 키를 구체화하는 대신, 순서가 지정된 사전 인 사전의 반복에 영향을 미치도록 사전을 변경하고 싶었습니다.

나는 jaraco.itertools 에서도 찾을 수있는 다음 루틴을 구축하게 되었습니다 .

def _mutable_iter(dict):
    """
    Iterate over items in the dict, yielding the first one, but allowing
    it to be mutated during the process.
    >>> d = dict(a=1)
    >>> it = _mutable_iter(d)
    >>> next(it)
    ('a', 1)
    >>> d
    {}
    >>> d.update(b=2)
    >>> list(it)
    [('b', 2)]
    """
    while dict:
        prev_key = next(iter(dict))
        yield prev_key, dict.pop(prev_key)

독 스트링은 사용법을 보여줍니다. 이 기능을 위의 대신 사용 d.iteritems()하여 원하는 효과를 얻을 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.