本文有些零碎,总题来说,包括两个问题:

(1)可变对象(最常见的是list dict)被意外修改的问题,

(2)对参数(parameter)的检查问题。

这两个问题,本质都是因为动态语言(动态类型语言)的特性造成了,动态语言的好处就不细说了,本文是要讨论因为动态--这种灵活性带来的一些问题。

什么是动态语言(Dynamic Programming language)呢,是相对于静态语言而言,将很多静态语言编译(compilation)时期所做的事情推迟到运行时,在运行时修改代码的行为,比如添加新的对象和函数,修改既有代码的功能,改变类型。

绝大多数动态语言都是动态类型(Dynamic Typed),所谓动态类型,是在运行时确定数据类型,变量使用之前不需要类型声明,通常变量的类型是被赋值的那个值的类型。Python就是属于典型的动态语言。

动态语言的魅力在于让开发人员更好的关注需要解决的问题本身,而不是冗杂的语言规范,也不用干啥都得写个类。运行时改变代码的行为也是非常有用,比如python的热更新,可以做到不关服务器就替换代码的逻辑,而静态语言如C++就很难做到这一点。笔者使用得最多的就是C++和Python,C++中的一些复杂的点,比如模板(泛型编程)、设计模式(比如template method),在Python中使用起来非常自然。我也看到过有一些文章指出,设计模式往往是特定静态语言的补丁 -- 为了弥补语言的缺陷或者限制。

以笔者的知识水平,远远不足以评价动态语言与静态语言的优劣。本文也只是记录在我使用Python这门动态语言的时候,由于语言的灵活性,由于动态类型,踩过的坑,一点思考,以及困惑。

第一个问题:Mutable对象被误改

这个是在线上环境出现过的一个BUG

事后说起来很简单,服务端数据(放在dict里面的)被意外修改了,但查证的时候也花了许多时间,伪代码如下:

上述的代码很简单,dct是一个dict,极大概率会调用一个不用修改dct的子函数,极小概率出会调用到可能修改dct的子函数。问题就在于,调用routine函数的参数是服务端全局变量,理论上是不能被修改的。当然,上述的代码简单到一眼就能看出问题,但在实际环境中,调用链有七八层,而且,在routine这个函数的doc里面,声明不会修改dct,该函数本身确实没有修改dct,但调用的子函数或者子函数的子函数没有遵守这个约定。

从python语言特性看这个问题

本小节解释上面的代码为什么会出问题,简单来说两点:dict是mutable对象; dict实例作为参数传入函数,然后被函数修改了。

  Python中一切都是对象(evething is object),不管是int str dict 还是类。比如 a =5, 5是一个整数类型的对象(实例);那么a是什么,a是5这个对象吗? 不是的,a只是一个名字,这个名字暂时指向(绑定、映射)到5这个对象。b = a  是什么意思呢, 是b指向a指向的对象,即a, b都指向整数5这个对象

  那么什么是mutable 什么是immutable呢,mutable是说这个对象是可以修改的,immutable是说这个对象是不可修改的(废话)。还是看Python官方怎么说的吧

Mutable objects can change their value but keep their id().

  Immutable:An object with a fixed value. Immutable objects include numbers, strings and tuples. Such an object cannot be altered. A new object has to be created if a different value has to be stored. They play an important role in places where a constant hash value is needed, for example as a key in a dictionary.

承接上面的例子(a = 5),int类型就是immutable,你可能说不对啊,比如对a赋值, a=6, 现在a不是变成6了吗?是的,a现在"变成"6了,但本质是a指向了6这个对象 -- a不再指向5了

  检验对象的唯一标准是id,id函数返回对象的地址,每个对象在都有唯一的地址。看下面两个例子就知道了

>>> a = 5;id(a)

  35170056

  >>> a = 6;id(a)

  35170044

  >>> lst = [1,2,3]; id(lst)

  39117168

  >>> lst.append(4); id(lst)

  39117168

或者这么说,对于非可变对象,在对象的生命周期内,没有办法改变对象所在内存地址上的值。

  python中,不可变对象包括:int, long, float, bool, str, tuple, frozenset;而其他的dict list 自定义的对象等属于可变对象。注意: str也是不可变对象,这也是为什么在多个字符串连接操作的时候,推荐使用join而不是+

  而且python没有机制,让一个可变对象不可被修改(此处类比的是C++中的const)

dict是可变对象!

那在python中,调用函数时的参数传递是什么意思呢,是传值、传引用?事实上都不正确,我不清楚有没有专业而统一的说法,但简单理解,就是形参(parameter)和实参(argument)都指向同一个对象,仅此而已。来看一下面的代码:

可以看到,刚进入子函数double的时候,a,v指向的同一个对象(相同的id)。对于test int的例子,v因为v*=2,指向了另外一个对象,但对实参a是没有任何影响的。对于testlst的时候,v*=2是通过v修改了v指向的对象(也是a指向的对象),因此函数调用完之后,a指向的对象内容发生了变化。

