最近在网上看到一篇介绍Pythonic编程的文章:Code Like a Pythonista: Idiomatic Python,其实做者在2006的PyCon会议后就写了这篇文章,写这篇文章的主要缘由是做者发现不少有经验的Pythoner写出的代码不够Pythonic。我以为这篇文章很不错,因此将它用中文写了下来(不是逐字的翻译,中间加了一些本身的理解),分享给你们。另:因为本人平时时间有限,这篇文章翻译了比较长的时间,若是你发现了什么不对的地方,欢迎指出。。php
The Zen of Python是Python语言的指导原则,遵循这些基本原则,你就能够像个Pythonista同样编程。具体内容你能够在Python命令行输入import this看到:html
The Zen of Python, by Tim Peters Beautiful is better than ugly. # 优美胜于丑陋(Python以编写优美的代码为目标) Explicit is better than implicit. # 明了胜于晦涩(优美的代码应当是明了的,命名规范,风格类似) Simple is better than complex. # 简洁胜于复杂(优美的代码应当是简洁的,不要有复杂的内部实现) Complex is better than complicated. # 复杂胜于凌乱(若是复杂不可避免,那代码间也不能有难懂的关系,要保持接口简洁) Flat is better than nested. # 扁平胜于嵌套(优美的代码应当是扁平的,不能有太多的嵌套) Sparse is better than dense. # 间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题) Readability counts. # 可读性很重要(优美的代码是可读的) Special cases aren't special enough to break the rules. Although practicality beats purity. # 即使假借特例的实用性之名,也不可违背这些规则(这些规则至高无上) Errors should never pass silently. Unless explicitly silenced. # 不要包容全部错误,除非你肯定须要这样作(精准地捕获异常,不写except:pass风格的代码) In the face of ambiguity, refuse the temptation to guess. # 当存在多种可能,不要尝试去猜想 There should be one-- and preferably only one --obvious way to do it. # 而是尽可能找一种,最好是惟一一种明显的解决方案(若是不肯定,就用穷举法) Although that way may not be obvious at first unless you're Dutch. # 虽然这并不容易,由于你不是 Python 之父(这里的Dutch是指Guido) Now is better than never. Although never is often better than *right* now. # 作也许好过不作,但不假思索就动手还不如不作(动手以前要细思量) If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. # 若是你没法向人描述你的方案,那确定不是一个好方案;反之亦然(方案测评标准) Namespaces are one honking great idea -- let's do more of those! # 命名空间是一种绝妙的理念,咱们应当多加利用(倡导与号召)
这首特别的“诗”开始做为一个笑话,但它确实包含了不少关于Python背后的哲学真理。Python之禅已经正式成文PEP 20,具体内容见:PEP 20python
Abelson & Sussman在《计算机程序的构造和解释》一书中说道:程序是写来给人读的,只是顺带让机器执行。因此,咱们在编码时应该尽可能让它更易读懂。PEP8是Python的编码规范,官方文档见:PEP 8,PEP是Python Enhancement Proposal的缩写。PEP8包括不少编码的规范,下面主要介绍一下缩进和命名等内容。git
空格和缩进(WhiteSpace and Indentation)github
空格和缩进在Python语言中很是重要,它替代了其余语言中{}的做用,用来区分代码块和做用域。在这方面PEP8有如下的建议:redis
一、每次缩进使用4个空格 二、不要使用Tab,更不要Tab和空格混用 三、两个方法之间使用一个空行,两个Class之间使用两个空行 四、添加一个空格在字典、列表、序列、参数列表中的“,“后,以及在字典中的”:“以后,而不是以前 五、在赋值和比较两边放置一个空格(参数列表中除外) 六、紧随括号后面或者参数列表前一个字符不要存在空格
Python命名express
命名规范是编程语言的基础,并且大部分的规范对于高级语言来讲都是同样的,Python的基本规范以下:编程
一、方法 & 属性:joined_lower 二、常量:joined_lower or ALL_CAPS 三、类:StudlyCaps 四、类属性:interface, _internal, __private 五、camelCase only to conform to pre-existing conventions
以上内容只是对PEP8作了很是简单的介绍,因为今天的主题不在于此,因此就不在这里多讲。想要更加深刻的了解Python编码规范,能够阅读PEP8官方文档和Google Python编码规范等内容。数据结构
在其余语言中,交换两个变量值的时候,能够这样写:多线程
temp = a a = b b = temp
在Python中,咱们能够简单的这样写:
b, a = a, b
可能你已经在其余地方见过这种写法,可是你知道Python是如何实现这种语法的吗?首先,逗号(,)是Python中tuple数据结构的语法;上面的语法会执行一下的操做:
一、Python会先将右边的a, b生成一个tuple(元组),存放在内存中;
二、以后会执行赋值操做,这时候会将tuple拆开;
三、而后将tuple的第一个元素赋值给左边的第一个变量,第二个元素赋值给左边第二个变量。
再举个tuple拆分的例子:
In [1]: people = ['David', 'Pythonista', '15145551234'] In [2]: name, title, phone = people In [3]: name Out[3]: 'David' In [4]: title Out[4]: 'Pythonista' In [5]: phone Out[5]: '15145551234'
这种语法在For循环中很是实用:
In [6]: people = [['David', 'Pythonista', '15145551234'], ['Wu', 'Student', '15101365547']] In [7]: for name, title, phone in people: ...: print name, phone ...: David 15145551234 Wu 15101365547
PS:在使用这种语法时,须要确保左边的变量个数和右边tuple的个数一致,不然,Python会抛出ValueError异常。
更多tuple的例子:
>>> 1, (1,) >>> (1,) (1,) >>> (1) 1 >>> value = 1, >>> value (1,)
咱们知道:逗号(,)在Python中是建立tuple的构造器,因此咱们能够按照上面的方式很方便的建立一个tuple;须要注意的是:若是声明只有一个元素的tuple,末尾必需要带上逗号,两个以上的元素则不须要。声明tuple的语法很简单,但同时它也比较坑:若是你发现Python中的变量难以想象的变成了tuple,那极可能是由于你多写了一个逗号。。
这是Python中比较有用的一个功能,不过有不少人不知道(我也是接触Python好久以后才知道的)。。在Python的交互式控制台中,当你计算一个表达式或者调用一个方法的时候,运算的结果都会放在一个临时的变量 _ 里面。_(下划线)用来存储上一次的打印结果,好比:
>>> import math >>> math.pi / 3 1.0471975511965976 >>> angle = _ >>> math.cos(angle) 0.50000000000000011 >>> _ 0.50000000000000011
PS:当返回结果为None的时候,控制台不会打印,_ 里面存储的值也就不会改变。
假如如今有一个list,里面是一些字符串,你如今须要将它们合并成一个字符串,最简单的方法,你能够按照下面的方式去处理:
colors = ['red', 'blue', 'green', 'yellow'] result = '' for s in colors: result += s
可是,很快你会发现:这种方法很是低效,尤为当list很是大的时候。Python中的字符串对象是不可改变的,所以对任何字符串的操做如拼接,修改等都将产生一个新的字符串对象,而不是基于原字符串。因此,上面的方法会消耗很大的内存:它须要计算,存储,同时扔掉中间的计算结果。正确的方法是使用Python中的join方法:
result = ','.join(colors)
当合并元素比较少的时候,使用join方法看不出太大的效果;可是当元素多的时候,你会发现join的效率仍是很是明显的。不过,在使用的时候请注意:join只能用于元素是字符串的list,它不会进行任何的强制类型转换。链接一个存在一个或多个非字符串元素的list时将抛出异常。
当你须要判断一个KEY是否在dict中或者要遍历dict的KEY时,最好的方法是使用关键字in:
d = {'a': 1, 'b': 2} if 'c' in d: print True # DO NOT USE if d.has_key('c'): print True for key in d: print key # DO NOT USE for key in d.keys(): print key
Python的dict对象是对KEY作过hash的,而keys()方法会将dict中全部的KEY做为一个list对象;因此,直接使用in的时候执行效率会比较快,代码也更简洁。
dict是Python内置的数据结构,在写Python程序时会常常用到。这里介绍一下它的get方法和defaultdict方法。
一、get
在获取dict中的数据时,咱们通常使用index的方式,可是若是KEY不存在的时候会抛出KeyError。这时候你可使用get方法,使用方法:dict.get(key, default=None),能够避免异常。例如:
d = {'a': 1, 'b': 2} print d.get('c') # None print d.get('c', 14) # 14
二、fromkeys
dict自己有个fromkeys方法,能够经过一个list生成一个dict,不过得提供默认的value,例如:
# ⽤序列作 key,并提供默认value >>> dict.fromkeys(['a', 'b', 'c'], 1) # {'a': 1, 'c': 1, 'b': 1}
三、setdefault
有些状况下,咱们须要给dict的KEY一个默认值,你能够这样写:
equities = {} for (portfolio, equity) in data: if portfolio in equities: equities[portfolio].append(equity) else: equities[portfolio] = [equity]
上面的实现方式很麻烦,使用dict的setdefault(key, default)方法会更简洁,更效率。
equities = {}
for (portfolio, equity) in data:
equities.setdefault(portfolio, []).append(equity)
setdefault方法至关于"get, or set & get",或者至关于"set if necessary, then get"
defaultdict是Python2.5以后引入的功能,具体的用法我已经在另一篇文章中详细介绍:Python的defaultdict模块和namedtuple模块
在Python中,你可使用zip方法将两个list组装成一个dict,其中一个list的值做为KEY,另一个list的值做为VALUE:
>>> given = ['John', 'Eric', 'Terry', 'Michael'] >>> family = ['Cleese', 'Idle', 'Gilliam', 'Palin'] >>> pythons = dict(zip(given, family)) >>> print pythons {'John': 'Cleese', 'Michael': 'Palin', 'Eric': 'Idle', 'Terry': 'Gilliam'}
相反的,你可使用dict的keys()和values()方法来获取KEY和VALUE的列表:
>>> pythons.keys()
['John', 'Michael', 'Eric', 'Terry']
>>> pythons.values()
['Cleese', 'Palin', 'Idle', 'Gilliam']
须要注意的是:因为dict自己是无序的,因此经过keys()和values()方法得到的list的顺序已经和原始的list不同了。。
在Python中,判断一个变量是否为True的时候,你能够这样作:
# 这样写 if x: pass # !不要这样写 if x == True: pass # 对于list,要这样写 if items: pass # !不要这样写 if len(items) == 0: pass
Python中的真值对象有如下几个:
False | True |
---|---|
False (== 0) | True (== 1) |
"" (空字符串) | 除 "" 以外的字符串(" ", "anything") |
0, 0.0 | 除 0 以外的数字(1, 0.1, -1, 3.14) |
[], (), {}, set() | 非空的list,tuple,set和dict ([0], (None,), ['']) |
None | 大部分的对象,除了明确指定为False的对象 |
对于本身声明的class,若是你想明确地指定它的实例是True或False,你能够本身实现class的__nonzero__或__len__方法。当你的class是一个container时,你能够实现__len__方法,以下:
class MyContainer(object):
def __init__(self, data):
self.data = data
def __len__(self):
""" Return my length. """
return len(self.data)
若是你的class不是container,你能够实现__nonzero__方法,以下:
class MyClass(object): def __init__(self, value): self.value = value def __nonzero__(self): """ Return my truth value (True or False). """ # This could be arbitrarily complex: return bool(self.value)
在Python 3.x中,__nonzero__方法被__bool__方法替代。考虑到兼容性,你能够在class定义中加上如下的代码:
__bool__ = __nonzero__
在Python中,咱们在遍历列表的时候,能够经过enumerate方法来获取遍历时的index,好比:
>>> items = 'zero one two three'.split() >>> print list(enumerate(items)) [(0, 'zero'), (1, 'one'), (2, 'two'), (3, 'three')] >>> for (index, item) in enumerate(items): print index, item
enumerate方法是惰性方法,因此它只会在须要的时候生成一项,也所以在上述代码print的时候须要包装一个list。enumerate实际上是一个生成器(generator),这个下面会讲到。使用enumerate以后,for循环变得很简单:
for (index, item) in enumerate(items): print index, item # compare: index = 0 for item in items: print index, item index += 1 # compare: for i in range(len(items)): print i, items[i]
使用enumerate的代码比其余两个都短,并且更简单,更容易读懂。下面的例子能够说明一下enumerate实际返回的数据:一个迭代器,
>>> enumerate(items)
<enumerate object at 0x011EA1C0>
>>> e = enumerate(items)
>>> e.next()
(0, 'zero')
>>> e.next()
(1, 'one')
>>> e.next()
(2, 'two')
>>> e.next()
(3, 'three')
>>> e.next()
Traceback (most recent call last):
File "<stdin>", line 1, in ?
StopIteration
在不少其余高级语言中,给一个变量赋值时会将"value"放在一个"盒子"里:
int a = 1;
如图:
如今,盒子"a"中包含了一个整数 1;将另一个"value"赋值给同一个变量时,会将"盒子"中的内容替换掉:
a = 2;
如图:
如今,盒子"a"中包含了一个整数 2;将变量赋值给其余一个变量时,会将"value"拷贝一份放在一个新的"盒子"中:
int b = a;
如图:
盒子"b"是第二个"盒子",里面是整数 2的一个拷贝,盒子"a"中是另一个拷贝。
在Python中,变量没有数据类型,是附属于对象的标示符名称,以下图:实际,这段代表了像python,PHP这类动态脚本语言中“变量”包含了两个内容:1 标识符名称 2 标识符所对应(引用)的值(对象),也就是说“变量”不在是一个容器。
a = 1
这里,整数 1 对象有一个名字为 "a" 的变量(tag)。若是咱们给变量 "a" 从新赋值,对Python来讲,只是将变量(tag) "a" 指向另一个对象:
a = 2
如今,变量 "a" 是附属在整数对象 2 上面。最初的整数对象 1 已经没有指向它的变量 "a",它可能还存在,可是咱们已经不能经过变量 "a"得到。当一个对象没有了指向它的引用的时候,它将会被从内存中删除(垃圾回收)。若是咱们将存在的变量赋值给一个新的变量,Python会在已经存在的对象上加上一个指向本身的变量(tag)。
b = a
变量 "a"和"b" 是指向同一个整数对象的。
PS:Python中的变量,引用等设计和其余语言不一样,这里只是将原文翻译说明了一下,更多的介绍能够参看:Python中的变量、引用、拷贝和做用域
对于Python初学者来讲,Python的方法默认参数有一个很容易犯错的地方:在默认参数中使用可变对象,甚至有很多Python老鸟也可能会在这个问题上掉坑里,若是他们不能理解Python的对象引用。。问题以下:
def bad_append(new_item, a_list=[]): a_list.append(new_item) return a_list >>> print bad_append('one') ['one'] >>> print bad_append('two') ['one', 'two']
这个问题的主要缘由是:a_list参数的默认值是一个空的list,它在函数定义的时候已经被建立。因此,以后每次调用该函数的时候,a_list的默认值都是这个list对象。List,dict和set是可变对象,若是想在函数中获取一个默认的list(dict or set)对象,正确的作法是在函数中建立:
def good_append(new_item, a_list=None): if a_list is None: a_list = [] a_list.append(new_item) return a_list
在许多编程语言中都包含有格式化字符串的功能,好比C语言中的格式化输入输出。Python中内置有对字符串进行格式化的操做符 "%" 以及str.format()方法。
一、操做符 "%"
Python中的 "%" 操做符和C语言中的sprintf相似。简单来讲,使用 "%" 来格式化字符串的时候,你须要提供一个字符串模板和用来插入的值。模板中有格式符,这些格式符为真实值预留位置,并说明真实数值应该呈现的格式。Python用一个tuple将多个值传递给模板,每一个值对应一个格式符。注意:给定的值必定要和模板中的格式符一一对应!
name = 'xianglong' messages = 3 text = ('Hello %s, you have %i messages' % (name, messages)) print text # Output: Hello xianglong, you have 3 messages
在上面的例子中,"Hello %s, you have %i messages" 是字符串模板。%s为第一个格式符,表示一个字符串。%i为第二个格式符,表示一个十进制整数。(name, messages)的两个元素为替换%s和%i的真实值。在模板和tuple之间,有一个%号分隔,它表明了格式化操做。
经常使用的格式符以下:
格式 | 描述 |
%% | 百分号 % 标记 |
%s | 字符串 (采用str()的显示) |
%r | 字符串 (采用repr()的显示) |
%c | 字符及其ASCII码 |
%b | 二进制整数 |
%d | 十进制整数 (有符号整数) |
%u | 十进制整数 (无符号整数) |
%i | 十进制整数 (有符号整数) |
%o | 八进制整数 (无符号整数) |
%x | 十六进制整数 (无符号整数) |
%X | 十六进制整数 (无符号整数) |
%e | 指数 (基底写为e) |
%E | 指数 (基底写为E) |
%f | 浮点数 |
%F | 浮点数,与上相同 |
%g | 指数(e)或浮点数 (根据显示长度) |
%G | 指数(E)或浮点数 (根据显示长度) |
%p | 指针(用十六进制打印值的内存地址) |
%n | 存储输出字符的数量放进参数列表的下一个变量中 |
使用操做符 "%" 也能够经过字典格式化字符串:
values = {'name': name, 'messages': messages} print ('Hello %(name)s, you have %(messages)i messages' % values) # Output: Hello xianglong, you have 3 messages
上面的代码中,咱们指定了用来格式化的值的名字,而后能够根据name在字典中查找相应的value。其实,上面的"name"和"messages"已经在local命名空间中定义,因此,咱们能够利用这一点:
print ('Hello %(name)s, you have %(messages)i messages' % locals())
locals()方法返回一个包含全部本地变量的字典。这个功能很是强大,你能够没必要担忧提供的values是否和模板匹配;可是同时这个也是很是危险的:你将会暴露整个本地命名空间给调用者,这一点须要你注意。
在Python中,对象有一个__dict__属性,你能够在格式化字符串的时候使用;
print ("We found %(error_count)d errors" % self.__dict__) # 等同于 print ("We found %d errors" % self.error_count)
另外,咱们还能够用以下的方式,对字符串格式化进一步的控制:%[(name)][flags][width].[precision]typecode,其中:
(name)为命名
flags能够有+,-,' '或0。+表示右对齐。-表示左对齐。' '为一个空格,表示在正数的左侧填充一个空格,从而与负数对齐。0表示使用0填充。
width表示显示宽度
precision表示小数点后精度
好比:
print("%+10x" % 10) # +a print("%04d" % 5) # 0005 print("%6.3f" % 2.3) # 2.300
上面的width, precision为两个整数。咱们能够利用*,来动态代入这两个量。好比:
print("%.*f" % (4, 1.2)) # 1.2000
Python实际上用4来替换*。因此实际的模板为"%.4f"。
二、str.format()方法
str.format()方法是在Python 2.6中引入的,它经过 {} 和 : 来代替 % ,功能很是强大。具体的用法见下面的例子:
In [1]: name = 'xianglong' In [2]: messages = 4 # 经过位置 In [3]: 'Hello {0}, you have {1} messages'.format(name, messages) Out[3]: 'Hello xianglong, you have 4 messages' # 经过关键字参数 In [4]: 'Hello {name}, you have {messages} messages'.format(name=name, messages=messages) Out[4]: 'Hello xianglong, you have 4 messages' # 经过下标 In [5]: 'Hello {0[0]}, you have {0[1]} messages'.format([name, messages]) Out[5]: 'Hello xianglong, you have 4 messages' # 格式限定符:填充与对齐 # ^、<、>分别是居中、左对齐、右对齐,后面带宽度 # :号后面带填充的字符,只能是一个字符,不指定的话默认是用空格填充 In [6]: 'Hello {0:>14}, you have {1:>14} messages'.format(name, messages) Out[6]: 'Hello xianglong, you have 4 messages' # 格式限定符:精度与类型f In [7]: '{:.2f}'.format(321.33345) Out[7]: '321.33' # 格式限定符:b、d、o、x分别是二进制、十进制、八进制、十六进制 In [8]: '{:b}'.format(14) Out[8]: '1110' In [9]: '{:d}'.format(14) Out[9]: '14' In [10]: '{:o}'.format(14) Out[10]: '16' In [11]: '{:x}'.format(14) Out[11]: 'e' # 格式限定符:千位分隔符 In [12]: '{:,}'.format(1234567890) Out[12]: '1,234,567,890'
更多关于Python字符串格式化的介绍,能够参看:PEP 3101 -- Advanced String Formatting
List Comprehensions即迭代器(列表生成式),是Python内置的很是简单却强大的能够用来建立list的生成式。在不使用迭代器的时候,建立一个新列表可使用for和if来实现:
new_list = [] for item in a_list: if condition(item): new_list.append(fn(item))
使用迭代器的话:
new_list = [fn(item) for item in a_list if condition(item)]
列表生成式很是简洁的,不过是在某种程度上。你能够在列表生成式中使用多个for循环和多个if语句,可是两个以上的for和if语句会让列表生成式很是复杂,这时候建议直接用for循环。根据Zen of Python,选择更容易读的方式。下面是一些例子:
>>> [n ** 2 for n in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> [n ** 2 for n in range(10) if n % 2] [1, 9, 25, 49, 81]
先出一个题:计算1 ~ 100的平方和。最简单的方法就是使用一个for循环:
total = 0 for num in range(1, 101): total += num * num
其实,咱们可使用Python内置的sum方法计算:
# 迭代器(列表生成式) total = sum([num * num for num in range(1, 101)]) # 生成器 total = sum(num * num for num in xrange(1, 101))
生成器和上面提到的迭代器差很少,能够说:生成器是一种特殊的迭代器;可是它们之间有一个很大的区别:迭代器是贪婪的,而生成器是懒惰的,具体来讲:迭代器会一次性的计算出整个结果列表,而生成器只在须要的时候计算一个值。这个特性在列表很是大,或者须要一步一步计算的时候很是有用。
在上面的例子中,咱们只须要平方和,不须要平方的list,因此咱们使用生成器xrange。若是咱们计算1 ~ 1000000000的平方和,使用迭代器的话会内存溢出,可是生成器却不会:
total = sum(num * num for num in xrange(1, 1000000000))
在语法上,迭代器会有一个"[]",可是生成器没有;不过有时候,生成器须要"()",因此,最好每次都带上。一些自定义的生成器例子:
# 过滤CSV文件中的空行
def filter_rows(row_iterator):
for row in row_iterator:
if row:
yield row
data_file = open(path, 'rb')
irows = filter_rows(csv.reader(data_file))
# 文件读取:open
datafile = open('datafile')
for line in datafile: do_something(line)
PS:原文中做者举了一些工做中的例子,这里再也不赘述,想了解的能够到原文连接中查看。
在Python中对列表排序很是简单,好比:
In [1]: a_list = ['Tommy', 'Jack', 'Smith', 'Paul'] In [2]: a_list.sort() In [3]: a_list Out[3]: ['Jack', 'Paul', 'Smith', 'Tommy']
须要注意的是:list的sort()方法会直接在原list变量上排序,改变本来的list对象,而且该方法不会返回一个list对象。若是你须要不改变原list,而且返回新的list对象的话,可使用Python的orted方法:
In [1]: a_list = ['Tommy', 'Jack', 'Smith', 'Paul'] In [2]: b_list = sorted(a_list) In [3]: b_list Out[3]: ['Jack', 'Paul', 'Smith', 'Tommy'] In [4]: a_list Out[4]: ['Tom', 'Jack', 'Smith', 'Paul']
可是,若是你想对一个list进行排序,不过不想使用默认的排序方式,好比你可能须要先根据第二行排序,再根据第四行排序。这时候,咱们也可使用sort()方法,可是得提供一个自定义的排序方法:
In [1]: def custom_cmp(item1, item2):
...: return cmp((item1[1], item1[3]), (item2[1], item2[3]))
...:
In [2]: a_list = ['Tommy', 'Jack', 'Smith', 'Paul']
In [3]: a_list.sort(custom_cmp)
In [4]: a_list
Out[4]: ['Jack', 'Paul', 'Smith', 'Tommy']
这种方法能够实现,可是在list比较大的状况下效率很低。下面介绍两种其余的方法。
一、DSU排序方法
DSU即Decorate-Sort-Undecorate,中文就是"封装-排序-解封"。DSU方法不会建立自定义的排序方法,而是建立一个辅助的排序列表,而后对这个列表进行默认排序。须要说明的是:DSU方法是一种比较老的方法,如今已经基本上不使用了,不过这里仍是给出一个简单的例子说明一下:
# Decorate: to_sort = [(item[1], item[3], item) for item in a_list] # Sort: to_sort.sort() # Undecorate: a_list = [item[-1] for item in to_sort]
上述代码第一行建立了一个tuple的list,tuple中的前两项是用来排序的字段,最后一项是原数据;第二行使用sort()方法对辅助的list进行默认的排序;最后一行是从已经排序的辅助list中获取原数据,从新组成list。
这种方法是使用复杂度和内存空间来减小计算时间,比较简单,也比较快,可是咱们得复制原列表的数据。
二、KEY方法
自从Python 2.4以后,list.sort()和sorted()都添加了一个key参数用来指定一个函数,这个函数做用于每一个list元素,在作cmp以前调用。key参数是一个函数,这个函数有一个参数,返回一个用来排序的关键字。这种排序方法很快,由于key方法在每一个输入的record上只执行一次。你可使用Python内置的函数(len, str.lower)或者自定义函数做为key参数,下面是一个简单的例子:
In [1]: a_list = ['Tommy', 'Jack', 'Smith', 'Paul'] In [2]: def my_key(item): ...: return (item[1], item[3]) ...: In [3]: a_list.sort(key=my_key) In [4]: a_list Out[4]: ['Jack', 'Paul', 'Smith', 'Tommy']
检查数据可让程序更健壮,用术语来讲就是防护性编程。检查数据的时候,有EAFP和LBYL两种不一样的编程风格,具体的意思以下:
LBYL: Look Before You Leap,即事先检查;
EAFP: It's Easier to Ask Forgiveness than Permission,即不检查,出了问题由异常处理来处理。
异常处理老是比事先检查容易,由于你很难提早想到全部可能的问题。因此,通常状况下编码时会倾向使用EAFP风格,但它也不是适应全部的状况。两个风格的优缺点以下:
d = {} words = ['a', 'd', 'a', 'c', 'b', 'z', 'd'] # LBYL for w in words: if w not in d: d[w] = 0 d[w] += 1 # EAFP for w in words: try: d[w] += 1 except KeyError: d[w] = 1
对于LBYL,容易打乱思惟,原本业务逻辑用一行代码就能够搞定的。却多出来了不少行用于检查的代码。防护性的代码跟业务逻辑混在一块下降了可读性。而EAFP,业务逻辑代码跟防护代码隔离的比较清晰,更容易让开发者专一于业务逻辑。不过,异常处理会影响一点性能。由于在发生异常的时候,须要进行保留现场、回溯traceback等操做。但其实性能相差不大,尤为是异常发生的频率比较低的时候。
另外,须要注意的是,若是涉及到原子操做,强烈推荐用EAFP风格。好比我某段程序逻辑是根据redis的key是否存在进行操做。若是先if exists(key),而后do something。这样就变成2步操做,在多线程并发的时候,可能key的状态已经被其余线程改变了。而用EAFP风格则能够确保原子性。
PS:在使用EAFP风格捕获异常时,尽可能指明具体的异常,不要直接捕获Exception。不然会捕获到其余未知的异常,若是有问题,你会很难去定位(debug)。
Python中的引用:
from module import *
你可能在其余地方见过这种使用通配符*的引用方式,可能你也比较喜欢这种方式。可是,这里要说的是:请不要使用这种引用方式!
通配符引用的方式属于Python中的阴暗面,这种方式会致使命名空间污染的问题。你可能会在本地命名空间中获得意想不到的东西,并且这种方式引入的变量可能将你在本地定义的变量覆盖,在这种状况下,你很难弄清楚变量来自哪里。因此,通配符引用的方式虽然方便,但可能会致使各类各样奇怪的问题,在Python项目中尽可能不要用这种方式。
在Python中,你们比较认同的import方式有如下几个规则:
一、经过模块引用变量(Reference names through their module)
这种方式直接import的是模块,而后经过模块访问其中的变量,Class和方法。使用这种方式能够很清晰的知道变量来自哪里:
import module module.name
二、模块名比较长时使用短名字(alias)
import long_module_name as mod mod.name
三、直接引用你须要的变量名
from module import name name
为了使一个Python文件既能够被引用,又能够直接执行,你能够在Python文件中加上这样的代码:
if __name__ == '__main__': # script code here
当被引用时,一个模块(module)的__name__属性会被设置为该文件的文件名(不包括.py后缀)。因此,上面代码片断中if语句中的脚本在被引用的时候不会执行。当把Python文件做为一个脚本执行的时候,__name__属性则被设置为"__main__",这时候if语句中的脚本才会被执行。
最好不要在Python文件中直接写可执行的语句,应该将这些代码放在方法或类里面,必要的时候放在"if __name__ == '__main__':"中。一个Python文件的结构能够参考下面的:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" 文档 module docstring """
# 引用 imports
# 常量 constants
# 异常 exception classes
# 方法 interface functions
# 类 classes
# 内部方法和类 internal functions & classes
def main(...):
...
if __name__ == '__main__':
status = main()
sys.exit(status)
Python是一种脚本语言,有时候咱们会直接在命令行运行Python文件,这时候可能须要解析命令行传入的参数,下面是一个例子:cmdline.py
#!/usr/bin/env python
"""
Module docstring.
"""
import sys
import optparse
def process_command_line(argv): """ Return a 2-tuple: (settings object, args list). `argv` is a list of arguments, or `None` for ``sys.argv[1:]``. """ if argv is None: argv = sys.argv[1:] # initialize the parser object: parser = optparse.OptionParser( formatter=optparse.TitledHelpFormatter(width=78), add_help_option=None) # define options here: parser.add_option( # customized description; put --help last '-h', '--help', action='help', help='Show this help message and exit.') settings, args = parser.parse_args(argv) # check number of arguments, verify values, etc.: if args: parser.error('program takes no command-line arguments; ' '"%s" ignored.' % (args,)) # further process settings & args if necessary return settings, args def main(argv=None): settings, args = process_command_line(argv) # application code here, like: # run(settings, args) return 0 # success if __name__ == '__main__': status = main() sys.exit(status)
Python中包的设计与引用规则,包的设计例子:
package/ __init__.py module1.py subpackage/ __init__.py module2.py
建议使用上面的方式来组织你的项目,尽可能减少引用路径,明确引用对象,避免引用冲突。引用示例:
import package.module1 from package.subpackage import module2 from package.subpackage.module2 import name
咱们能够经过__future__模块使用Python 3.0的功能:absolute_import。方法以下:
from __future__ import absolute_import
简单介绍一下相对引入和绝对引入的概念:
相对导入:在不指明 package 名的状况下导入本身这个 package 的模块,好比一个 package 下有 a.py 和 b.py 两个文件,在 a.py 里 from . import b 便是相对导入 b.py。
绝对导入:指明顶层 package 名。好比 import a,Python 会在 sys.path 里寻找全部名为 a 的顶层模块。
引入absolute_import以后不是支持了绝对引入,而是拒绝相对引入。
简单比复杂好
调试程序的难度是写代码的两倍。所以,只要你的代码写的尽量的清楚,那么你在调试代码时就不须要那么地有技巧。(Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. ) -- Brian Kernighan。因此,尽可能保持你的程序足够简单。
不要重复造轮子
在你写代码以前,你须要先看一下有没有其余人已经实现了相似的功能。你能够从下面的几个地方寻找:
一、Python标准库
二、Python第三方LIB, PYPI(Python Package Index),地址:PYPI
三、搜索引擎,Google,百度等。。
参考
Code Like a Pythonista: Idiomatic Python
writing idiomatic python 3
LBYL与EAFP两种防护性编程风格
Over!