为 Django 创建一个保留顺序的多值字典
Creating an order-preserving multi-value dict for Django
当尝试创建一个保留 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.
TLDR: Re-implement 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
也应该是 re-implemented。
问题是 Django 的 MultiValueDict
覆盖 __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'])]
original code 使用 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()
在Python2中,OrderedDict
在pure-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])
因此,它从 MRO 中选取被覆盖的 __getitem__
,并且 returns 仅选取单个值,而不是整个列表。
这个问题在 Python 3.5+ 的大多数构建中都被回避了,因为 OrderedDict
通常有一个 C-implementation 可用,意外地屏蔽了它的方法以防止使用被覆盖的方法。
collections.OrderedDict is now implemented in C, which makes it 4 to 100 times faster.[What's new in Python 3.5]
当尝试创建一个保留 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.
TLDR: Re-implement 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
也应该是 re-implemented。
问题是 Django 的 MultiValueDict
覆盖 __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'])]
original code 使用 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()
在Python2中,OrderedDict
在pure-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])
因此,它从 MRO 中选取被覆盖的 __getitem__
,并且 returns 仅选取单个值,而不是整个列表。
这个问题在 Python 3.5+ 的大多数构建中都被回避了,因为 OrderedDict
通常有一个 C-implementation 可用,意外地屏蔽了它的方法以防止使用被覆盖的方法。
collections.OrderedDict is now implemented in C, which makes it 4 to 100 times faster.[What's new in Python 3.5]