首页 文章资讯内容详情

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

2026-06-01 4 花语

本文内容纲要:

-一、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 arent 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 youre Dutch. # 虽然这并不容易,因为你不是 Python 之父(这里的Dutch是指Guido) Now is better than never. Although never is often better than *right* now. # 做也许好过不做,但不假思索就动手还不如不做(动手之前要细思量) If the implementation is hard to explain, its a bad idea. If the implementation is easy to explain, it may be a good idea. # 如果你无法向人描述你的方案,那肯定不是一个好方案;反之亦然(方案测评标准) Namespaces are one honking great idea -- lets 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:ItsEasiertoAskForgivenessthanPermission,即不检查,出了问题由异常处理来处理。

异常处理总是比事先检查容易,因为你很难提前想到所有可能的问题。所以,一般情况下编码时会倾向使用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