对PyObject的一点理解

很久之前看过一篇有关Python的list的博客,里面提到,Python中list的数据结构是这样的:

我很不理解的是,list完全可以存储×ob_item就可以了啊,为什么要存放××ob_item呢?

只要list的每一个元素存放的是指向PyObject的指针,就已经可以实现增删改查了,维护一些指向PyObject的指针就可以了,而且指针只有一层,还比“指向指针的指针”更快一些。百思不得其解,去stackoverflow上面提问,有个人回答,这样的话,如果要修改list的值,只要将指针指向另一个“指向PyObject的指针”就可以了。但是我觉得,即使是存一层指针,repoint也不是很麻烦。

后来自己就想了很长时间,跟朋友吃饭讨论过,跟张哥也讨论过。

昨天晚上,我感觉自己有点理解了。

首先,PyObject肯定是不能拿来用的,因为这就是文件系统的blob一样,我们用的是指向它的node,看他的文件块没有意义。所以一般来说,我们用的是×PyObject来操作Python对象。换句话说,我们基本上不会去直接搞PyObject,×PyObject一般是我们能获得PyObject的唯一方式。那么如果在list里面存放一层指针,即×PyObject的话,就差不多是复制这个对象进去了(虽然存放的也是指针)。参考下图:

PyObject

这个时候,如果从list的索引改了PyObject,那么×PyObject也会受影响,是指向了同一个对象(这个地方和××PyObject)是一样的。但是考虑这样一种情况:

假如是我想象的list存放的是×PyObject的话,我们将tom添加到list之后,重新给tom赋值了一个对象,也就是说,tom指向了另一个对象。但是li中的tom是直接指向一个对象的。也就是说,tom的repoint就和li中的tom关联不到了。

可能说的有点绕,和指针相关就比较麻烦,简单来说,就是:如果list存放的是×PyObject,那么变量重新指向别的对象的话,list存放的变量还是指向原来的对象,我们无法通过引用给list中的元素重新指向了(只能通过list的索引重新指向)。这就点像软链接和硬链接的区别。

但是如果存放的是××PyObject,那么引用(×PyObject)指向的什么,数组里面就存放的什么:

PyObject -pointer

另外,如果list存放×PyObject,那么list的元素改变之后,旧元素很可能没有指针指向它了,很可能被作为垃圾回收。

以上,是我这几天的理解,没有经过验证,很可能都是错误的。

 

Python 为什么list不能作为字典的key?

很多Python初学者经常会有这样的疑问,为什么Python有tuple(元组)和list(列表)两种类型?为什么tuple可以作为字典的key,list不可以?要理解这个问题,首先要明白python的字典工作原理。

1.Python的字典是如何工作的

在Python中,字典也就是一个个的“映射”,将key映射到value:

