第1章 欢迎来到 Python 世界
- Python 提倡简洁的代码设计、高级的数据结构和模块化的组件。
Python 的可插入化和模块化架构能使你的项目生机盎然和易于管理。
第2章 快速入门
有些语言通过函数输出数据到屏幕,Python 和大多数解释执行的脚本语言使用语句输出。
print
语句调用str()
函数显示对象,而交互式解释器则调用repr()
函数显示对象。_
在解释器中表示最后一个表达式的值。建议函数应该保持其清晰性,也就是它只应该接受参数,返回结果。
将函数分为两大类,一类只做事,不需要返回值,另一类则执行一些运算,最后返回结果。
除非输出就是目的。//
是地板除法,/
是浮点除法。括号在 Python 语言中不是必须存在的,不过为了可读性,使用括号总是值得的。
Python 不支持自增和自减操作符。
print ***,
抑制自动生成的换行符。
第3章 Python 基础
反斜线换行和括号元素换行,推荐使用括号,可读性更好。
赋值并不是直接将一个值赋给一个变量。在 Python 语言中,对象是通过引用传递的。
赋值语句不会返回值,但是链式赋值没问题。多元赋值时,建议总是加上圆括号以提供更高的可读性。
多元赋值可以实现无需中间变量交换两个变量的值:(x, y) = (y, x)
。Python 不支持重载操作符。
_xxx
,不用from module import *
导入。
_xxx_
,系统定义名字。
_xxx
,类中的私有变量名。在模块、类声明或函数声明中第一个没有赋值的字符串,可以用属性
obj.__doc__
访问,其中obj
是模块、类或函数的名字。查看 Python 之禅,在 Python 解释器输入
import this
然后回车。合理的布局:
1、起始行(类 Unix 环境下使用,以直接执行文件)
2、模块文档
3、模块导入
4、全局变量定义
5、类定义
6、函数定义
7、主程序大部分的 Python 模块都是用于导入调用的,直接运行模块应该调用该模块的回归测试代码。
所有的模块都有能力执行代码。
通常只有主程序模块中有大量的顶级可执行代码,所有其它被导入的模块只应该有很少的顶级执行代码,所有的功能代码都应该封装在函数或类当中。__name__
的值指示模块应如何被加载:
如果模块是被导入,该值为模块名字;
如果模块是被直接执行,该值为__main__
。
用于直接执行的测试代码应该随着测试条件及测试结果的变更及时修改,每次代码更新都应该运行测试代码,以确认没有引发新问题。追踪或调试程序会给对象增加一个额外引用。
解释器负责跟踪对象的引用,垃圾收集器负责释放内存。将经常用到的模块属性替换为一个本地引用,代码速度更快,也不用老是敲那么长的变量名了。
第4章 Python 对象
所有的 Python 对象都拥有三个特性:身份(可用内建函数
id()
得到),类型(可用内建函数type()
查看,类型也是对象,所以返回的是对象),值。所有类型对象的类型都是
type
,它也是所有 Python 类型的根和所有 Python 标准类的默认元类。类就是类型,实例是对应类型的对象。
比较操作是针对对象的值进行的。
Python 也支持对象本身的比较,即a is b
和a is not b
,二者等价于id(a) == id(b)
和id(a) != id(b)
。对象就像一个装着内容的盒子。当一个对象被赋值到一个变量,就像在这个盒子上贴了一个标签,表示创建了一个引用。当这个对象有了一个新的引用,就会在盒子上新贴一张标签。当一个引用被销毁时,这个标签就会被撕掉。当所有的标签都被撕掉时,这个盒子就会被回收。每个对象都天生具有一个计数器,记录它自己的引用次数。
整型对象和字符串对象是不可变对象,所以 Python 会很高效的缓存它们。
repr()
返回的是一个对象的“官方”字符串表示,绝大多数情况下可以通过eval()
重新得到该对象。
str()
致力于生成一个对象的可读性好的字符串表示,很适合于print
语句输出。
也就是说repr()
输出对 Python 比较友好,而str()
输出对用户比较友好。每次调用函数都会付出性能代价,如果能减少调用次数,就会提高程序的性能。
运行效率:
if type(num) == type(0)
<import types; if type(num) == types.IntType
<if type(num) is types.IntType
<from types import IntType; if type(num) is IntType
。工厂函数,看上去像函数,实质上是类。当调用它们时,实际上是生成了该类型的一个实例,就像工厂生产货物一样。
Python 的标准数据类型可以按照存储模式、更新模式、访问模式分类。
第5章 数字
Python 标准整型类型等价于 C 的(有符号)长整型。
Python 的长整型类型能表达的数值仅仅与机器支持的(虚拟)内存大小有关。
整型和长整型正在缓慢的统一,只有对长整型调用repr()
才有机会看到L
,调用str()
就看不到L
。int()
直接截去小数部分(返回值为整型),floor()
得到最接近原数但小于原数的整数(返回值为浮点型),round()
得到最接近原数的整数(返回值为浮点型)。coerce(num1, num2)
将num1
和num2
转换为统一类型,以一个元组的形式返回。
divmod(num1, num2)
返回一个元组(num1 / num2, num1 % num2)
。
pow(num1, num2, mod=1)
如果提供mod
参数,则取num1
的num2
次方后再对mod
取余。
第6章 序列
- 循环显示一个字符串,每次砍掉一个末位字符:
s = 'abcde'
for i in [None] + range(-1, -len(s), -1):
print s[:i]
一般来说,从性能的角度来考虑,把重复操作作为参数放到循环里面进行是非常低效的。
extend()
比连接操作符+
的一个优点是它实际上是把新列表添加到了原有的列表里面,而不是像连接操作那样新建一个列表。可以用
dir()
得到一个对象的所有方法和属性。可变对象的方法会原地执行操作,对象的值会被改变,但是没有返回值。
不可变对象的方法必须返回一个新的对象。只有一个元素的元组赋值需要在元素后加一个逗号,以防止跟普通的分组操作符混淆。
虽然元组对象本身是不可变的,但这并不意味着元组包含的可变对象也不可变了。
所有的多对象的、逗号分隔的、没有明确用符号封装的集合,默认的类型都是元组。
所有函数返回的多对象(不包括有符号封装的)都是元组类型。序列类型对象的浅拷贝是默认类型拷贝,包括几种实施方式:(1)完全切片操作
[:]
;(2)利用工厂函数;(3)使用copy
模块的copy()
函数。
深拷贝需要copy.deepcopy()
函数。非容器类型没有深拷贝一说,浅拷贝是用完全切片操作来完成的。
如果容器只包含原子类型对象,对它的深拷贝将不会进行。
第7章 映射和集合类型
字典是 Python 语言中唯一的映射类型。
序列类型只用数字类型的键。
映射类型可以用其它对象类型做键,一般最常见的是用字符串做键。映射类型通常被称作哈希表。
哈希表的算法是获取键,对键执行一个叫做哈希函数的操作,并根据计算的结果,选择在数据结构的某个地址中来存储对应的值。可以用内建方法
fromkeys()
创建一个“默认”字典,字典中的元素具有相同的值(如果没有给出,默认为None
)。hash()
函数会返回对象的哈希值。只有可哈希的对象,才可以作为字典的键。
值相等的数字表示相同的键,如整型数字1和浮点型数字1.0。
元组中只包括不可变元素,才可以作为字典中的键。集合操作符的结果类型与左操作数的类型相同。
加号不是集合类型的操作符。集合的很多内建方法几乎和操作符等价。“几乎”的意思是它们间有一个重要的区别:当用操作符时,两边的操作数必须是集合;使用内建方法时,对象也可以是迭代类型的。
第8章 条件和循环
- 使用映射对象的一个最大好处就是它的搜索操作比类似
if-elif-else
语句或是for
循环这样的序列查询要快得多:
if user.cmd == 'create':
action = 'create item'
elif user.cmd == 'delete':
action = 'delete item'
elif user.cmd == 'update':
action = 'update item'
else:
action = 'invalid choice... try again!'
if user.cmd in ('create', 'delete', 'update'):
action = '%s item' % user.cmd
else:
action = 'invalid choice... try again!'
msgs = {'create': 'create item',
'delete': 'delete item',
'update': 'update item'}
default = 'invalid choice... try again!'
action = msgs.get(user.cmd, default)
Python 的
for
更像是 shell 或是脚本语言中的foreach
循环。通过
print
语句调试for
循环中的序列时,如果在应该看到字符串的地方发现的却是单个字符,那么很有可能接受的是一个字符串,而不是对象的序列。直接迭代序列要比通过索引迭代快。
xrange()
类似range()
,不过当你有一个很大范围的列表时,前者可能更适合,因为它不会在内存里创建列表的完整拷贝,它的性能远高出后者。它只被用在for
循环中。pass
不做任何事情,也可标记以后要完成的代码,先把结构定下来。Python 可以在
while
和for
循环中使用else
语句,只在循环完成后执行,也就是说break
语句也会跳过else
块。根本上说,迭代器就是有一个
next()
方法的对象,而不是通过索引来计数。对一个对象调用
iter()
就可以得到它的迭代器。
iter(obj)
会检查obj
是不是一个序列,是则根据索引从0一直迭代到序列结束。
iter(func, sentinel)
会重复的调用func
,直到迭代器的下个值等于sentinel
。列表解析
[expr for iter_var in iterable if cond_expr]
的核心是for
循环,expr
应用于序列的每个成员,最后的结果值是应用后产生的列表。列表解析支持多重嵌套
for
循环以及多个if
子句,如
f = open('xxx.txt', 'r')
len([word for line in f for word in line.split()])
f.seek(0)
sum([len(word) for line in f for word in line.split()])
- 生成器表达式
(expr for iter_var in iterable if cond_expr)
并不真正创建列表,而是返回一个生成器,在使用内存上更有效。如上例中的文本文件很大,则最后一句可改为
sum(len(word) for line in f for word in line.split())
再如找到一个文件的最长行的长度
max(len(x.strip()) for x in open('/path/xxx'))
第9章 文件和输入输出
文件打开模式可以包含
b
,即以二进制模式访问,但它不能作为第一个字符出现。
如果要处理二进制文件,并希望可移植到非 Unix 的环境中,加上b
是不错的主意。open()
和file()
具有相同的功能,可以任意替换。
建议使用open()
来读写文件,在想说明在处理文件对象时使用file()
,例如if instance(f, file)
。当使用
U
模式打开文件的时候,所有的行分隔符(或行结束符)都会被替换为换行符NEWLINE(\n)
。文件对象的newlines
属性会记录它曾“看到的”文件的行结束符。sys.argv
返回命令行参数的列表,len(sys.argv)
是命令行参数的个数,sys.argv[0]
是程序的名称。marshal
和pickle
模块的区别在于,前者只能处理简单的 Python 对象(数字、序列、映射以及代码对象),而后者还可以处理递归对象、被不同地方多次引用的对象,以及用户定义的类和实例。Python 提供了 DBM 的多种实现,如果不确定,最好使用
anydbm
模块。它会自动检测系统上已安装的 DBM 兼容模块,并选择“最好”的一个。shelve
模块使用anydbm
模块寻找合适的 DBM 模块,然后使用cPickle
来完成储存转换过程。cPickle
是pickle
的一个更快的 C 语言编译版本。向
os.path.expanduser()
函数传递一个带波浪号~
的目录,它会返回对应的绝对路径。
第10章 错误和异常
SyntaxError
异常是唯一不在运行时发生的异常,一般都是在编译时发生,Python 解释器无法把脚本转化为 Python 字节代码。如果一个异常没有找到合适的处理器,就向上移交给调用者处理;如果也没找到,继续向上移交;如果到达最顶层仍然没有找到,就认为这个异常是未处理的,Python 解释器会显示“跟踪记录
Traceback
”,然后退出。except
语句在处理多个异常时,要求异常被放在一个元组里。Python 提供的
try-except
语句是为了更好的跟踪错误,并在代码里准备好处理异常的逻辑。它的目的是减少程序出错的次数,并在出错后仍能保证程序正常执行。
它的作用是提供一个可以提示错误或处理错误的机制,而不是一个错误过滤器。
可以捕获特定的异常并忽略它们,或是捕获所有异常并采取特定的动作。不要捕获所有异常,然后忽略掉它们。在
try
范围中没有异常被检测到时,执行else
子句。finally
子句是无论异常是否发生、是否捕捉,都会执行的一段代码。try-except-else-finally
语法示例:
try:
A
except MyException:
B
else:
C
finally:
D
可以有不止一个except
子句,正常时执行A-C-D
,异常时执行A-B-D
。
try-finally
语句,当在try
范围中产生一个异常时,会立即跳转到finally
语句,当finally
中的所有代码都执行完毕后,会继续向上一层引发异常。
如果finally
中的代码引发了另一个异常,或由于return
、break
、continue
而终止,原来的异常将丢失而且无法重新引发。Python 提供了一种机制明确的触发异常,就是
raise
语句。断言语句
assert
如果断言成功,不采取任何措施,否则触发AssertionError
。运行环境必须足够强健,来处理应用级别的错误,并提供用户级别的错误信息。
第11章 函数和函数式编程
过程是简单、特殊、没有返回值的函数。Pthon 的过程就是函数,因为解释器会隐式的返回默认值
None
。func
是函数对象的引用,func()
是函数对象的调用。定义函数时,所有必需的参数都要在默认参数之前。
非关键字可变长参数(元组)前加*
,关键字可变长参数(字典)前加**
且其应为最后的参数。如果将全局变量的名字声明在一个函数体内的时候,全局变量的名字能被局部变量给覆盖掉。
为了明确的引用一个已命名的全局变量,必须使用global
语句。如果在一个内部函数里,对在外部作用域(但不是全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。
第12章 模块
模块是用来组织代码的,包是用来组织模块的。
模块的搜索路径被保存在
sys
模块的sys.path
变量里。
sys.modules
给出当前导入了哪些模块和它们的地址。名称空间是纯粹意义上的名字和对象间的映射关系,而作用域还指出了从用户代码的哪些物理位置可以访问到这些名字。
推荐使用此顺序导入模块:Python 标准库模块、Python 第三方模块、应用程序自定义模块,并使用一个空行分割这三类模块的导入语句。
如果一个模块在顶层导入,它的作用域是全局的;如果在函数中导入,作用域是局部的。
加载模块会导致被导入模块的顶层代码直接执行。
只把函数和模块定义放入模块的顶层是良好的模块编程习惯。只在两种场合下建议使用
from module import *
,一个是目标模块中的属性非常多,反复键入模块名很不方便,如Tkinter
和Numpy
,可能还有socket
;另一个是在交互解释器下。只从模块导入名字的另一个副作用是那些名字会成为局部名称空间的一部分,而且这些变量的改变只影响它的局部拷贝,而不是所导入模块的原始名称空间。
globals()
和locals()
内建函数分别返回调用者全局和局部名称空间的字典。reload()
内建函数重新导入一个已经导入的模块。该模块必须是全部导入,且必须被成功导入。
第13章 面向对象编程
未绑定的方法调用需要传递一个适当的实例(
self
)给方法。类名通常由大写字母打头,可以帮助区别于函数调用。
数据值应该使用名词作为名字,方法使用谓词(动词加对象)。考虑用面向对象设计(OOD)来工作的一个最重要的原因,在于它直接提供建模和解决现实世界问题与情形的途径。
Python 并不支持纯虚函数(像 C++)或者抽象方法(如 Java),这些都强制程序员在子类中定义方法。作为替代方法,可以简单的在基类方法中引发
NotImplementedError
。类属性包括数据属性(如复数的实部和虚部)和函数属性(方法)。
访问一个属性时,它同时也是一个对象,拥有自己的属性,可以访问,这导致了一个属性链。
类数据属性仅当需要有更加“静态”数据类型时才变得有用。
通常,所有方法都有一个限制:在调用前,需要创建一个实例。查看类的属性有两种方法:
dir()
内建函数返回对象属性的名字列表;__dict__
属性返回一个字典,键是属性名,键值是相应的属性对象的数据值。任何类都有的特殊属性:
__name__
,__doc__
,__bases__
,__dict__
,__module__
,__class__
。实例仅拥有数据属性(方法严格来说是类属性)。
实例仅有两个特殊属性:__class__
,__dict__
。静态类属性只有类引用才能更新,在实例中设定或更新静态类属性会创建一个实例属性,遮蔽静态类属性。
调用一个还没有任何实例的类中的方法的一个主要场景是:在派生一个子类时,而且要覆盖父类的方法。
静态方法是类中的函数(不需要实例),由
staticmethod()
内建函数显式声明,或使用函数修饰符@staticmethod
。
类方法需要类而不是实例作为第一参数,由classmethod()
内建函数显式声明,或使用函数修饰符@classmethod
。组合就是通过利用已有类创建实例的方式,来给出新类中的变量。
调用被覆盖的父类方法可以使用
super()
内建函数。__mro__
属性用于查看继承类调用的方法的查找顺序。
经典类使用深度优先算法查找,新式类使用广度优先算法查找。由双下划线开始的属性在运行时被“混淆”,直接访问是不允许的。实际上,解释器会在名字前面加上下划线和类名,可以防止在祖先类或子孙类中的同名冲突。
授权是包装的一个特性。
实现授权的关键点就是覆盖__getattr__()
方法,在代码中包含一个对getattr()
内建函数的调用。isinstance()
没有执行严格匹配,子类的实例也会返回True
。如果想严格匹配,仍然需要使用is
。__slots__
是一个类变量,用以限定类属性,主要目的是节约内存,副作用是防止用户随意增加实例属性。__getattribute__()
方法类似__getattr__()
,不同之处在于,当属性被访问时,它就一直都可以被调用,而不局限于不能找到的情况。
如果类同时定义了两者,一般前者会遮蔽后者。__getattribute__()
的优先级别:类属性 > 数据描述符 > 实例属性 > 非数据描述符 > 默认为__getattr__()
。
__get__()
、__set__()
、__delete__()
这三个特殊方法充当描述符协议的作用。那些同时覆盖前两者的类被称作数据描述符。property()
内建函数有四个参数:property(fget=None, fset=None, fdel=None, doc=None)
,可以把自定义的函数fget
、fset
、fdel
影射为描述符的__get__()
、__set__()
、__delete__()
方法。
利用它可以避免搞乱类的名字空间,并很好的控制属性访问。
第14章 执行环境
默认情况下,类的
__call__()
方法是没有实现的。如果在类定义中覆盖了它,这个类的实例就成为可调用的。调用这样的实例对象等同于调用__call__()
方法。compile()
函数提供了一次性字节代码预编译,以后每次调用都不用重新编译。用法示例:
1、可求值表达式
eval_code = compile('100+200', '', 'eval')
eval(eval_code)
2、单一可执行语句
single_code = compile('print "Hello World!"', '', 'single')
exec single_code
3、可执行语句组
exec_code = compile("""
req = input('Count how many numbers?')
for eachNum in range(req):
print eachNum
""", '', 'exec')
exec exec_code
以交互方式输入的表达式,按下回车后的结果应和
eval()
返回的结果相同。内建函数
input()
等价于eval(raw_input())
,即它把输入作为 Python 对象来求值并返回表达式的结果。可以将字符串形式的测试代码作为函数属性,并通过
exec
语句执行。execfile(filename)
相当于f = open(filename, 'r'); exec f; f.close()
。如果想把外部命令的返回值读入变量并执行内部操作或存储到日志文件中,可以使用
popen()
,它返回一个类文件对象。