如何防止mutable对象被函数误改:

为了防止传入到子函数中的可变对象被修改,最简单的就是使用copy模块拷贝一份数据。具体来说,包括copy.copy, copy.deepcopy, 前者是浅拷贝,后者是深拷贝。二者的区别在于:

简单来说,深拷贝会递归拷贝,遍历任何compound object然后拷贝,例如:

>>> lst = [1, [2]]
  >>> import copy
  >>> lst1 = copy.copy(lst)
  >>> lst2 = copy.deepcopy(lst)
  >>> print id(lst[1]), id(lst1[1]), id(lst2[1])
  4402825264 4402825264 4402988816
  >>> lst[1].append(3)
  >>> print lst, lst1,lst2
  [1, [2, 3]] [1, [2, 3]] [1, [2]]

从例子可以看出浅拷贝的局限性,Python中,对象的基本构造也是浅拷贝,例如 dct = {1: [1]}; dct1 = dict(dct)

  正是由于浅拷贝与深拷贝本质上的区别,二者性能代价差异非常之大,即使对于被拷贝的对象来说毫无差异:

在上面的示例中,dct这个dict的values都是int类型,immutable对象,因为无论浅拷贝 深拷贝效果都是一样的,但是耗时差异巨大。如果在dct中存在自定义的对象,差异会更大

  那么为了安全起见,应该使用深拷贝;为了性能,应该使用浅拷贝。如果compound object包含的元素都是immutable,那么浅拷贝既安全又高效,but,对于python这种灵活性极强的语言,很可能某天某人就加入了一个mutable元素。

第二个问题:参数检查

上一节说明没有签名 对 函数调用者是多么不爽,而本章节则说明没有签名对函数提供者有多么不爽。没有类型检查真的蛋疼,我也遇到过有人为了方便,给一个约定是int类型的形参传入了一个int的list,而可怕的是代码不报错,只是表现不正常。

def func(arg):       if arg:             print 'do lots of things here'       else:             print 'do anothers'

上述的代码很糟糕,根本没法“望名知意”,也看不出有关形参 arg的任何信息。但事实上这样的代码是存在的,而且还有比这更严重的,比如挂羊头卖狗肉。

  这里有一个问题,函数期望arg是某种类型,是否应该写代码判断呢,比如:isinstance(arg, str)。因为没有编译器静态来做参数检查,那么要不要检查,如何检查就完全是函数提供者的事情。如果检查,那么影响性能,也容易违背python的灵活性 -- duck typing; 不检查,又容易被误用。

  但在这里,考虑的是另一个问题,看代码的第二行: if arg。python中,几乎是一切对象都可以当作布尔表达式求值,即这里的arg可以是一切python对象,可以是bool、int、dict、list以及任何自定义对象。不同的类型为“真”的条件不一样,比如数值类型(int float)非0即为真;序列类型(str、list、dict)非空即为真;而对于自定义对象,在python2.7种则是看是否定义了__nonzero__ 、__len__,如果这两个函数都没有定义,那么实例的布尔求值一定返回真。

 

总结

以上两个问题,是我使用Python语言以来遇到的诸多问题之二,也是我在同一个地方跌倒过两次的问题。Python语言以开发效率见长,但是我觉得需要良好的规范才能保证在大型线上项目中使用。而且,我也倾向于假设:人是不可靠的,不会永远遵守拟定的规范,不会每次修改代码之后更新docstring ...

  因此,为了保证代码的可持续发展,需要做到以下几点

  第一:拟定并遵守代码规范

  代码规范最好在项目启动时就应该拟定好,可以参照PEP8和google python styleguild。很多时候风格没有优劣之说,但是保证项目内的一致性很重要。并保持定期review、对新人review!

  第二:静态代码分析

  只要能静态发现的bug不要放到线上,比如对参数、返回值的检查,在python3.x中可以使用注解(Function Annotations),python2.x也可以自行封装decorator来做检查。对代码行为,既可以使用Coverity这种高大上的商业软件,或者王垠大神的Pysonar2,也可以使用ast编写简单的检查代码。

  第三:单元测试

  单元测试的重要性想必大家都知道,在python中出了官方自带的doctest、unittest,还有许多更强大的框架,比如nose、mock。

  第四:100%的覆盖率测试

  对于python这种动态语言,出了执行代码,几乎没有其他比较好的检查代码错误的手段,所以覆盖率测试是非常重要的。可以使用python原生的sys.settrace、sys.gettrace,也可以使用coverage等跟更高级的工具。

原文出处:http://www.cnblogs.com/xybaby/p/7208496.html


识别图中二维码,领取python全套视频资料

转载于:https://www.cnblogs.com/IT-Scavenger/p/9642558.html

动态语言的灵活性是把双刃剑 -- 以Python语言为例相关推荐

  1. python动态语言双刃性_动态语言的灵活性是把双刃剑:以 Python 语言为例

    原标题:动态语言的灵活性是把双刃剑:以 Python 语言为例 本文有些零碎,总题来说,包括两个问题:(1)可变对象(最常见的是list dict)被意外修改的问题,(2)对参数(parameter) ...

  2. python语言特点粘性扩展_010 深入理解Python语言

    [TOC] 一.概述 计算机技术的演进 编程语言的多样初心 Python语言的特点 "超级语言"的诞生 二.计算机技术的演进 2.1 计算机技术的演进过程 - - - 2017- ...

  3. python语言合法命名有哪些-以下不是 Python 语言合法命名的是 :( )_学小易找答案...

    [单选题]字符串是一个字符序列, 给 字符串 s ,以下表示 s 从右 侧 向左第三个字符的是: ‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‪‫‪‪‪‪‪‫‪‪‪‪‪‪( ) [简答题]1课堂示范两款西装, ...

  4. 下列有关python语言的说法正确的是-关于 Python 语言的注释,以下选项中描述正确的是( )...

    [多选题]以下选项属于 Python 整数类型的是( ) [其它]根据CAD原文件绘制别墅立面图:如图 别墅立面图002.dwg [单选题]字符串是一个连续的字符序列,用________方式打印出可以 ...

  5. python语言自学-为什么建议大家都学习Python语言?原因在这

    有很多人在问小编,为什么说学人工智能一定要学Python?运行速度慢不好之类的,今天就让小编谈谈自己对于Python的感受. 一.先来说说Python的前景 随着"大数据"&quo ...

  6. python语言能做什么软件-什么是Python语言,Python语言可以用来做什么?

    什么是Python语言? Python是一种计算机程序设计语言.你可能已经听说过很多种流行的编程语言,比如非常难学的C语言,非常流行的Java语言,适合初学者的Basic语言,适合网页编程的JavaS ...

  7. 精通python语言要多久-这样学可以精通Python语言

    你们所说的什么要怎样数据分析.爬虫.Web 等,在我看来那不是精通 Python,而是精通某一领域,抛开 Python 换用另一门语言也可,在我看来,精通 Python 语言大约需要如下这些步骤(你有 ...

  8. python语言程序设计基础网课-程序设计基础(Python语言)答案

    [单选题]下面代码的输出结果是 ‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬ d = ...

  9. 教孩子学编程 python语言版_教孩子学编程 PYTHON语言版 PDF_IT教程网

    资源名称:教孩子学编程 PYTHON语言版 PDF 资源目录: 第1章Python基础--认识环境1 1.1认识Python3 1.2用Python编写程序5 1.3运行Python程序5 1.4本章 ...

最新文章

  1. Unity中对象池的使用
  2. 局部图像特征描述概述
  3. df命令---Linux学习笔记
  4. 使用Spring Webservices构建SOAP Webservices代理模块
  5. start uml怎么自动生成代码_基于UML-RT和Papyrus-RT的系统建模与代码生成
  6. 图解 Android 广播机制
  7. 开源中文关系抽取框架,来自浙大知识引擎实验室
  8. 鲸鱼优化算法_鲸鱼优化算法:一种群体智能最优化方法
  9. CTF warmup
  10. 灌区农业水价综合改革解决方案
  11. 记录一次dns劫持及其解决办法
  12. AIOC快速卸载3dsmax2014
  13. 高精度加法(C++,高精度)
  14. YUTUc++沙盒小游戏5.0(亲测可运行)
  15. 机器视觉在服务机器人中的应用
  16. 互联网晚报 | 10月24日 星期日 | 华为鸿蒙生态建设投入已超500亿;瑞幸门店端扭亏为盈;文旅部要求暂停经营旅游专列业务...
  17. RabbitMQ简介以及AMQP协议
  18. 比尔-盖茨2010年年信:世界首富的幸福观
  19. 一篇文章把你带入到JavaScript中的闭包与高级函数
  20. 2021年广东省专业技能大赛——大数据技术与应用 真题题目及解析(2)

热门文章

  1. 前置体验,才是打动用户的神器
  2. 面向对象编程(OOP)----BLUE大师JS课堂笔记(二)
  3. 3D-2D:PnP算法原理
  4. fpga数据位宽截取_FPGA信号截位策略研究
  5. mysql数据导入python_利用python将mysql中的数据导入excel
  6. Java每日一讲讲什么好_撩课-Java每天10道面试题第1天
  7. php url模式在哪修改,如何在nginx环境中启用php的pathinfo URL模式[修正]
  8. docker添加jar包_Maven系列教材 (七)- 如何添加第三方jar包
  9. c# tcp显示服务器图片,C# TCP通迅原码(服务器与客户端相互发送文本与图片)
  10. 和12岁小同志搞创客开发:手撕代码,做一款亮度可调节灯