为了实现这个功能,Python必须能够做到,给出一个key,找到哪一个value与这个key对应。先来考虑一种比较简单的实现,将所有的key-value键值对存放到一个list中,每当需要的时候,就去遍历这个list,用key去和键值对的key匹配,如果相等,就拿到value。但是这种实现在数据量很大的时候就变得很低效。它的算法复杂度是O(n),n是存放键值对的数量。(关于Hash表具体的工作原理,可以参考我的这篇文章

为此,Python使用了hash(哈希)的方法来实现,要求每一个存放到字典中的对象都要实现hash函数,这个函数可以产生一个int值,叫做hash value(哈希值),通过这个int值,就可以快速确定对象在字典中的位置。然而,由于Hash碰撞的存在,可能存在两个对象的Hash值是相同的,所以查找字典的过程中,要比较hash值,还要比较value的值。

这个查询的大致过程如下:

要使这个查找过程正常工作,hash函数必须满足条件:如果两个key产生了不同的hash value,那么这两个key对象是不相等的。

否则的话,hash value不同,对象却相同,那么相同的对象产生不同的hash value,查找的时候就会进错桶(step 2),在错误的桶里永远也找不到你要找的value。

另外,要让字典保持高查找效率,还要保证:当两个key产生相同的hash value,那么他们是相等的。

这样做的目的是,尽量满足每个hash桶只有一个元素。为什么要这样呢? 考虑下面这个hash函数。

这个hash函数是满足上面我们谈的第一个条件的:如果两个key的hash value不同,那么两个key对象不相同。因为所有的对象产生的hash value都是1,所以不存在能产生不同hash value的key,也就不存在不满足的情况。但是这样做的坏处是,因为所有的hash value都相同,所以就把所有的对象分到了同一个地方。查找的时候,进行到第三步,遍历的效率就变成了O(n).

Hash函数应该保证所有的元素平均的分配到每一个桶中,理想的情况是,每一个位置只有一个元素。

以上两个原则,第一个保证了你能从字典中拿到要找的元素,第二个保证了查询效率。

2.字典Key要满足的要求

经过上面的讨论,我们应该明白Python为什么对字典的key有这样的要求了:

要作为字典的key,对象必须要支持hash函数(即__hash__),相等比较(__eq__或__cmp__),并且满足上面我们讨论过的条件。

3.List为什么不能作为key

至于这个问题,最直接的答案就是:list没有支持__hash__方法,那么为什么呢?

对于list的hash函数,我们可能有下面两种实现的方式:

第一种,基于id。这满足条件——“如果hash值不同,那么他们的id当然不同”。但考虑到list一般是作为容器,基于id来hash可能会导致下面两种情况:

  • 用相同的list作为key去字典中找某个元素可能会得到不同的结果,因为是基于id hash的,所以即使他们的内容相同,字典依然将他们作为不同的元素对待。
  • 创建一个一模一样的list用字典查找永远会得到一个KeyError。

第二种,基于内容。tuple就是这样做的,但是要注意一点,tuple是不可以修改的,但list是可以修改的。当list修改之后,你就永远别想再从字典中拿回来了。见下面的代码。

鉴于两种实现的方式都存在一定的副作用,所以Python规定:

内置的list不能作为字典的key.

但tuple是不可变,所以tuple可以作为字典的key。

(2018年1月2日更新,上面我说tuple不可变可以作为字典的key,这句话并不是完全正确的。tuple只是相对不可改变的,如果tuple中有元素是可变对象,那么虽然tuple不可改变,那么其中元素所指向的对象是可变的,所以同样会出现上面“list不能作为字典的key”这个问题,即含有可变对象的tuple也不能作为字典的key,举个例子就很好懂了。)

4.自定义的类型作为字典的Key

用户自定义的类型就可以作为key了,默认的hash(object)id(object), 默认的cmp(object1, object2)cmp(id(object1), id(object2)),同样是可以修改的对象,为什么这里就没有上面说的问题呢?

  1. 一般来说,在映射中比较常见的需求是用一个object替换掉原来的,所以id比内容更重要,就可以基于id来hash
  2. 如果内容重要的话,自定义的类型可以通过覆盖__hash__函数和__cmp__函数或__eq__函数来实现

值得注意的是:将对象和一个value关联起来,更好的做法是将value设置为对象的一个属性。

 

Django中null和blank的区别

Django的Model可以设置字段的null属性和blank属性。

null属性是针对数据库而言的,null=True将设置数据库的COLUMN为NULL(或者NOT NULL),而blank为True将不会影响数据库的结构,依然是必填。但是在Django里面,是可选的。即Form不会是required,Django自带的admin页面不要求必填了。

具体作用到数据库,是这样:

在 PostgreSQL 9.4 的结果如下 :

MySQL 5.6 的结果如下 :

可以看出,数据库的字段是否可以为空完全取决于null属性,和blank没有关系。

那么为什么需要需要两个变量分开呢? 想象这样一种场景:我们需要用户的forms必填某一个选项,但是是否存储到数据库,就取决于其他的一些情况了。再如,我们需要用户必须输入,但是我们用其他方式(除了Django之外,例如用shell维护数据库的时候)时,可以不用设置这些字段,这时候就可以设置null=True,blak=False。

另外要注意的是,有些字段是不适合设置null或blank的,参考下表:


参考资料:

  1. differentiate null=True, blank=True in django
  2. Django 1.6 最佳实践: 如何正确的使用和设置Database和Model
 

2016年总结

去年没有像往常一样,在31号这天写年终总结,倒不是事情多,最近好不容易有三天假期,玩游戏去了。

去年发生了好多事啊,川普当上了总统,暴雪出了守望先锋,英国脱欧,土耳其军事政变,网络直播更火了,传互联网行业进入下半场,共享单车又火了起来,漂亮的小黄车突然都是。

对我来说,2016年也是非常精彩的一年。这年最大的事情,是我人生中第一次踏出祖国。在德国做交换生的半年,让我的视野和性格变得大有不同。

前年年末的申请本来是抱着试一试的心态的,结果就成功了。很多事情不会等你准备好,你也永远都不会有准备好的时候,要是你想着“这次先算了,等我准备足够充分了再试一试”,那就永远都不会有这个时候。

3月份出发,8月底回国。这六个月像梦一样。现在偶尔挺高在国外经常听的那些歌,在国外的那些感觉就会切实的感觉到。好像自己还在308的厨房,好像刚从Penny回来,可以叫朋友来这里一起做饭吃,好像还还能随时和Igor,Ed,Sah,Ibrahim他们开开玩笑。热情的姐姐Dviya。和国内的狐朋狗友一样的Paul,Juho,Lucas。跨年的这几天,Vj,Ibrahim他们发消息祝福,我更想他们了。我用自己不熟练的语言,和他们表达自己的意思,竟然也能谈心,谈政治。这是一群可爱的人们,来自不同的国家,却相处的很好,性格大差不离。那时候都没什么学习压力,每天都想着去哪里玩。

潇洒了半年之后,回国就感受到了压力。秋招太快,我天真的以为是“秋天”的秋,结果10月份都已经快结束了。准备的不好,败的很惨。也让我明白自己还有很多要学习的,把心态放低,一点一点学。

回国之后在学校分配的公司实习,非常不喜欢,技术太浅太杂,都是些乱七八糟的杂活,幸好后面有时间自己学习了。后来转到一个面试过了的公司,遇见比较亲和,技术靠谱的上司,成长就比较快了。一边学一边用,学的也比较扎实。

今年还做了一件事,从零开始用django搭建疯驴户外俱乐部的官网。从3月份开工,用自己空闲的时间,断断续续开发,也算是上线了。中间接触了Ouath2,微信支付宝支付,移动端适配,部署线上环境,django的数据迁移等各种各样的坑,自己一个人负责所有工作,在难的东西也得自己搞,搞不定就换个方式搞,在搞不定就用本办法丑陋的实现,总之得搞出来。现在整个流程通了,可以不断迭代了。

今年还给csdn写了一年稿子。从稿费看,字数在二三十万吧。今年回国之后,我同时有四份工作,鉴于学校的狗屁学分制度必须去分配的实习,得空就去热心大哥的公司实习(这个比较好),晚上回来写稿子,维护网站。2点睡6点半起,坐1个半小时1辆公交三辆地铁去上班,累的吐血。于是年底的时候,和曙光说csdn这边的稿子,先放一放了。其实写到现在,动力基本上只剩下钱了,可以学到的东西不多。但是一年之前,卢老师耐心的给我校对,出错了也不说什么,稿费照发,我还是很感动的。想快点进步,翻译些优秀的东西出来,对得起自己的工资。

面对选择兼职,选择工作,我都比较果断,判断做还是不做就看两点,给多少钱,能学到多少东西。如果第二点可以的话,第一条可以放宽。学新东西我自己也快乐,这种快乐也算是回报吧。

今年在德国认识了欣,后来恋爱。这也是我第一次比较认真的恋爱吧,有很多快乐的时候,也遇到过很多问题。对我来说,这样的亲密关系难度比较大。因为我这个人比较喜欢简单的方式,和朋友相处有什么说什么,比如有人借我的钱,我会问清楚什么时候还,到时间就要钱,还不了以后就不会再借了,就这么简单。工作上也是,做技术很好的一点就是,没啥人情世故,拿那些钱,做好自己的事,不用看别人脸色。老板要是有毛病,我走人就是了。这么努力的学习,要的就是给自己带来的自信,今天走了,马上能再找一份更好的工作。但是恋爱就不一样了,她是你最爱的人,总不能直来直去的,对我来说,这太难了。我比较傻,吵架吧,总喜欢分析出对错,然后就越吵越厉害,欣又爱哭,所以很多事情让我处理的很糟糕。后来逐渐变好一些了,我不那么较真了,很多事情不用管谁对谁的错,哄哄就过去了。我觉得这就是所谓的情商高吧,我还是适合情商低一点,以后还是把情商都留给女朋友,处理别的事情上随自己的性子,算是个让步吧。欣现在是我生命中非常重要的一部分,这对我以后的计划有很大的影响,我自由自在的生活已经没有了,以后很多事情可能不会由着我的想法了,我们之间还存在很多分歧,比如对游戏的看法上,这些都要以后继续沟通和互相理解。为我们两个加油。

这年过的非常开心,非常精彩。昨天刚吃过了生日蛋糕,发现自己都22岁了,好像突然之间,变成了小时候的自己最想成为的年纪,有自己的收入,没有太大的压力,可以买好多好多游戏,做想做的事情,非常幸福。我前年列的心愿单都实现了,kindle又有了俩,女朋友送了我ps4,还是slim。啥都不缺了,越想越幸福。

2017年自己也有很多小目标,我已经开始努力了。不好意思写出来,怕完不成。给自己加油!

 

AngularJs与Django标签冲突的解决方案

Django和Angular的模板系统使用了非常相似的标签系统,比如说,都是使用{{ content }}表示变量名字。所以Django和Angular配合使用的时候,会引起冲突。我在网上找到了一些解决方法。

一、 改变AngularJs的默认标签

下面的代码可以将Angular原来的标签改成{[{ content }]}

这是比较简单,并且直观的一种方法。修改之后的代码比较容易阅读,一眼就能看出来是Django的标签还是Angular的。缺点是很容易与第三方的插件冲突(如果第三方的插件使用了指令等用到标签的地方)。

二、 告诉Django不要渲染模板的其中一部分内容

从Django 1.5开始,支持{% verbatim %}标签(verbatim的意思是逐字翻译的,字面意思的),Django不会渲染verbatim标签包裹的内容:

这个标签不支持嵌套,但是你可以为标签添加名字:

这样,Django会寻找myblock的endverbatim作为结束的标志,中间插入了verbatim标签,会作为myblock中不解释的一部分处理。

这种方案的优点是,不会增加代码的复杂度,并且是Django的原生支持,对Angular也没有影响。缺点是可能在很多地方用到很多verbatim标签,搞得template很乱。

三、 使用第三方插件

目前,我已知的有django-angular。这个插件有混合django和angular标签的功能。

正确解析angular标签的同时,还可以继续使用django的if等标签。

这样做的缺点是,引入插件增加了代码的复杂度,团队的所有人都需要学习这种写法,我个人感觉,也比较容易增加错误。

我觉得第二种比较合适, 写入变量的时候尽量前后端分开,django负责返回静态的模板,数据交给angular,没大问题。

参考:

  1. AngularJS with Django – Conflicting template tags
  2. Built-in template tags and filters
  3. Share a template between Django and AngularJS