前言这是“PythonTinkerers”系列的第三篇文章。数字是几乎所有编程语言中最基本的数据类型,是我们通过代码连接现实世界的基础。Python中共有三种数值类型:整数(int)、浮点数(float)和复数(complex)。在大多数情况下,我们只需要处理前两个。整数在Python中更省心,因为它们无符号且无符号且永远不会溢出。但是浮点数和大多数其他编程语言一样,仍然存在精度问题,这常常让很多刚进入编程世界的新人感到困惑:“为什么浮点数不准确?”。Python中的字符串比数字复杂得多。要想掌握它,就得搞清楚bytes和str的区别。如果更倒霉,你还是Python2用户,就够你喝几壶unicode和字符编码问题了(赶紧迁移到Python3,今天!)。不过,以上都不是本文的主题。如果你有兴趣,你可以在网上找到成堆的相关资料。在本文中,我们将讨论一些更微妙和不太常见的编程实践。帮助您编写更好的Python代码。内容目录最佳实践1少写字面数字,使用enum枚举类型改进代码2裸字符串处理不要走得太远3不需要预先计算字面表达式实用技巧1多行出现多行时级别缩进字符串2布尔值其实就是“数字”3提高超长字符串的可读性4不要忘记那些以“r”开头的内置字符串函数5使用“无限”的常见错误float("inf")1"value=1"不是线程安全的2字符串拼接并不慢最佳实践1.少写字面数字“整数字面值”是指直接出现在代码中的数字。它们分布在代码的各个角落,例如代码delusers[0]中的0就是一个数字字面量。它们简单、实用,而且每个人每天都在写作。但是,当您在代码中不断重复某些文字时,您的“代码质量警告灯”应该呈黄色亮起。比如你刚入职一个你崇拜已久的新公司,同事交给你的项目中有这样一个功能:defmark_trip_as_featured(trip):"""在推荐栏中添加行程"""iftrip.source==11:do_some_thing(trip)eliftrip.source==12:do_some_other_thing(trip)......return这个函数是做什么的?您正在尝试弄清楚,但是trip.source==11呢?那么==12呢?这两行代码很简单,没有使用任何神奇的功能。但是,如果您不熟悉这些代码,可能需要整个下午才能弄清楚它们的含义。问题在于那几个数字文字。最先写这个功能的人可能是公司成立之初加入的老程序员。而且他很清楚那些数字的含义。但如果您是这段代码的新手,那就完全是另外一回事了。使用枚举改进代码那么,如何改进这段代码呢?最直接的方式就是给这两个条件分支加上注释。但在这里,“添加注释”显然不是提高代码可读性的最佳方式(事实上,在其他大多数情况下并非如此)。我们需要用有意义的名称来替换这些字面量,而枚举类型(enum)最适合于此。enum是Python从3.4版本开始引入的内置模块。如果您使用的是早期版本,可以通过pipinstallenum34安装。这是使用枚举的示例代码:#-*-coding:utf-8-*-fromenumimportIntEnumclassTripSource(IntEnum):FROM_WEBSITE=11FROM_IOS_CLIENT=12defmark_trip_as_featured(trip):iftrip.source==TripSource.FROM_WEBSITE:do_some_thing(trip)eliftrip.source==TripSource.FROM_IOS_CLIENT:do_some_other_thing(trip)......return将重复的数字字面量定义为枚举类型,这样不仅提高了代码的可读性,同时也降低了出现bug的几率减少。试想一下,如果你在判断某一个分支的时候,把11误打成了111,会发生什么?我们总是犯这个错误,而且很难及早发现。把这些数字字面量都放到枚举类型中,可以更好的避免这种问题。同样,将字符串文字重写为枚举会产生相同的好处。使用枚举类型而不是文字的好处:提高代码可读性:没有人需要记住某个幻数代表什么提高代码正确性:减少由错误的数字或字母引起错误的可能性当然,你没有在all需要把代码中所有的字面量都改成枚举类型。可以使用代码中出现的文字,只要它们在放置它们的上下文中易于理解即可。比如那些经常作为数字下标出现的0和-1就完全没问题了,因为大家都知道是什么意思。2.原始字符串处理不要走得太远什么是“裸字符串处理”?在本文中,它指的是仅使用基本的加减乘除和循环,通过内置函数/方法来操作字符串以获得我们需要的结果。每个人都写过这样的代码。有时我们需要拼接一大段发送给用户的报警信息,有时我们需要构造一大段发送给数据库的SQL查询语句,像这样:deffetch_users(conn,min_level=None,gender=None,has_membership=False,sort_field="created"):"""获取用户列表:paramintmin_level:最低要求的用户级别,默认为所有级别:paramintgender:过滤用户性别,默认为所有性别:paraminthas_membership:filterall会员/非会员用户,默认为非会员:paramstrsort_field:排序字段,默认创建"用户创建日期":returns:list:[(UserID,UserName),...]"""#一种古老的SQL拼接技术,使用“WHERE1=1”简化字符串拼接操作#区分查询参数,避免SQL注入问题statement="SELECTid,nameFROMusersWHERE1=1"params=[]ifmin_levelisnot无:语句+="ANDlevel>=?"params.append(min_level)如果性别不是None:语句+="ANDgender>=?"params.append(gender)ifhas_membership:statement+="ANDhas_membership==true"else:statement+="ANDhas_mmembership==false"statement+="ORDERBY?"params.append(sort_field)returnlist(conn.execute(statement,params))我们之所以这样拼接出需要的字符串——这里是SQLstatement-这是因为简单、直接、直观,但是这样做最大的问题是随着功能逻辑越来越复杂,这种拼接代码会变得容易出错,难以扩展。其实就是上面的Demo代码也只是没有明显的bug(谁知道有没有其他隐藏的问题),其实对于SQL语句这样的结构化、正则化的字符串,最好还是采用面向对象的方式来构造和编辑的方式.下面这段代码使用了SQLAlchemy模块完成同样的功能:deffetch_users_v2(conn,min_level=None,gender=None,has_membership=False,sort_field="created"):"""获取用户列表"""query=select([users.c.id,users.c.name])如果min_level不是None:query=query.where(users.c.level>=min_level)如果性别不是无:query=query.where(users.c.gender==gender)query=query.where(users.c.has_membership==has_membership).order_by(users.c[sort_field])returnlist(conn.execute(query))上面的fetch_users_v2函数更短,也更易于维护,完全不用担心SQL注入问题。所以,当你的代码中出现复杂的裸字符串处理逻辑时,请尝试用下面的方式代替:问:目标/源字符串是否结构化并遵循一定的格式?是:找找有没有开源的对象模块来操作它们,或者自己写一个SQL:SQLAlchemyXML:lxmlJSON,YAML...否:尝试使用模板引擎,而不是复杂的字符串处理逻辑来达到目的Jinja2makoMustache3。计算字面量表达式偶尔会在我们的代码中出现一些复数,像这样deff1(delta_seconds):#如果超过11天,什么都不做ifdelta_seconds>950400:return...之前我说了,没有错上面的代码。首先,我们在一本小书上计算(当然像我这样聪明的人会用IPython):11天有多少秒?.然后把结果950400这个幻数填入我们的代码,最后心满意足的在上面加上一行注释:告诉大家这个幻数是怎么来的。我想问的是:“为什么我们不把代码写成好像delta_seconds<11243600:?”“性能”,答案一定是“性能”。我们都知道Python是一种(速度很差)解释型语言,所以预计算950400正是因为我们不想在每次调用函数f1时都承担这部分计算开销。但事实是:即使我们把代码改成ifdelta_seconds<11243600:,函数也不会有任何额外的开销。Python代码在执行时被解释器编译成字节码,字节码中隐藏着真相。我们用dis模块看看:deff1(delta_seconds):ifdelta_seconds<11*24*3600:returnimportdisdis.dis(f1)#dis执行结果50LOAD_FAST0(delta_seconds)2LOAD_CONST1(950400)4COMPARE_OP0(<)6POP_JUMP_IF_FALSE1268LOAD_CONST0(无)10RETURN_VALUE>>12LOAD_CONST0(无)14RETURN_VALU请参阅上面的2LOAD_CONST1(950400)?这意味着当Python解释器将源代码编译成字节码时,它会计算整数表达式11243600并将其替换为950400。因此,当我们的代码中需要有复杂的计算字面量时,请保留整个计算公式。它对性能没有影响,并且会增加代码的可读性。提示:除了预先计算数字文字表达式外,Python解释器还对字符串和列表执行类似的操作。一切都与性能有关。谁让你抱怨Python很慢?实用技巧1、布尔值其实就是“数字”。在大多数情况下,Python中的True和False这两个布尔值可以直接等价于两个整数1和0,就像这样:>>>True+12>>>1/FalseTraceback(最近调用last):File"
