一次跑偏之旅!
 
对于一个惯用C++的人来说,使用Python这种语言的一大障碍就是许多集合类型的操作效率并不如传统的经典数据结构那样直观可见,以及许多实际上涉及到内存分配、对象复制之类的耗时操作被隐藏在看似简单的接口之中。加上Python的文档只强调如何使用,大部分时候都对实现的细节和效率语焉不详。这使我在使用Python时,会有一种比用C++更加小心翼翼的心态。当有许多个方式来加工一个数据集时,我不得不仔细考虑哪一种方式才是效率最高的,因为无法从文档中获得相关的信息,所以只能靠经验推测或是阅读源码来判断,这经常比用C++更加费时和困难。
 
虽然Python的优势在于其开发效率、统一的类库和简洁的语法,但对于所有从事企业级开发的人来说,显然任何语言的效率都是值得重视的。
 
所以,在假装不关心效率地写了几天之后,我今天花了些时间来尝试判断一个小问题:
 
当需要渲染一个dict的所有value时,究竟应该向RenderContext里塞一个怎样的数据集对象才是最高效的?
 
从最直观的写法开始:
 
data = {'a' : '1', 'b' : '2'}
ctx = Context({'data' : data.values()})
 
这里使用到的dict.values()函数会新建一个列表,把dict的所有value复制到其中 —— 显然效率不够高。一个改进的写法是使用迭代器:
 
data = {'a' : '1', 'b' : '2'}
ctx = Context({'data' : data.itervalues()})
 
这是一个效率很高的实现,但是很遗憾的是,它存在一个问题。正是这个问题导致我下决心来探寻其中的实现细节:当使用iterator来构造Context时,只有第一个使用该数据集的for Tag能够正确的渲染出数据,如果在一个模板中存在多个地方需要渲染同一个数据集,后续的for Tag全部只能输出空列表。
 
产生这个问题的原因是迭代器指针在第一次遍历完之后,指针位置到达了列表末尾,在下一次遍历时,迭代器并没有重置,所以自然无法取到数据。
 
坦白说,我认为这是一个语义范畴的Bug,渲染引擎应该考虑这种情况并确保多次渲染所取到的数据是一致的。所以接下来我想看看有没有什么办法来解决这个Bug,说不定还能成为我对开源项目的第一个commit...
 
首先,最简单的办法是在使用完迭代器之后reset一下,然而iterator并没有reset接口。
 
或者,在每次使用都使用原始迭代器的拷贝,然而iterator同样没有clone接口。找到个itertools.tee函数,号称可以复制迭代器,但是其实只能接收iterable参数,传递iterator参数给它同样会导致上面的问题。
 
d= {'a':'1','d':'2'}
import itertools
di = d.itervalues()
di1 = itertools.tee(di,1)
list(di1[0])
>>> ['1', '2']
list(di)
>>> []
 
不得不吐槽一句,各种翻译真害人不浅,搞得我还以为这个tee是元数据语言的黑科技,闹半天发现只不过一个从iterable批量产生iterator的util。
 
既不能reset,又不能clone的话,那么就只剩两个选择了,一个是从Django渲染引擎的实现中看有没有办法,二是不用iterator来构造Context。
 
对于后者,一个简单的办法是自己实现一个iterable,用来代理dict.itervalues,然后用这个iterable对象来构造Context,这样不需要拷贝数据集,还可以保证每次使用的迭代器都是新的:
 
class iterable4dictval():
    def __init__(self, dict_obj):
        self.dict_obj = dict_obj
    
    def __iter__(self):
        if self.dict_obj is None or not isinstance(self.dict_obj, dict):
            return None
        
        return self.dict_obj.itervalues()
    
data = {'a' : '1', 'b' : '2'}
ctx = Context({'data' : iterable4dictval(data)})
 
这是一个能工作的实现,调用方的开销也很小,但是效率是否真的高呢?嘿!Django的实现告诉你然并卵...
 
