Django模板不能循环defaultdict

Posted

技术标签:

【中文标题】Django模板不能循环defaultdict【英文标题】:Django template can't loop defaultdict 【发布时间】:2011-06-13 10:36:09 【问题描述】:
import collections

data = [
  'firstname': 'John', 'lastname': 'Smith', 
  'firstname': 'Samantha', 'lastname': 'Smith', 
  'firstname': 'shawn', 'lastname': 'Spencer', 
]

new_data = collections.defaultdict(list)

for d in data:
    new_data[d['lastname']].append(d['firstname'])

print new_data

这是输出:

defaultdict(<type 'list'>, 'Smith': ['John', 'Samantha'], 'Spencer': ['shawn'])

这是模板:

% for lastname, firstname in data.items %
  <h1>  lastname  </h1>
  <p>  firstname|join:", "  </p>
% endfor %

但是我的模板中的循环不起作用。什么都没有出现。它甚至没有给我一个错误。我怎样才能解决这个问题?它应该显示姓氏和名字,如下所示:

<h1> Smith </h1>
<p> John, Samantha </p>

<h1> Spencer </h1>
<p> shawn </p>

【问题讨论】:

您没有显示将字典放入模板上下文的代码。你确定这一切正常吗? 是的,其他一切都在循环之外正确呈现。 【参考方案1】:

在插入新值后,您可以通过禁用 defaultdict 的默认功能来避免复制到新字典:

new_data.default_factory = None

说明

template variable resolution algorithm in Django 将首先尝试将new_data.items 解析为new_data['items'],当使用defaultdict(list) 时会解析为一个空列表。

要禁用默认为空列表并让 Django 在 new_data['items'] 上失败然后继续解析尝试直到调用 new_data.items(),default_factory attribute of defaultdict 可以设置为 None

【讨论】:

+1 - 这比选择的答案更有效,尤其是对于大型字典集。 很好的答案,解释有助于理解底层问题! 我可能会补充一点,% for k, v in detauldictvar % 允许您迭代 defaultdict 的元素,即使没有将 default_factory 设置为 None。现在有了 Sebastien 的解释,我可以理解为什么 :-) 【参考方案2】:

尝试:

dict(new_data)

在 Python 2 中,最好使用 iteritems 而不是 items :)

【讨论】:

我无法相信像这样简单的事情。非常感谢! 它实际上是作为针对 django 的错误提交的:code.djangoproject.com/ticket/16335 但事实证明,唯一好的解决方案确实是将其转换为字典,因为它现在显示在 docs 中,并且在changeset【参考方案3】:

由于“问题”多年后仍然存在并且是 Django 模板工作方式所固有的,我更喜欢写一个新的答案,详细说明为什么这种行为保持原样。

如何修复错误

首先,解决方案是将defaultdict 转换为dict,然后再将其传递给模板上下文:

context = 
    'data': dict(new_data)

您不应该在 Django 的模板上下文中使用 defaultdict 对象。

但是为什么呢?

下面Django issue #16335详细说明了这个“bug”背后的原因:

确实,归结为模板语言使用相同的语法进行字典和属性查找。

...来自the docs:

字典查找、属性查找和列表索引查找是用点符号实现的。 [...] 如果变量解析为可调用对象,模板系统将不带参数调用它,并使用其结果而不是可调用对象。

当 Django 解析你的模板表达式时它会首先尝试data['items']。但是,这是一个有效的表达式,它会自动在你的默认字典 data 中创建一个新条目 items,初始化为一个空列表(在原始作者的情况下)并返回创建的列表(空)。

意图的操作是调用方法items 没有实例data 的参数(简而言之:data.items()),但由于data['items'] 是一个有效的表达式,Django 停在那里并得到空刚刚创建的列表。

如果您尝试使用相同的代码但使用data = defaultdict(int),您将得到TypeError: 'int' object is not iterable,因为Django 将无法迭代创建@987654336 的新条目所返回的“0”值@。

【讨论】:

以上是关于Django模板不能循环defaultdict的主要内容,如果未能解决你的问题,请参考以下文章

py18_06:Django之模板语言

Django-模板标签方法

django 模板使用

怎样在django 模板中只显示前n条记录

django 模板通过 forloop.counter 访问列表项

Django模板中查询集中的循环内循环