Создание сохраняющего порядок многозначного dict для Django - PullRequest
7 голосов
/ 13 мая 2019

При попытке сделать кросс-совместимый заказ с сохранением QueryDict подкласса:

from collections import OrderedDict

from django.http import QueryDict
from django.conf import settings

settings.configure()

class OrderedQueryDict(QueryDict, OrderedDict):
    pass

querystring = 'z=33&x=11'
print(QueryDict(querystring).urlencode())
print(OrderedQueryDict(querystring).urlencode())

Вывод на Python 3.x (правильный и ожидаемый результат):

z=33&x=11  # or maybe x=11,z=33 on Python<=3.5
z=33&x=11

Выводна Python 2.7 (эта строка запроса была повреждена):

x=11&z=33
z=3&z=3&x=1&x=1

Почему эта идея работает на Python 3, но не на Python 2?

Django v1.11.20.

1 Ответ

6 голосов
/ 14 мая 2019

TLDR: повторно внедрить lists:

class OrderedQueryDict(QueryDict, OrderedDict):
    def lists(self):
        """Returns a list of (key, list) pairs."""
        return [(key, self.getlist(key)) for key in self]

Для полной функциональности, iterlists также необходимо повторно реализовать.


Проблема в том, что MultiValueDict в Django перезаписывает __getitem__ для получения только последнего значения, а getlist для получения всех значений. Это неявно опирается на другие методы базового отображения, не использующие переопределенные методы. Например, он опирается на super().iteritems возможность извлекать списки значений:

>>> from django.utils.datastructures import MultiValueDict
>>> d = MultiValueDict({"k": ["v1", "v2"]})
>>> d.items()
[('k', 'v2')]
>>> super(MultiValueDict, d).items()
[('k', ['v1', 'v2'])]

Исходный код использует six для охвата Python 2 и 3. Вот что выполняет Python 2:

def lists(self):
    return list(self.iterlists())

def iterlists(self):
    """Yields (key, list) pairs."""
    return super(MultiValueDict, self).iteritems()

В Python 2 OrderedDict реализован в чистом Python и использует self[key], т.е. __getitem__, для получения значений:

def iteritems(self):
    'od.iteritems -> an iterator over the (key, value) pairs in od'
    for k in self:
        yield (k, self[k])

Таким образом, он получает переопределенный __getitem__ из MRO и возвращает только отдельные значения, а не все списки.

Эта проблема обходит стороной в большинстве сборок Python 3.5+, поскольку OrderedDict обычно имеет доступную C-реализацию, случайно отгораживая свои методы от использования переопределенных.

collection.OrderedDict теперь реализован в C, что делает его в 4-100 раз быстрее. [Что нового в Python 3.5]

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...