那么,来看看Django渲染引擎的实现,对于安装好的Django,for Tag的实现代码在Lib\site-packages\django\template\defaulttags.py文件中,它对数据集的处理过程大概是这样的:
 
class ForNode(Node):
    def render(self, context):
        ...
        try:
            values = self.sequence.resolve(context, True)
        except VariableDoesNotExist:
            values = []
        if values is None:
            values = []
        if not hasattr(values, '__len__'):
            values = list(values)
        len_values = len(values)
        if len_values < 1:
            context.pop()
            return self.nodelist_empty.render(context)
 
        for i, item in enumerate(values):
            ...
 
这个实现会先判断Data Object是否有__len__属性,没有的话就会先转换成一个list。什么样的对象支持或应该支持__len__属性呢?Python小白的我还特意先百度了一番:
 
简单来说呢,__len__基本上和len()的支持是对应的,而文档里说len函数支持所有的sequence和collection类型,也就是string, bytes, tuple, list, range, dictionary, set这些。
 
显然,iterable和iterator是不支持len的,也就是说,如果使用iterable或iterator来构造Context,那么Django在渲染前,还是会把所有数据都转存到一个新建的list里去...得!调用方省下的效率,全都在实现中还回去了!
 
Django的开发者显然不至于脑残到不知道iterator,那么为什么要这样实现?ForNode.render实现的其它部分揭示了答案,代码就不列了。我们看看for Tag支持的一些变量:
 
forloop.counter
forloop.counter0
forloop.revcounter
forloop.revcounter0
forloop.first
forloop.last
forloop.parentloop
 
其它的都好说,唯独revcounter,如果不知道数据集的长度,要支持这个变量就难了。对于iterable,或许可以做两次遍历,一次计算长度,一次渲染;但对iterator,除了转换为列表,还真没有什么好的办法,更何况还可能有形状提到的多次渲染需求问题。所以,Django干脆直接把把iterable和iterator都转成list。
 
最后,回到正题,如果要渲染dict的所有value,到底怎样构造Context才是最高效的?如果没看过Django的实现,或许我们会认为使用迭代器是最高效的方法之一,但是看过之后,最高效的办法只有一个,没有之一。那就是直接用dict来构造Context,然后这样写模板:
 
{% for key, value in data.items %}
    {{ key }}: {{ value }}
{% endfor %}
 
注意data变量就是字典,而不是data.items。
 
 

后话:

 
Andorid的文件枚举接口,也存在一个类似的效率问题,当初也是搞得我很无语。java.io.File.dir()有一个重载是带一个过滤器参数,返回一个经过过滤的文件列表,看起来这比返回所有子文件列表的开销要小一点。然而,大家看看这个实现:
 
public String[] list(FilenameFilter filter) {
    String[] filenames = list();
    if (filter == null || filenames == null) {
        return filenames;
    }
    List<String> result = new ArrayList<String>(filenames.length);
    for (String filename : filenames) {
        if (filter.accept(this, filename)) {
            result.add(filename);
        }
    }
    return result.toArray(new String[result.size()]);
}
 
先获取一个所有子文件的列表,再用for循环处理一遍,把符合条件的项再放到一新列表中去。也就是说,这货其实是创建两个列表的开销,效率比调用方直接用list()再手工迭代差远了。盒盒~
 

