如何像Python高手(Pythonista)一样编程
本文内容纲要:
-一、Python之禅(TheZenofPython)
-二、PEP8:Python编码规范(PEP8:StyleGuideforPythonCode)
-三、交换变量值(SwapValues)
-四、Python控制台的"_"(Interactive"_")
-五、合并字符串(BuildingStringsfromSubstrings)
-六、使用关键字in(Useinwherepossible)
-七、字典(Dictionary)
-八、defaultdict
-九、字典的组装和拆分(Building&SplittingDictionaries)
-十、Python的True值(TruthValues)
-十一、enumerate:索引和元素(Index&Item:enumerate)
-十二、Python中的变量&引用(variables&names)
-十三、Python方法中参数的默认值(DefaultParameterValues)
-十四、字符串格式化(StringFormatting)
-十五、迭代器(Listcomprehensions)
-十六、生成器(Generator&Generatorexpressions)
-十七、排序(Sorting)
-十八、EAFPvsLBYL
-十九、引用(Importing)
-二十、模块与脚本(Modules&Scripts)
-二十一、命令行解析(Commend-LineProcessing)
-二十二、包(Packages)
最近在网上看到一篇介绍Pythonic编程的文章:CodeLikeaPythonista:IdiomaticPython,其实作者在2006的PyCon会议后就写了这篇文章,写这篇文章的主要原因是作者发现很多有经验的Pythoner写出的代码不够Pythonic。我觉得这篇文章很不错,所以将它用中文写了下来(不是逐字的翻译,中间加了一些自己的理解),分享给大家。另:由于本人平时时间有限,这篇文章翻译了比较长的时间,如果你发现了什么不对的地方,欢迎指出。。
一、Python之禅(TheZenofPython)
TheZenofPython是Python语言的指导原则,遵循这些基本原则,你就可以像个Pythonista一样编程。具体内容你可以在Python命令行输入importthis看到:
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之禅已经正式成文PEP20,具体内容见:PEP20
二、PEP8:Python编码规范(PEP8:StyleGuideforPythonCode)
Abelson&Sussman在《计算机程序的构造和解释》一书中说道:程序是写来给人读的,只是顺带让机器执行。所以,我们在编码时应该尽量让它更易读懂。PEP8是Python的编码规范,官方文档见:PEP8,PEP是PythonEnhancementProposal的缩写。PEP8包括很多编码的规范,下面主要介绍一下缩进和命名等内容。
空格和缩进(WhiteSpaceandIndentation)
空格和缩进在Python语言中非常重要,它替代了其他语言中{}的作用,用来区分代码块和作用域。在这方面PEP8有以下的建议:
1、每次缩进使用4个空格
2、不要使用Tab,更不要Tab和空格混用
3、两个方法之间使用一个空行,两个Class之间使用两个空行
4、添加一个空格在字典、列表、序列、参数列表中的“,“后,以及在字典中的”:“之后,而不是之前
5、在赋值和比较两边放置一个空格(参数列表中除外)
6、紧随括号后面或者参数列表前一个字符不要存在空格
Python命名
命名规范是编程语言的基础,而且大部分的规范对于高级语言来说都是一样的,Python的基本规范如下:
1、方法 & 属性:joined_lower
2、常量:joined_lower or ALL_CAPS
3、类:StudlyCaps
4、类属性:interface, _internal, __private
5、camelCase only to conform to pre-existing conventions
以上内容只是对PEP8做了非常简单的介绍,由于今天的主题不在于此,所以就不在这里多讲。想要更加深入的了解Python编码规范,可以阅读PEP8官方文档和GooglePython编码规范等内容。
三、交换变量值(SwapValues)
在其他语言中,交换两个变量值的时候,可以这样写:
temp = a
a = b
b = temp
在Python中,我们可以简单的这样写:
b, a = a, b
可能你已经在其他地方见过这种写法,但是你知道Python是如何实现这种语法的吗?首先,逗号(,)是Python中tuple数据结构的语法;上面的语法会执行一下的操作:
1、Python会先将右边的a,b生成一个tuple(元组),存放在内存中;
2、之后会执行赋值操作,这时候会将tuple拆开;
3、然后将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控制台的"_"(Interactive"_")
这是Python中比较有用的一个功能,不过有很多人不知道(我也是接触Python很久之后才知道的)。。在Python的交互式控制台中,当你计算一个表达式或者调用一个方法的时候,运算的结果都会放在一个临时的变量_里面。_(下划线)用来存储上一次的打印结果,比如:
>>> import math
>>> math.pi / 3
1.0471975511965976
>>> angle = _
>>> math.cos(angle)
0.50000000000000011
>>> _
0.50000000000000011
PS:当返回结果为None的时候,控制台不会打印,_里面存储的值也就不会改变。
五、合并字符串(BuildingStringsfromSubstrings)
假如现在有一个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时将抛出异常。
六、使用关键字in(Useinwherepossible)
当你需要判断一个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的时候执行效率会比较快,代码也更简洁。
七、字典(Dictionary)
dict是Python内置的数据结构,在写Python程序时会经常用到。这里介绍一下它的get方法和defaultdict方法。
1、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
2、fromkeys
dict本身有个fromkeys方法,可以通过一个list生成一个dict,不过得提供默认的value,例如:
# ⽤序列做 key,并提供默认value
>>> dict.fromkeys(['a', 'b', 'c'], 1)
# {'a': 1, 'c': 1, 'b': 1}
3、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,orset&get",或者相当于"setifnecessary,thenget"
八、defaultdict
defaultdict是Python2.5之后引入的功能,具体的用法我已经在另外一篇文章中详细介绍:Python的defaultdict模块和namedtuple模块
九、字典的组装和拆分(Building&SplittingDictionaries)
在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值(TruthValues)
在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)
在Python3.x中,__nonzero__方法被__bool__方法替代。考虑到兼容性,你可以在class定义中加上以下的代码:
__bool__ = __nonzero__
十一、enumerate:索引和元素(Index&Item:enumerate)
在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
十二、Python中的变量&引用(variables&names)
在很多其他高级语言中,给一个变量赋值时会将"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方法中参数的默认值(DefaultParameterValues)
对于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(dictorset)对象,正确的做法是在函数中创建:
def good_append(new_item, a_list=None):
if a_list is None:
a_list = []
a_list.append(new_item)
return a_list
十四、字符串格式化(StringFormatting)
在许多编程语言中都包含有格式化字符串的功能,比如C语言中的格式化输入输出。Python中内置有对字符串进行格式化的操作符"%"以及str.format()方法。
1、操作符"%"
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,youhave%imessages"是字符串模板。%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"。
2、str.format()方法
str.format()方法是在Python2.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字符串格式化的介绍,可以参看:PEP3101--AdvancedStringFormatting
十五、迭代器(Listcomprehensions)
ListComprehensions即迭代器(列表生成式),是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循环。根据ZenofPython,选择更容易读的方式。下面是一些例子:
>>> [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]
十六、生成器(Generator&Generatorexpressions)
先出一个题:计算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:原文中作者举了一些工作中的例子,这里不再赘述,想了解的可以到原文链接中查看。
十七、排序(Sorting)
在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比较大的情况下效率很低。下面介绍两种其他的方法。
1、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。
这种方法是使用复杂度和内存空间来减少计算时间,比较简单,也比较快,但是我们得复制原列表的数据。
2、KEY方法
自从Python2.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']
十八、EAFPvsLBYL
检查数据可以让程序更健壮,用术语来说就是防御性编程。检查数据的时候,有EAFP和LBYL两种不同的编程风格,具体的意思如下:
LBYL:LookBeforeYouLeap,即事先检查;
EAFP:It'sEasiertoAskForgivenessthanPermission,即不检查,出了问题由异常处理来处理。
异常处理总是比事先检查容易,因为你很难提前想到所有可能的问题。所以,一般情况下编码时会倾向使用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是否存在进行操作。如果先ifexists(key),然后dosomething。这样就变成2步操作,在多线程并发的时候,可能key的状态已经被其他线程改变了。而用EAFP风格则可以确保原子性。
PS:在使用EAFP风格捕获异常时,尽量指明具体的异常,不要直接捕获Exception。否则会捕获到其他未知的异常,如果有问题,你会很难去定位(debug)。
十九、引用(Importing)
Python中的引用:
from module import *
你可能在其他地方见过这种使用通配符*的引用方式,可能你也比较喜欢这种方式。但是,这里要说的是:请不要使用这种引用方式!
通配符引用的方式属于Python中的阴暗面,这种方式会导致命名空间污染的问题。你可能会在本地命名空间中得到意想不到的东西,而且这种方式引入的变量可能将你在本地定义的变量覆盖,在这种情况下,你很难弄清楚变量来自哪里。所以,通配符引用的方式虽然方便,但可能会导致各种各样奇怪的问题,在Python项目中尽量不要用这种方式。
在Python中,大家比较认同的import方式有以下几个规则:
1、通过模块引用变量(Referencenamesthroughtheirmodule)
这种方式直接import的是模块,然后通过模块访问其中的变量,Class和方法。使用这种方式可以很清晰的知道变量来自哪里:
import module
module.name
2、模块名比较长时使用短名字(alias)
import long_module_name as mod
mod.name
**3、直接引用你需要的变量名**
from module import name
name
二十、模块与脚本(Modules&Scripts)
为了使一个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)
二十一、命令行解析(Commend-LineProcessing)
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)
二十二、包(Packages)
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__模块使用Python3.0的功能:absolute_import。方法如下:
from __future__ import absolute_import
简单介绍一下相对引入和绝对引入的概念:
相对导入:在不指明package名的情况下导入自己这个package的模块,比如一个package下有a.py和b.py两个文件,在a.py里from.importb即是相对导入b.py。
绝对导入:指明顶层package名。比如importa,Python会在sys.path里寻找所有名为a的顶层模块。
引入absolute_import之后不是支持了绝对引入,而是拒绝相对引入。
简单比复杂好
调试程序的难度是写代码的两倍。因此,只要你的代码写的尽可能的清楚,那么你在调试代码时就不需要那么地有技巧。(Debuggingistwiceashardaswritingthecodeinthefirstplace.Therefore,ifyouwritethecodeascleverlyaspossible,youare,bydefinition,notsmartenoughtodebugit.)--BrianKernighan。所以,尽量保持你的程序足够简单。
不要重复造轮子
在你写代码之前,你需要先看一下有没有其他人已经实现了类似的功能。你可以从下面的几个地方寻找:
1、Python标准库
2、Python第三方LIB,PYPI(PythonPackageIndex),地址:PYPI
3、搜索引擎,Google,百度等。。
**参考**
CodeLikeaPythonista:IdiomaticPython
writingidiomaticpython3
LBYL与EAFP两种防御性编程风格
Python高级编程
Python补充05字符串格式化(%操作符)
Howtosorting
探索Python的变量、类型和引用
Over!
本文内容总结:一、Python之禅(TheZenofPython),二、PEP8:Python编码规范(PEP8:StyleGuideforPythonCode),三、交换变量值(SwapValues),四、Python控制台的""(Interactive""),五、合并字符串(BuildingStringsfromSubstrings),六、使用关键字in(Useinwherepossible),七、字典(Dictionary),八、defaultdict,九、字典的组装和拆分(Building&SplittingDictionaries),十、Python的True值(TruthValues),十一、enumerate:索引和元素(Index&Item:enumerate),十二、Python中的变量&引用(variables&names),十三、Python方法中参数的默认值(DefaultParameterValues),十四、字符串格式化(StringFormatting),十五、迭代器(Listcomprehensions),十六、生成器(Generator&Generatorexpressions),十七、排序(Sorting),十八、EAFPvsLBYL,十九、引用(Importing),二十、模块与脚本(Modules&Scripts),二十一、命令行解析(Commend-LineProcessing),二十二、包(Packages),
原文链接:https://www.cnblogs.com/rrxc/p/4660426.html