python: ошибка: словарь изменил размер во время итерации @ при попытке выполнить итерацию по django объекту запроса в SimpleLazyObject - PullRequest
1 голос
/ 09 января 2020

В python в словаре, если key равно byte string, тогда json.dumps выдаст ошибку, поэтому я пытаюсь рекурсивно преобразовать все keys в string перед передачей их в json.dumps.

Note: json.dumps converts the value to str using default function but not keys

Следующее - моя функция, которая проверяет любые byte string keys и преобразует их в string:

def keys_string(d):
    rval = {}
    if not isinstance(d, dict):
        if isinstance(d,(tuple,list,set)):
            v = [keys_string(x) for x in d]
            return v
        else:
            return d
    for k,v in d.items():
        if isinstance(k,bytes):
            k = k.decode()
        if isinstance(v,dict):
            v = keys_string(v)
        elif isinstance(v,(tuple,list,set)):
            v = [keys_string(x) for x in v]
        rval[k] = v
    return rval

Я отлаживаю некоторый код в django

Я хочу проверить request объект в определенной точке моего кода

Итак, у меня есть

request_dir = dir(request)

, а затем преобразовать любые байтовые ключи в строку, используя keys_string (иначе json сбросит дамп ошибки)

request_dir_keys_stringed = keys_string(request_dir)

Тогда наконец

json.dumps(request_dir_keys_stringed, indent=4, sort_keys=True, default=str)

Когда я пытаюсь сделать request_dir_keys_stringed = keys_string(request_dir), он говорит

in keys_string
    for k,v in d.items():
RuntimeError: dictionary changed size during iteration

Я нашел это происходит, когда:

k: user и v: <SimpleLazyObject: <User: test@gmail.com>>

Я пытался для request.session объекта, он не выдает такую ​​ошибку. Но какой-то объект делает.

request_session_dir = dir(request.session)
request_session_dir_keys_stringed = keys_string(request_session_dir)
json.dumps(request_session_dir_keys_stringed, indent=4, sort_keys=True, default=str)

Что делать в таких ситуациях

Дополнительные сведения для воспроизведения проблемы:

$ python --version
Python 3.7.3

$ django-admin --version
2.2.6

def articles(request):
    request_dir = dir(request)
    request_dir_keys_stringed = keys_string(request_dir)
    print(json.dumps(request_dir_keys_stringed, indent=4, sort_keys=True, default=str)
    return render(request, 'articles/main_page/articles.html')

ПОСЛЕ РЕАЛИЗАЦИИ РЕШЕНИЯ Строка keys_string становится:

def keys_string(d):
    rval = {}
    if not isinstance(d, dict):
        if isinstance(d,(tuple,list,set)):
            v = [keys_string(x) for x in d]
            return v
        else:
            return d
    keys = list(d.keys())
    for k in keys:
        v = d[k]
        if isinstance(k,bytes):
            k = k.decode()
        if isinstance(v,dict):
            v = keys_string(v)
        elif isinstance(v,(tuple,list,set)):
            v = [keys_string(x) for x in v]
        rval[k] = v
    return rval


        request_dir = dir(request)
        request_dir_keys_stringed = keys_string(request_dir)
        print(json.dumps(request_dir_keys_stringed, indent=4, sort_keys=True, default=str)

И теперь объект запроса отображается без ошибок

1 Ответ

2 голосов
/ 09 января 2020

request.user - это SimpleLazyObject, который содержит обратный вызов, являющийся закрытием, которое содержит ссылку на тот же объект request. И затем этот обратный вызов обновляет объект request, создавая новый атрибут request._cached_user, если он не существует. Таким образом, наблюдение request.user может создать новый атрибут request._cached_user.

Думаю, проще объяснить его с помощью выдержек из кода.

Исходный код django:

class SimpleLazyObject(LazyObject):
    def __init__(self, func):
        ...

class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        request.user = SimpleLazyObject(lambda: get_user(request))

def get_user(request):
    if not hasattr(request, '_cached_user'):
        request._cached_user = auth.get_user(request)
    return request._cached_user

Итак, если вы хотите более стабильно проходить по словарным ключам, то вам нужно перебрать копию ключей dict:

keys = list(d.keys())
for k in keys:
    v = d[k]
    if isinstance(k, bytes):
        ...
...