Django模板输出Dict所有Value的效率问题的更多相关文章

  1. 怎么通过django模板输出双花括号{{}}

    https://segmentfault.com/q/1010000000685399

  2. Django模板层

    一:模板简介 二:模板语法值变量 三: 模板之过滤器 四: 模板之标签 五:自定义标签和过滤器   一:模板简介 def current_datetime(request): now=datetime ...

  3. Django——模板层(template)(模板语法、自定义模板过滤器及标签、模板继承)

    前言:当我们想在页面上给客户端返回一个当前时间,一些初学者可能会很自然的想到用占位符,字符串拼接来达到我们想要的效果,但是这样做会有一个问题,HTML被直接硬编码在 Python代码之中. 1 2 3 ...

  4. django模板(一)

    模板(一) 实验简介 在前一章中,你可能已经注意到我们在例子视图中返回文本的方式有点特别. 也就是说,HTML被直接硬编码在 Python 代码之中. def current_datetime(req ...

  5. Django模板修炼

    引言:由于我们在使用Django框架时,不会将HTML代码采用硬编码的方式,因为会有以下缺点: 1:对页面设计进行的任何改变都必须对 Python 代码进行相应的修改. 站点设计的修改往往比底层 Py ...

  6. Django—模板

    索引 一.模板语言 1.1 变量 1.2 标签 1.3 过滤器 1.4 自定义过滤器 1.5 注释 二.模板继承 三.HTML转义 四.CSRF 五.验证码 六.反向解析 模板 作为Web框架,Dja ...

  7. django——模板层

    每一个Web框架都需要一种很便利的方法用于动态生成HTML页面. 最常见的做法是使用模板. 模板包含所需HTML页面的静态部分,以及一些特殊的模版语法,用于将动态内容插入静态部分. 说白了,模板层就是 ...

  8. 测试开发之Django——No7.Django模板中的过滤器

    1.add 将参数添加到值. 例如: {{ value|add:"2" }} 如果value是4,那么输出将是6. 此过滤器将首先尝试将两个值强制转换为整数.如果失败,它将尝试将值 ...

  9. django模板(template)

    模板层(template) 你可能已经注意到我们在例子视图中返回文本的方式有点特别. 也就是说,HTML被直接硬编码在 Python代码之中. 1 2 3 4 def current_datetime ...

随机推荐

  1. RMAN中format的参数

    (转自:http://blog.chinaunix.net/uid-23079711-id-2554290.html) format 的替换变量,注意大小写! 1.     %d --数据库的db_n ...

  2. hdu 4081 Qin Shi Huang's National Road System 树的基本性质 or 次小生成树思想 难度:1

    During the Warring States Period of ancient China(476 BC to 221 BC), there were seven kingdoms in Ch ...

  3. L3-010. 是否完全二叉搜索树

    L3-010. 是否完全二叉搜索树 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 将一系列给定数字顺序插入一个初始为空的二叉搜 ...

  4. Spring整合hibernate:3、使用XML进行声明式的事务管理

    配置applicationContext.xml文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 2 ...

  5. 在pycharm中自定义模板代码,快速输出固定代码块

    pycharm中有时会经常输出固定一段代码,为避免每次重复输入,可以自定义一段模板代码,请看以下图教程: 1.  点击 file   里面的   setting 2. 在搜索框输入live,就会显示出 ...

  6. LVS模式二:隧道模式(Tun)

    一.IP隧道(IP Tunneling) ip隧道是一个将ip报文封装到另一个ip报文的技术,这可以使得目标为一个ip地址的数据报文被封装和转发到另一个ip地址.ip隧道技术也成为ip封装技术.    ...

  7. MPAndroidChart Wiki(译文)~Part 5

    19. ChartData子类 这篇wiki主要关注ChartData子类的具体介绍.至于此部分没有提及到的ChartData的子类,代表他们没有特性功能需要介绍. BarData 方法 使用 set ...

  8. UTL_DBWS包的创建和用法

    UTL_DBWS - Consuming Web Services in Oracle 10g In a previous article I presented a method for Consu ...

  9. OPEN(SAP) UI5 学习入门系列之三:MVC (上) - 模型

    这次我们来一起学习MVC,这个专题分为两个小节,本次主要是总览以及模型,下一次着重会介绍视图以及控制器,因为控制器其实没有太多可以讲的,所以和视图合并在一块. 1 Model View Control ...

  10. Android中的“再按一次返回键退出程序”实现 (转) 按返回键退出程序时进行提醒

    原文地址: https://blog.csdn.net/xichenguan/article/details/47030303 最近在研究   Android  编程方面的东西, 有了以下发现,  该 ...