网站首页 > 论文格式> 文章内容

如何像Python高手(Pythonista)一样编程

※发布时间:2017-9-8 12:13:22   ※发布作者:A   ※出自何处: 

  最近在网上看到一篇介绍Pythonic编程的文章:Code Like a Pythonista: Idiomatic Python,其实作者在2006的PyCon会议后就写了这篇文章,写这篇文章的主要原因是作者发现很多有经验的Pythoner写出的代码不够Pythonic。我觉得这篇文章很不错,所以将它用中文写了下来(不是逐字的翻译,中间加了一些自己的理解),分享给大家。另:由于本人平时时间有限,这篇文章翻译了比较长的时间,如果你发现了什么不对的地方,欢迎指出。。

  这首特别的“诗”开始作为一个笑话,但它确实包含了很多关于Python背后的哲学真理。Python之禅已经正式成文PEP 20,具体内容见:PEP 20

  Abelson & Sussman在《计算机程序的构造和解释》一书中说道:程序是写来给人读的,只是顺带让机器执行。所以,我们在编码时应该尽量让它更易读懂。PEP8是Python的编码规范,文档见:PEP 8,PEP是Python Enhancement Proposal的缩写。PEP8包括很多编码的规范,下面主要介绍一下缩进和命名等内容。

  空和缩进在Python语言中非常重要,它替代了其他语言中{}的作用,用来区分代码块和作用域。在这方面PEP8有以下的:

  1、每次缩进使用4个空2、不要使用Tab,更不要Tab和空混用3、两个方法之间使用一个空行,两个Class之间使用两个空行4、添加一个空在字典、列表、序列、参数列表中的“,“后,以及在字典中的”:“之后,而不是之前5、在赋和比较两边放置一个空(参数列表中除外)6、紧随括号后面或者参数列表前一个字符不要存在空

  命名规范是编程语言的基础,而且大部分的规范对于高级语言来说都是一样的,Python的基本规范如下:

  以上内容只是对PEP8做了非常简单的介绍,由于今天的主题不在于此,所以就不在这里多讲。想要更加深入的了解Python编码规范,可以阅读PEP8文档Google Python编码规范等内容。

  可能你已经在其他地方见过这种写法,但是你知道Python是如何实现这种语法的吗?首先,逗号(,)是Python中tuple数据结构的语法;的语执行一下的操作:

  3、然后将tuple的第一个元素赋给左边的第一个变量,第二个元素赋给左边第二个变量。

  PS:在使用这种语法时,需要确保左边的变量个数和右边tuple的个数一致,否则,Python会抛出ValueError异常。

  我们知道:逗号(,)在Python中是创建tuple的构造器,所以我们可以按照的方式很方便的创建一个tuple;需要注意的是:如果声明只有一个元素的tuple,末尾必须要带上逗号,两个以上的元素则不需要。声明tuple的语法很简单,但同时它也比较坑:如果你发现Python中的变量不可思议的变成了tuple,那很可能是因为你多写了一个逗号。。

  这是Python中比较有用的一个功能,不过有很多人不知道(我也是接触Python很久之后才知道的)。。在Python的交互式控制台中,当你计算一个表达式或者调用一个方法的时候,运算的结果都会放在一个临时的变量_里面。_(下划线)用来存储上一次的打印结果,比如:

  PS:当返回结果为None的时候,控制台不会打印,_里面存储的也就不会改变。

  假如现在有一个list,里面是一些字符串,你现在需要将它们合并成一个字符串,最简单的方法,你可以按照下面的方式去处理:

  但是,很快你会发现:这种方法非常低效,尤其当list非常大的时候。Python中的字符串对象是不可改变的,因此对任何字符串的操作如拼接,修改等都将产生一个新的字符串对象,而不是基于原字符串。所以,的方消耗很大的内存:它需要计算,存储,同时扔掉中间的计算结果。正确的方法是使用Python中的join方法:

  当合并元素比较少的时候,使用join方法看不出太大的效果;但是当元素多的时候,你会发现join的效率还常明显的。不过,在使用的时候请注意:join只能用于元素是字符串的list,它不会进行任何的强制类型转换。连接一个存在一个或多个非字符串元素的list时将抛出异常。

  当你需要判断一个KEY是否在dict中或者要遍历dict的KEY时,最好的方法是使用关键字in:

  Python的dict对象是对KEY做过hash的,而keys()方将dict中所有的KEY作为一个list对象;所以,直接使用in的时候执行效率会比较快,代码也更简洁。

  dict是Python内置的数据结构,在写Python程序时会经常用到。这里介绍一下它的get方法和deultdict方法。

  在获取dict中的数据时,我们一般使用index的方式,但是如果KEY不存在的时候会抛出KeyError。这时候你可以使用get方法,使用方法:dict.get(key, deult=None),可以避免异常。例如:

  dict本身有个fromkeys方法,可以通过一个list生成一个dict,不过得提供默认的value,例如:

  需要注意的是:由于dict本身是无序的,所以通过keys()和values()方法获得的list的顺序已经和原始的list不一样了。。

  在Python中,我们在遍历列表的时候,可以通过enumerate方法来获取遍历时的index,比如:

  enumerate方法是惰性方法,所以它只会在需要的时候生成一项,也因此在上述代码print的时候需要包装一个list。enumerate其实是一个生成器(generator),这个下面会讲到。使用enumerate之后,for循环变得很简单:

  使用enumerate的代码比其他两个都短,而且更简单,更容易读懂。下面的例子可以说明一下enumerate实际返回的数据:一个迭代器,

  在Python中,变量没有数据类型,是附属于对象的标示符名称,如下图:实际,这段表明了像python,PHP这类动态脚本语言中“变量”包含了两个内容:1标识符名称 2 标识符所对应(引用)的(对象),也就是说“变量”不在是一个容器。

  现在,变量a 是附属在整数对象 2 。最初的整数对象 1 已经没有指向它的变量 a,它可能还存在,但是我们已经不能通过变量 a获得。当一个对象没有了指向它的引用的时候,它将会被从内存中删除(垃圾回收)。如果我们将存在的变量赋给一个新的变量,Python会在已经存在的对象上加上一个指向自己的变量(tag)。

  PS:Python中的变量,引用等设计和其他语言不同,这里只是将原文翻译说明了一下,更多的介绍可以参看:Python中的变量、引用、拷贝和作用域

  对于Python初学者来说,Python的方法默认参数有一个很容易犯错的地方:在默认参数中使用可变对象,甚至有不少Python老鸟也可能会在这个问题上掉坑里,如果他们不能理解Python的对象引用。。问题如下:

  这个问题的主要原因是:a_list参数的默认是一个空的list,它在函数定义的时候已经被创建。所以,之后每次调用该函数的时候,a_list的默认都是这个list对象。List,dict和set是可变对象,如果想在函数中获取一个默认的list(dict or set)对象,正确的做法是在函数中创建:

  的代码中,我们指定了用来式化的的名字,然后可以根据name在字典中查找相应的value。其实,的name和messages已经在local命名空间中定义,所以,我们可以利用这一点:

  locals()方法返回一个包含所有本地变量的字典。这个功能非常强大,你可以不必担心提供的values是否和模板匹配;但是同时这个也常的:你将会整个本地命名空间给调用者,这一点需要你注意。

  在Python中,对象有一个__dict__属性,你可以在式化字符串的时候使用;

  flags可以有,-, 或0。表示右对齐。-表示左对齐。 为一个空,表示在正数的左侧填充一个空,从而与负数对齐。0表示使用0填充。

  的width, precision为两个整数。我们可以利用*,来动态代入这两个量。比如:

  str.format()方法是在Python 2.6中引入的,它通过 {} 和 : 来代替 % ,功能非常强大。具体的用法见下面的例子:

  List Comprehensions即迭代器(列表生成式),是Python内置的非常简单却强大的可以用来创建list的生成式。在不使用迭代器的时候,创建一个新列表可以使用for和if来实现:

  列表生成式非常简洁的,不过是在某种程度上。你可以在列表生成式中使用多个for循环和多个if语句,但是两个以上的for和if语句会让列表生成式非常复杂,这时候直接用for循环。根据Zen of Python,选择更容易读的方式。下面是一些例子:

  生成器和提到的迭代器差不多,可以说:生成器是一种特殊的迭代器;但是它们之间有一个很大的区别:迭代器是的,而生成器是懒惰的,具体来说:迭代器会一次性的计算出整个结果列表,而生成器只在需要的时候计算一个。这个特性在列表非常大,或者需要一步一步计算的时候非常有用。

  在的例子中,我们只需要平方和,不需要平方的list,所以我们使用生成器xrange。如果我们计算1 ~ 1000000000的平方和,使用迭代器的话会内存溢出,但是生成器却不会:

  在语法上,迭代器会有一个[],但是生成器没有;不过有时候,生成器需要(),所以,最好每次都带上。一些自定义的生成器例子:

  需要注意的是:list的sort()方直接在原list变量上排序,改变原本的list对象,并且该方法不会返回一个list对象。如果你需要不改变原list,并且返回新的list对象的话,可以使用Python的orted方法:

  但是,如果你想对一个list进行排序,不过不想使用默认的排序方式,比如你可能需要先根据第二行排序,再根据第四行排序。这时候,我们也可以使用sort()方法,但是得提供一个自定义的排序方法:

  DSU即Decorate-Sort-Undecorate,中文就是封装-排序-解封。DSU方法不会创建自定义的排序方法,而是创建一个辅助的排序列表,然后对这个列表进行默认排序。需要说明的是:DSU方法是一种比较老的方法,现在已经基本上不使用了,不过这里还是给出一个简单的例子说明一下:

  上述代码第一行创建了一个tuple的list,tuple中的前两项是用来排序的字段,最后一项是原数据;第二行使用sort()方法对辅助的list进行默认的排序;最后一行是从已经排序的辅助list中获取原数据,重新组成list。

  这种方法是使用复杂度和内存空间来减少计算时间,比较简单,也比较快,但是我们得复制原列表的数据。

  自从Python 2.4之后,list.sort()和sorted()都添加了一个key参数用来指定一个函数,这个函数作用于每个list元素,在做cmp之前调用。key参数是一个函数,这个函数有一个参数,返回一个用来排序的关键字。这种排序方法很快,因为key方法在每个输入的record上只执行一次。你可以使用Python内置的函数(len, str.lower)或者自定义函数作为key参数,下面是一个简单的例子:

  检查数据可以让程序更健壮,用术语来说就是防御性编程。检查数据的时候,有EAFP和LBYL两种不同的编程风,具体的意思如下:

  异常处理总是比事先检查容易,因为你很难提前想到所有可能的问题。所以,一般情况下编码时会倾向使用EAFP风,但它也不是适应所有的情况。两个风的优缺点如下:

  对于LBYL,容易打乱思维,本来业务逻辑用一行代码就可以搞定的。却多出来了很多行用于检查的代码。防御性的代码跟业务逻辑混在一块降低了可读性。而EAFP,业务逻辑代码跟防御代码隔离的比较清晰,更容易让开发者专注于业务逻辑。不过,异常处理会影响一点性能。因为在发生异常的时候,需要进行保留现场、回溯traceback等操作。但其实性能相差不大,尤其是异常发生的频率比较低的时候。

  另外,需要注意的是,如果涉及到原子操作,强烈推荐用EAFP风。比如我某段程序逻辑是根据redis的key是否存在进行操作。如果先if exists(key),然后do something。这样就变成2步操作,在多线程并发的时候,可能key的状态已经被其他线程改变了。而用EAFP风则可以确保原子性。

  PS:在使用EAFP风捕获异常时,尽量指明具体的异常,不要直接捕获Exception。否则会捕获到其他未知的异常,如果有问题,你会很难去定位(debug)。

  你可能在其他地方见过这种使用通配符*的引用方式,可能你也比较喜欢这种方式。但是,这里要说的是:请不要使用这种引用方式!

  通配符引用的方式属于Python中的面,这种方式会导致命名空间污染的问题。你可能会在本地命名空间中得到意想不到的东西,而且这种方式引入的变量可能将你在本地定义的变量覆盖,在这种情况下,你很难弄清楚变量来自哪里。所以,通配符引用的方式虽然方便,但可能会导致各种各样奇怪的问题,在Python项目中尽量不要用这种方式。

  这种方式直接import的是模块,然后通过模块访问其中的变量,Class和方法。使用这种方式可以很清晰的知道变量来自哪里:

  为了使一个Python文件既可以被引用,又可以直接执行,你可以在Python文件中加上这样的代码:

  当被引用时,一个模块(module)的__name__属性会被设置为该文件的文件名(不包括.py后缀)。所以,代码片段中if语句中的脚本在被引用的时候不会执行。当把Python文件作为一个脚本执行的时候,__name__属性则被设置为__main__,这时候if语句中的脚本才会被执行。

  最好不要在Python文件中直接写可执行的语句,应该将这些代码放在方法或类里面,必要的时候放在if __name__ == __main__:中。一个Python文件的结构可以参考下面的:

  Python是一种脚本语言,有时候我们会直接在命令行运行Python文件,这时候可能需要解析命令行传入的参数,下面是一个例子:cmdline.py

  在你写代码之前,你需要先看一下有没有其他人已经实现了类的功能。你可以从下面的几个地方寻找:

  m0_38081481:作者在开始对hfp、hbp、hsw几个定义有错误,说明和图有出入,还有hsw的必要性的定义有问题。

  推荐: