python学习手册

python学习手册

前言

还没有系统学习语法,这是我选择的系统学习语法的书。因为之前已经看过4本python的书。在摘抄的时候,过于基本的就不写了。做为一个vim党,idle之类的内容我都略过了。

评价

这是我目前为止看过内容最丰富的python书。以python2.6和python3.0为基础讲解的,并且对比了两个差别。适合大概浏览后放在桌上的参考书。本书定位为基础书籍。讲得比较细,但是对有经验的程序员来说,会感觉很啰嗦,该略过就略过吧,没啥损失。看过这本书之后,之前写代码和看代码时的疑惑都得到了解答。_^

学语法,这本书就够了。
要写出程序,还要看《标准库》。特定领域的功能得熟悉特定领域的模块。
要写网站,可能还得看django或flask等框架。
程序结构要好,易于扩展,要看设计模式。
值得买一本放到桌上:https://s.click.taobao.com/Nq5KsAx

内容

第一部分

第1章 问答部分(可不看)

下面是我个人写的,和书无关:
我选择python的原因比较简单,python的代码量少,很方便地用于学习研究。嗯,我喜欢研究。暂时还是熟悉工具的过程:
我学习python的书单有:

  • 《Flask Web开发 基于Python的Web应用开发实战》
  • 《python绝技:运用python成为顶级黑客》
  • 《python标准库》
  • 《python学习手册》
  • 《python算法》
  • 《python编程实战》
  • 《effective python》
  • 《python cookbook》
  • 《python 高级编程》
  • 《python 核心编程》
  • 《python自动化运维》

看完之后开始阅读源码,先把flask源码看完。

第2章 Python如何运行程序

python filenam.py 执行程序。深入点看下面。
执行过程:

  1. python内部会先将源码编译成字节码。生成.pyc文件。
    ps1:在有写入权限时,.pyc会在磁盘上,否则是在内存中,在磁盘上的pyc可以当成一种缓存,可以直接运行,因为省略了编译过程,执行速度更快。并且在源文件更新过后,.pyc也会更新。
    ps2:字节码并不是机器二进制码,只是特定于python的一种表现形式,这也是python能跨平台,执行速度又没有c/c++快的原因。
  2. python虚拟机(pvm)载入字节码并执行。其实并不存在这样一个专门的pvm, pvm是python本身的一部分,用来执行节字码的一个大循环。

简单描述:
m.py(源文件)—>m.pyc(字节码)—>pvm(运行时)

第3章 如何运行程序

4种方式:
1. 交互模式。直接键入python。 主要是用来测试的。譬如我忘记os模块有哪些功能了。

Python 2.7.12 (default, Nov  4 2016, 00:00:52)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.38)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> dir(os)
['EX_CANTCREAT', 'EX_CONFIG', 'EX_DATAERR', 'EX_IOERR', 'EX_NOHOST', 'EX_NOINPUT', 'EX_NOPERM', 'EX_NOUSER', 'EX_OK', 'EX_OSERR', 'EX_OSFILE', 'EX_PROTOCOL', 'EX_SOFTWARE', 'EX_TEMPFAIL', 'EX_UNAVAILABLE', 'EX_USAGE', 'F_OK', 'NGROUPS_MAX', 'O_APPEND', 'O_ASYNC', 'O_CREAT', 'O_DIRECTORY', 'O_DSYNC', 'O_EXCL', 'O_EXLOCK', 'O_NDELAY', 'O_NOCTTY', 'O_NOFOLLOW', 'O_NONBLOCK', 'O_RDONLY', 'O_RDWR', 'O_SHLOCK', 'O_SYNC', 'O_TRUNC', 'O_WRONLY', 'P_NOWAIT', 'P_NOWAITO', 'P_WAIT', 'R_OK', 'SEEK_CUR', 'SEEK_END', 'SEEK_SET', 'TMP_MAX', 'UserDict', 'WCONTINUED', 'WCOREDUMP', 'WEXITSTATUS', 'WIFCONTINUED', 'WIFEXITED', 'WIFSIGNALED', 'WIFSTOPPED', 'WNOHANG', 'WSTOPSIG', 'WTERMSIG', 'WUNTRACED', 'W_OK', 'X_OK', '_Environ', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '_copy_reg', '_execvpe', '_exists', '_exit', '_get_exports_list', '_make_stat_result', '_make_statvfs_result', '_pickle_stat_result', '_pickle_statvfs_result', '_spawnvef', 'abort', 'access', 'altsep', 'chdir', 'chflags', 'chmod', 'chown', 'chroot', 'close', 'closerange', 'confstr', 'confstr_names', 'ctermid', 'curdir', 'defpath', 'devnull', 'dup', 'dup2', 'environ', 'errno', 'error', 'execl', 'execle', 'execlp', 'execlpe', 'execv', 'execve', 'execvp', 'execvpe', 'extsep', 'fchdir', 'fchmod', 'fchown', 'fdopen', 'fork', 'forkpty', 'fpathconf', 'fstat', 'fstatvfs', 'fsync', 'ftruncate', 'getcwd', 'getcwdu', 'getegid', 'getenv', 'geteuid', 'getgid', 'getgroups', 'getloadavg', 'getlogin', 'getpgid', 'getpgrp', 'getpid', 'getppid', 'getsid', 'getuid', 'initgroups', 'isatty', 'kill', 'killpg', 'lchflags', 'lchmod', 'lchown', 'linesep', 'link', 'listdir', 'lseek', 'lstat', 'major', 'makedev', 'makedirs', 'minor', 'mkdir', 'mkfifo', 'mknod', 'name', 'nice', 'open', 'openpty', 'pardir', 'path', 'pathconf', 'pathconf_names', 'pathsep', 'pipe', 'popen', 'popen2', 'popen3', 'popen4', 'putenv', 'read', 'readlink', 'remove', 'removedirs', 'rename', 'renames', 'rmdir', 'sep', 'setegid', 'seteuid', 'setgid', 'setgroups', 'setpgid', 'setpgrp', 'setregid', 'setreuid', 'setsid', 'setuid', 'spawnl', 'spawnle', 'spawnlp', 'spawnlpe', 'spawnv', 'spawnve', 'spawnvp', 'spawnvpe', 'stat', 'stat_float_times', 'stat_result', 'statvfs', 'statvfs_result', 'strerror', 'symlink', 'sys', 'sysconf', 'sysconf_names', 'system', 'tcgetpgrp', 'tcsetpgrp', 'tempnam', 'times', 'tmpfile', 'tmpnam', 'ttyname', 'umask', 'uname', 'unlink', 'unsetenv', 'urandom', 'utime', 'wait', 'wait3', 'wait4', 'waitpid', 'walk', 'write']
>>>

我有时也会把它当成一个简单的计算器

Python 2.7.12 (default, Nov  4 2016, 00:00:52)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.38)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 20000-3500-(20000-3500)*(0.12+0.2)
11220.0
>>>

2.写成一个文件,再执行。

python script1.py

3.导入模块。每一个文件就是一个模块,可以被其它文件导入并执行。文件只有在第一次导入时才会运行代码。如果需要再次运行,需要借助于imp模块的relaod。
在命令行中可以直接使用python -m modulename 来执行模块文件。
ps: from 和 import 差不多,但是from会复制属性到当前模块的命名空间中,好处是使用时去掉了模块名,更好写,缺点是需要注意变量名覆盖问题。尤其是用到了from xxx import *的时候。我就遇到过了变量覆盖的问题。

4.使用exec运行模块文件。例如exec(open(‘module.py’).read())。在2.6中还有一个更简单的函数。execfile(filename),如execfile(‘module.py’)。事实上,还有codeevalcompile模块也能,可以参考下python标准库

第二部分 类型和运算

第4章 介绍Python对象类型

内置对象

对象类型 例子 常量/创建
数字 1234,3.14145,3+4j,Decimal,Fraction
字符串 ‘span’,”go2live.cn”,b’axolc’
列表 [1,[2,’three’],4]
字典 {‘food’:’spam’,’tests’:’yum’}
元组 (1,’spam’,4,’U’)
文件 myfile=open(‘eggs’,’r’)
集合 set(‘abc’),{‘a’,’b’,’c’}
其它类型 类型、None、布尔值
编程单元类型 函数、模块、类
与实现相关的类型 编译的代码堆栈跟踪

ps:虽然我们有时候会用typy,isinstance等判断对象的类型,但是最好不要滥用,因为这样限制了对象的使用,因而也破坏了代码的灵活性,写出来的代码,也就不够’pythonic’

第5章 数字

Python数字类型的完整工具包括:

  • 整数和浮点数 1234,1.23, 0x9ff, 0o177,0B1101
  • 复数 3+4j
  • 固定精度的十进制数
  • 有理分数 Fraction
  • 集合
  • 布尔类型
  • 无穷的整数精度
  • 各种数字内置函数和模块

Python表达式操作符及程序

操作符 描述
yield x 生成器函数发送协议
lambda args: expression 生成匿名函数
x if y else z 三元选择表达式
x or y 逻辑或(只有x为假,才会计算y)
x and y 逻辑与(只有x为真,才会计算y)
not x 逻辑非
x in y, x not in y 成员关系(可迭代对象、集合)
x is y, x is not y 对象实体测试
x < y, x <= y, x >y , x>= y, x==y, x!=y 大小比较,集合子集和超集值相等性操作符
x y
x ^ y 位异或,集合对象差
x & y 位与,集合交集
x << y, x >>y 左移或右移y位

….

第6章 动态类型介绍

变量与对象、引用的关系:

  • 变量是一个系统表的元素,拥有指向对象的连接的空间。在初次赋值时创建。
  • 对象是分配的一块内存,有足够的空间去表示它们所代表的值。
  • 引用是自动形成的从变量到对象的指针。

从技术上来讲,每一个对象都有两个标准的头部信息:一个类型标志符去标识这个对象的类型,
以及一个引用的计数器,用来决定是不是可以回收这个对象。

在Python中,变量名没有类型,类型属于对象,而不是变量

需要注意共享引用和在原处修改

a = [1,2,3]
b = a # a和b指向了同一个对象
a[2] = 3 #修改了列表对象的值后,因为b和a指向同一个对象,b指向的对象值其实也发生了变化。
#在这里看不出来。但如果语句2和语句3跨了几十上百行,可能发生了Bug就不那么好发现了。

关于is和==

  • is 是判断两个变量是否指向同一个对象,即测试对象的一致性。
  • == 是判断两个变量指向的对象的值是否一样,即测试值的相等性。

第7章 字符串

字符串是不可变序列。

常见字符串常量和表达式

操作 解释
s = ” 空字符串
s = “spam’s” 双引号和单引号相同
s = ’snptax00m’ 转义序列
s = “””…””” 三重引号字符串块
s = r’tempspam’ Raw字符串,针对转义序列来的,关闭了转义功能。
s= b’spam’ python3.0中的字节字符串
s = u’spam’ 仅在python2.6中使用的unicode字符串
s1+s2 合并
s*3 重复
s[i], s[i:j], len(s) 索引,分片,求长度
“a %s parrot” % kind 字符串格式化表达式
“a {0} parrot”.format(kind) 字符串格式化方法
s.find(‘pa’) 搜索
s.rstrip() 移动空格
s.replace(‘pa’,’xx’) 替换
s.split(‘,’) 用占位符分隔
s.isdigit() 内容测试
s.lower() 变小写
s.endswith(‘spam’) 尾部测试
‘spam’.join(strlist) 插入分隔符
s.encode(‘latin-1’) 字符串编码
for x in S:print(x) 迭代
‘spam’ in s 成员关系

扩展分片:第三个限制值
完整形式的分片是X[I:J:K], I是起始值,默认为0;J是结束值,默认是字符串长度;K是步进。

Python的设计座右铭之一就是拒绝猜的诱惑

字符串代码转换
单个的字符可以通过将其传给内置的ord函数转换为其对应的ASCII码–这个函数实际上返回的是这个字符在内存中对应的字符的二进制值。而chr函数将会执行相反的操作,获取ASCII码并将其转化为对应的字符。

字符串格式化代码

代码 意义
s 字符串(或任何对象)
r s,但使用repr,而不是str
c 字符
d 十进制(整数)
i 整数
u 无符号整数
o 八进制整数
x 十六进制整数
X x,但打印大写
e 浮点指数
E e,但打印大写
f/F 浮点十进制
g 浮点e或f
G 浮点E或F
% 常量%

还有基于字典的字符串格式化, 左边的转换目标引用右边字典中的键来提取对应的值。

>>> "%(n)d %(x)s" % {"n":1,"x":"spam"}

字符串格式化调用方法
字符串对象的format方法使用主体字符串作为模板,并且接受任意多个表示将要根据模板替换的值的参数。
在主体字符串中,花括号通过位置(例如,{1})或关键字(例如,{food})指出替换目标及要插入的参数。

第8章 列表与字典

列表list是Python中最具灵活性的有序集合对象类型。
常用列表常量和操作

操作 解释
L=[] 一个空的列表
L = [0,1,2,3] 四项:索引为0到3
L = [‘abc’,[‘def’,‘ghi’]] 潜套的子列表
L = list(‘spam’), L = list(range(-4,4)) 可迭代项目的列表,连续整数的列表
L[i], L[i][j] L[i:j], len(L) 索引,索引的索引,分片,求长度
L1+L2 合并
L * 3 重复
for x in L : print(x) 迭代
3 in L 成员关系
L.append(4),L.extend([5,6,7]) 方法:增加
L.sort(),L.index(1),L.insert(I,X),L.reverse() 方法:排序,搜索,插入,反转
del L[k] 方法,语句:缩短
del L[i:j]
L.pop()
L.remove(2)
L[i:j] = []
L[i] = 1 索引赋值,分片赋值
L[i:j] = [4,5,6]
L = [ x**2 for x in range(5)] 列表解析
list(map(ord,’spam’))

除了列表以外,字典(dict)也许是Python之中最灵活的内置数据结构类型。
常见字典常量和操作

操作 解释
D = {} 空字典
D = {‘spam’:2,’eggs’:3} 两项目字典
D = {‘food’:{‘ham’:1,’egg’:2}} 嵌套
D = dict.fromkeys([‘a’,’b’]) 其它构造技术
D = dict(zip(keyslist,valslist)) 关键字、对应的就、键列表
D = dict(name=‘Bob’,age=42)
D[‘eggs’] 以键进行索引运算
D[‘food’][‘ham’]
‘eggs’ in D 成员关系:键存在测试
D.keys() 方法:键
D.values()
D.items() 键+值
D.copy() 副本
D.get(key,default) 不在key时,返回default
D.update(D2) 合并
D.pop(key) 删除等
len(D) 长度
D[key]=42 新增/修改键,删除键
del D[key] 根据键删除条目
list(D.keys()) 字典视图
D1.keys() & D2.keys()
D = { x: x*2 for x in range(10)} 字典解析

字典用法注意事项

  • 序列运算无效。字典是映射机制,不是序列。
  • 对新索引赋值会添加项。
  • 键不一定总是字符串。

字典可用于稀疏数据结构

>>> Matrix = {}
>>> Matrix[(2,3,4)] = 88
>>> Matrix[(7,8,9)] = 99
>>> X=2;Y=3;Z=4
>>> Matrix[(X,Y,Z)]
88
>>> Matrix
{(2, 3, 4): 88, (7, 8, 9): 99}
>>>

三种方式避免missing-key错误:

  • 成员测试 if key in D:
  • try except捕获异常
  • get(key,default)不存在时给默认值。

字典解析
Python3.0中的字典也可以用字典解析来创建。
字典解析隐式地运行一个循环,根据每次迭代收集表达式的键/值结果,并且使用它们来填充一个新的字典。
字典视图
在Python3.0中,字典的keys、vales和items都返回视图对象,而在Python2.6中,它们返回实际的结果列表。视图对象是可迭代的,这就意味着对象每次产生一个结果项,而不是在内存中立即产生结果列表。

字典大小比较不再有效果,has_key方法已死:in 永生。

第9章 元组、文件和其他

元组由简单的对象组构成。元组与列表非常类似,只不过元组不能在原处修改(它们是不可变的),并且通常写成圆括号(而不是方括号)中的一系列项。虽然元组不支持任何方法调用,但元组具有列表大多数属性。
常见元组常量和运算

运算 解释
() 空元组
T = (0,) 单个元素的元组,‘,’号是为了避免歧义。因为(0)可以表求数学运算,结果是0
T = (0,’Ni’,1.2,3) 四个元素的元组
T = 0,’Ni’,1.2,3 另一个四元素的无组(与前列相同)
T = (‘abc’,(‘def’,’ghi’)) 嵌套元组
T = tuple(‘spam’) 一个可迭代对象的项的元组
T[i] 索引
T[i][j] 索引的索引
T[i:j] 分片
len(T) 长度
T1+T2 合并
T * 3 重复
for x in T: print(x) 迭代
‘spam’ in T 成员关系
[x **2 for x in T ]
T.index(‘Ni’) 搜索
T.count(‘Ni’) 计数

作为特殊情况,在不会引起语法冲突的情况下,Python允许忽略元组的圆括号。
元组不能排序,在需要排序时先转成list,排完序再转成tuple。也可以用新的sorted内置方法,它接受任何序列对象。

需要注意元组的不可变性只适用于元组本身顶层而并非其内容。例如,元组内部的列表是可以像往常那样修改的。

>>> T = (1,[2,3],4)
>>> T[1] = 'spam'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> T[1][0] = 'spam'
>>> T
(1, ['spam', 3], 4)
>>>

文件,调用open方法会返回一个文件对象。
常见文件运算

操作 解释
output = open(r’C:spam’,’w’) 创建输出文件
input = open(‘data’,’r’) 创建输入文件
input = open(‘data’) 同上,’r’是默认值
aString = input.read() 把整个文件读进单一字符串
aString = input.read(N) 读取之后的N个字节到一个字符串
aString = input.readline() 读取下一行(包括行末标识符)到一个字符串
aList = input.readlines() 读取整个文件到字符串列表
output.write(aString) 写入字节字符串到文件
output.writelines(aList) 把列表内的所有字符串写入文件
output.close() 手动关闭
output.flush() 把输出缓冲区刷到硬盘中,但不关闭文件
anyFile.seek(N) 修改文件位置到偏移量N处以便进行下一个操作
for line in open(‘data’):use line 文件迭代器一行一行地读取
open(‘f.txt’,encoding=‘latin-1’) python3.0 Unicode文本文件
open(‘f.bin’,’rb’) python3.0 二进制byte文件

使用文件的一些提示:

  • 文件迭代器是最好的读取行工具
  • 内容是字符串,不是对象
  • close是通常选项
  • 文件是缓冲的并且是可查找的

Python3.0中文本和二进制文件区别:

  • 文本文件把内容表示为常规的str字符串,自动执行Unicode编码和解码,并且默认执行末行转换。
  • 二进制文件把内容表示为一个特殊的bytes字符串类型,并且允许程序不修改地访问文件内容。

文本文件中存储的都是字符串,要还原成Python对象,可以使用转换工具,如int()等,也可以使用eval,但是eval功能太强大,恶意代码同样会执行,可参见python标准库。在数据来源无法依赖的情况下,可以使用pickle来序列化/反序列化Python对象。不过pickle是Python专用的,其它编程语法无法解析。

>>> D={'a':1,'b':2}
>>> F = open('datafile.pkl', 'wb')
>>> import pickle
>>> pickle.dump(D,F)
>>> F.close()
>>> F = open('datafile.pkl','rb')
>>> E = pickle.load(F)
>>> E
{'a': 1, 'b': 2}

pickle做为简单的本地存储还是不错的。当然你也可以用Sqlite。
至于处理结构化二进制数据的模块还有struct,不过你得自己写对模板。
文件上下文管理器
with语句,可以帮助我们自动地关闭文件,而不需要手动调用close

>>> with open(r'datafile.pkl') as myfile:
...     for line in myfile:
...             print(line)

除了open,还有其它一些文件工具:

  • 标准流, sys.stdin,sys.stdout等
  • os模块中的描述文件:处理整数文件,支持诸如文件锁定之类的较低级工具。
  • sockets、pipes和FIFO文件:文件类对象,用于同步进程或者通过网络进行通信。
  • 通过键来存取的文件:通过键直接存储的不变的Python对象。
  • Shell命令流:像os.popen和subprocess.Popen这样的工具,支持产生shell命令,并读取和写入到标准流
  • 还有第三方的PySerail和pexpect等。

对象分类

对象分类 分类 是否可变
数字 数值
字符串 序列
列表 序列
字典 对应
元组 序列
文件 扩展 N/A
Sets 集合
frozenset 集合
bytearray(3.0) 序列

引用 vs 拷贝
赋值操作总是存储对象的引用。这样一来,在共享引用的时候,对a对象的修改。可能会引起其它同样指向a对象变量的修改,这可能不是我们期待的。如果确定这不是我们想用的,用拷贝才合适。
有4种方法获取拷贝:

  • 没有限制条件的分片表达式(L[:])能够复制序列。
  • 字典copy方法(X.copy())能够复制字典。
  • 有些内置函数(例如,list)能够生成拷贝(list(L))。
  • copy标准库模块能够生成完整拷贝。这里需要注意下浅拷贝和深拷贝,copy.copy是浅拷贝,copy.deepcopy是深拷贝。
>>> L = [1,2,3]
>>> import copy
>>> L2 = copy.copy(L)
>>> L2
[1, 2, 3]
>>> L[1]='2'
>>> L
[1, '2', 3]
>>> L2
[1, 2, 3]
>>> L = [1,2,3,['a','b','c']]
>>> L2 = copy.copy(L)
>>> L[3][0] = 'aaaaa'
>>> L
[1, 2, 3, ['aaaaa', 'b', 'c']]
>>> L2
[1, 2, 3, ['aaaaa', 'b', 'c']]
>>>

需要注意:无条件值的分片以及字典copy方法只能做顶层复制,相当于copy.copy,如果需要能够复制嵌套的数据结构,得使用copy.deepcopy。

真假判断

  • 数字如果非零,则为真。
  • 其他对象如果非空,则为真。

对象真值的例子

对象
“spam” True
“” False
[] False
{} False
1 True
0.0 False
None False

Python主要内置对象类型

第三部分 语句和语法

第10章 python语句简介

重访Python程序结构

  • 程序由模块构成。
  • 模块包含语句。
  • 语句包含表达式。
  • 表达式建立并处理对象。

Python语句

语句 角色 例子
赋值 创建引用值 a,b,c = ‘good’,’bad’,’ugly’
调用 执行函数 log.write(‘spam, ham’)
打印调用 打印对象 print(‘The killer’, joke)
if/elif/else 选择动作 if “python” in text: print(text)
for/else 序列迭代 for x in mylist: print(x)
while/else 一般循环 while X > Y: print(‘hello’)
pass 空占位符 while True:pass
break 循环退出 while True: if exittest():break
continue 循环继续 while True: if skiptest(): continue
def 函数和方法 def f(a,b,c=1,*d):print(a+b+c+d[0])
return 函数结果 def f(a,b,c=1,*d):return a+b+c+d[0]
yield 生成器函数 def gen(n): for i in n: yield i*2
global 命名空间
nonlocal 命名空间
import 模块访问 import sys
from 属性访问 from sys import stdin
class 创建对象
try/except finally 捕捉异常
raise 触发异常 raise EndSearch(‘action error’)
assert 调试检查 assert X > Y, ‘X too small’
with/as 环境管理器
del 删除引用

第11章 赋值、表达式和打印

赋值语句形式

运算 解释
spam = ‘Spam’ 基本形式
spam,ham=‘yum’,’YUM’ 元组赋值运算(位置性)
[spam,ham] = [‘yum’,’YUM’] 列表赋值运算(位置性)
a,b,c,d = ‘spam’ 序列赋值运算,通用性
a,*b=‘spam’ 扩展的序列解包(3.0)
spam = ham = ‘lunch’ 多目标赋值运算
spams += 42 增强赋值运算(相当于spams=spams+42)

Python3.0中的扩展序列解包
一个带有单个星号的名称,可以在赋值目标中使用,以指定对于序列的一个更为通用的匹配– 一个列表赋给了带星号的名称,该列表收集了序列中没有赋值给其他名称的所有项。

>>> seq=[1,2,3,4,5,6]
>>> a,b,*c,d = seq
>>> a,b,c,d
(1, 2, [3, 4, 5], 6)
>>> seq=[1,2,3]
>>> a,b,*c,d = seq
>>> a,b,c,d
(1, 2, [], 3)
>>>

调用Python3.0的print函数有如下的形式:
print([object,…][,sep=’ ‘][, end=’n’][, file=sys.stdout])

第12章 if测试和语法规则

避免混合使用制表符和空格:Python3.0中的新的错误检查

if/else的三元表达式是 X if Y else Z。而不是Y?X:Z哦。
在Python2.5之前没有这个表达式,是通过 and or 实现的。如A = ((X and Y) or Z)
现在也还可以使用。使用的前提是,确保Y是布尔真值,否则即便X为真,还是会返回Z,这当然不是你需要的

第13章 while和for循环

while循环的形式
python
while <test>:
<statements1>
else:#只有当循环正常离开时才会执行(也就是没有碰到break语句。)
<statements2>

在Python中,赋值语句只是赋值,而不是表达式,不会像C语言一样返回赋值后的值。
在Python中是没有类似于while((x=next()) !=NULL) {..process x…}这种写法的。

for循环的形式

for <target>in <object>:
    <statements>
else:
    <statements>

python提供了两个内置函数,在for循环内定制迭代:

  • 内置range函数返回一系列连续增加的整数,可作为for中的索引。
  • 内置zip函数返回并行元素的元组的列表,可用于在for中内遍历数个序列。

在python3.0中,range是一个迭代器,会根据需要产生元素。如果需要完整的列表,需要将其包含到一个list调用中。
range(start,end,step)。

对于遍历字符串来说,使用range唯一的真正优点是—-它没有复制字符串,并且不会在Python3.0中创建一个列表,对于很大的字符串来说,这会节省内存。
对于小的字符串来说,使用分片操作比range更方便。

当参数长度不同时,zip会以最短序列的长度为准来截断所得到的元组。如

>>> S1='abc'
>>> S2='xyz123'
>>>
>>> list(zip(S1,S2))
[('a', 'x'), ('b', 'y'), ('c', 'z')]

enumerate函数返回一个一成器对象。调用next()返回偏移量和元素。

>>> S1='abc'
>>> e=enumerate(S1)
>>> e
<enumerate object at 0x10d22e1b0>
>>> next(e)
(0, 'a')
>>> for (index,item) in e:
...     print(index, item)
...
1 b
2 c
>>>

第14章 迭代器和解析,第一部分

Python中所谓的迭代协议:有next方法的对象会前进到下一个结果,而在一系列结果的末尾时,则会引发StopIteration。在Python中,任何这类对象都认为是可迭代的。任何这类对象也能以for循环或其它迭代工具遍历,因为所有迭代工具内部工作起来都是在每次迭代中调用next,并且捕获StopIteration异常来确定何时离开。

按行读取文件内容的最佳实践就是利用这一点,因为文件对象也有next方法,是可迭代的。

>>> for line in open('script1.py'):
...     print(line,end='')
...
import sys                # Load a library module
print(sys.platform)
print(2 ** 100)           # Raise 2 to a power
x = 'Spam!'
print(x * 8)              # String repetition

>>>

迭代器在python中是以C语言的速度运行的,比while循环要快。是迭代的优先选择

Python3.0提供了一个内置函数next,它会自动调用一个对象的next方法。给定一个可迭代对象X,调用next(X)等同于X.next()。

有时候,术语”可迭代的”指的是支持iter的一个对象,而”迭代器”指的是iter所返回的一个支付next(I)的对象

for循环迭代的步骤是:把可迭代对象传给iter获取到迭代器。然后调用迭代器的next方法,直到捕获到StopIteration异常。

列表解析一般是处理列表的最佳实践。

>>> [line.rstrip() for line in open('script1.py')]
['import sys                # Load a library module', 'print(sys.platform)', 'print(2 ** 100)           # Raise 2 to a power', "x = 'Spam!'", 'print(x * 8)              # String repetition', '']

扩展的列表解析语法
表达式中的嵌套for循环可以有一个相关的if语句,来过滤那些测试不为真的结果项。

>>> [line.rstrip() for line in open('script1.py') if line[0]=='p']
['print(sys.platform)', 'print(2 ** 100)           # Raise 2 to a power', 'print(x * 8)              # String repetition']
>>>

列表解析还可以变理更复杂,如包含嵌套的循环,也可能被编写为一系列的for子句。如

>>> [x+y for x in 'abc' for y in 'lmn']
['al', 'am', 'an', 'bl', 'bm', 'bn', 'cl', 'cm', 'cn']

在Python3.0更强制可迭代对象,在2.6中map,range,zip,filter等是返回的结果列表,而在3.0中只是返回一个可迭代对象,为了获取结果,还得调用list。

>>> zip('abc','xyz')
<zip object at 0x10d22dfc8>
>>> list(zip('abc','xyz'))
[('a', 'x'), ('b', 'y'), ('c', 'z')]

多个迭代器 vs 单个迭代器
range对象,它支持len和索引,它不是自己的迭代器(手动迭代时,我们可以使用iter产生一个迭代器),并且,它支持在其结果上的多个迭代器,这些迭代器会记住它们各自的位置,而zip、map和filer因为就是其自身的迭代器,都不支持相同结果上的多个活跃迭代器。

通常针对iter调用返回一个新的对象,来支持多个迭代器;单个的迭代器一般意味着一个对象返回其自身。

第15章 文档

Python文档资源

形式 角色
#注释 文件中的文档
dir函数 对象中可用属性的列表
文档字符串:__doc__ 附加在对象上的文件中的文档
PyDoc:help函数 对象的交互帮助
PyDoc:HTML报表 浏览器中的模块文档
标准手册 正式的语言和库的说明
网站资源 在线教程、例子等
出版的书籍 商业参考书籍

module,class,method,function都有doc属性,如果要取出模块中类的方法函数的文档字符串,可以通过路径访问类:module.class.method.doc

help函数会启用PyDoc从而产生简单的文字报表。

PyDoc启用Gui按下面的方式就可以

>>> import pydoc
>>> pydoc.gui()

会弹出Gui界面,输入搜素的模块,选中一个结果后,选”go to selected”就会打开浏览器,在浏览器中显示文档。

不过在这里我还是推荐mac用户使用dash。参见mac编程装哪些软件

第四部分 函数

第16章 函数基础

函数是Python为了代码最大程度的重用和最小化代码冗余而提供的最基本的程序结构。
函数相关的语句和表达式

语句 例子
Calls myfunc(“spam”,”eggs”,meat=ham)
def, def adder(a,b=1,*c):
return return a+b+c[0]
global def changer(): global x;x=“new”
nonlocal def changer():nonlocal x; x=‘new’
yield def squares(x): for i in rang(x):yield i ** 2
lambda Funcs = [lambda x: x*2, lambda x:x3]

主要概念的简要介绍:

  • def是可执行的代码。函数并不存在,直到Python运行了def后才存在。
  • def创建了一个对象并将其赋值给某一变量名。当Python运行到def语句时,它将会生成一个新的函数对象并将其赋值给这个函数名。
  • lambda创建一个对象但将其作为结果返回。
  • return将一个结果对象发送给调用者。
  • yield向调用者发回一个结果对象,但是记住它离开的地方。
  • global声明了一个模块级的变量并被赋值。如果不改变这个全局变量,也可以不声明global。
  • nonlocal声明了将要赋值的一个封闭的函数变量。感觉像闭包的用法。
  • 函数是通过赋值(对象引用)传递的。
  • 参数、返回值以及变量并不是声明。

Python将对某一对象在某种语法的合理性交由对象自身来判断。这种依赖类型的行为称为多态。
在Python中,代码不应该关心特定的数据类型。这其实要求我们写完善的测试用例来测试代码。关于测试,我推荐Flask Web开发 基于Python的Web应用开发实战

第17章 作用域

当你在一个程序中使用变量名时,Python创建、改变或者查找变量名是在所谓的命名空间(一个保存变量名的地方)中进行的。
在代码中变量名被赋值的位置决定了这个变量名能被访问到的范围。

变量可以在3个不同的地方分配,分别对应3种不同的作用域:

  • 如果一个变量在def内赋值,它被定位在这个函数内。
  • 如果一个变量在一个嵌套的def中赋值,对于嵌套的函数来说,它是非本地的。
  • 如果在def之外赋值,它就是整个文件全局的。

函数定义了本地作用域,而模块定义的是全局作用域。这两个作用域有如下的关系。

  • 内嵌的模块是全局作用域。
  • 全局作用域的作用范围仅限于单个文件。
  • 每次对函数的调用都创建了一个新的本地作用域。
  • 赋值的变量名除非声明为全局变量或非本地变量,否则均为本地变量。如果需要给一个在函数内部却位于模块文件顶层的变量名赋值,需要在函数内部通过global语句声明。如果需要给位于一个嵌套的def中的名称赋值,从Python3.0开始可以通过一条nonlocal语句中声明它来做到。
  • 所有其他的变量都可以归纳为本地、全局或内置的。

交互模式运行的代码实际上真的输入到一个叫做main的内置模块中

变量名解析:LEGB原则
对于一个def语句:

  • 变量名引用分为三个作用域进行查找:首先是本地,之后是函数内(如果有的话),之后全局,最后是内置。
  • 在默认情况下,变量名赋值会创建或者改变本地变量。
  • 全局声明和非本地声明将赋值的变量名映射到模块文件内部的作用域。

Python的变量名解析机制有时称为LEGB法则,这也是由作用域的命令而来的。

  • 当在函数中使用未认证的变量名时,Python搜索4个作用域【本地作用域(L=Local), 之后是上一层结构的def或lambda的本地作用域(E=Enclosing function locals),之后是全局作用域(G=Global),最后是内置作用域(B=Build-in)】,并且在第一处能够找到这个变量的地方停下来。
  • 当在函数中给一个变量名赋值时(而不是在一个表达式中对其进行引用),Python总是创建或改变本地作用域的变量名,除非它已经在那个函数中声明为全局变量。
  • 当在函数之外给一个变量名赋值时(也就是,在一个模块文件的顶层,或者是在交互提示模式下),本地作用域与全局作用域(这个模块的命名空间)是相同的。

这里的LEGB仅对简单变量有用,至于对象属性,那是另一套规则,和继承有关。即LEGB规则只适用于无点号运算的纯变量名。

PyCheker可以检查python代码的问题

关于全局变量:

  • 全局变量是位于模块文件内部的顶层的变量名。
  • 全局变量如果是在函数内被赋值的话,必须经过声明。
  • 全局变量名在函数的内部不经过声明也可以被引用。

一个模块文件的全局变量一旦被导入就成为了这个模块对象的一个属性。

函数嵌套通常用作装饰器。

看个工厂函数的例子

>>> def maker(N):
...     def action(X):
...             return X ** N
...     return action
...

>>> f = maker(2)
>>> f
<function maker.<locals>.action at 0x1077efd08>
>>> f(3)
9

内嵌函数记住了整数2,即maker函数内部的变量N的值,尽管在调用执行f时maker已经返回了值并退出。实际上,在本地作用域内的N被作用执行的状态信息保留了下来。

除了类,全局变量、像这样的嵌套作用域引用以及默认的参数是Python的函数能够保留状态信息的主要方法。

需要注意的是LEGB中的E是在Python2.2才引进的。在这之前需要利用默认参数才能实现。
所以如果看到下面的代码,请不要奇怪。x=x在python2.2之前是不能省略的。。

>>> def func():
...     x = 4
...     action = (lambda n, x=x: x**n)
...     return action
...
>>>

作用域与带有循环变量的默认参数相比较
如果lambda或者def在函数中定义,嵌套在一个循环之中,并且嵌套的函数引用了一个上层作用域的变量,该变量被循环所改变,所有在这个循环中产生的函数将会有相同的值–在最后一次循环中完成时被引用变量的值。

>>> def makeActions():
...     acts = []
...     for i in range(5):
...             acts.append(lambda x: i ** x)
...     return acts
...
>>> acts = makeActions()
>>> acts[0]
<function <lambda> at 0x1096de140>
>>> acts[0](2)
16
>>>

在定义的时候,其实期望的是每个函数记住嵌套作用域中当前变量i的值。
为什么会失效呢?
因为在Python中,嵌套作用域中的变量是在嵌套的函数被调用时才进行查找。在调用的时候,i的值已经变成了4。

怎么解决呢?
可以利用默认参数。因为默认参数是在嵌套函数创建时评估。
代码改成下面的就OK了。

>>> def makeActions():
...     acts = []
...     for i in range(5):
...             acts.append(lambda x,i=i: i**x)
...     return acts
...
>>> acts = makeActions()
>>> acts[0]
<function <lambda> at 0x1096de0c8>
>>> acts[0](2)
0
>>> acts[1](2)
1
>>>

nonlocal是global的近亲。嵌套函数可以引用一个嵌套的函数作用域中的变量,而加上nonlocal声明,则可以修改这个变量了。
和global语句不同的是:当执行一条nonlocal语句时,nonlocal名称必须已经在一个嵌套的def作用域赋值过,否则将会得到一个错误。

函数属性也能保持状态

>>> def tester(start):
...     def nested(label):
...             print(label,nested.state)
...             nested.state += 1
...     nested.state = start
...     return nested
...
>>> f = tester(0)
>>> f('go2live.cn')
('go2live.cn', 0)
>>> f('http://go2live.cn')
('http://go2live.cn', 1)
>>>

第18章 参数

函数传递参数时的简要的关键点:

  • 参数的传递是通过自动将对象赋值给本地变量名来实现的。
  • 在函数内部的参数名的赋值不会影响调用者。
  • 改变函数的可变对象参数的值也许会对调用者有影响。

Python的通过赋值进行传递的机制与C语言传递模型相当相似。

  • 不可变参数”通过值”进行传递。
  • 可变对象是通过”指针”进行传递的。

实际上Python的参数传递模型相当简单:它仅仅是将对象赋值给变量名,并且无论对可变对象或不可变对象都是这样的。

避免可变参数的修改
手动copy一个对象。修改copy的对象就不会影响到原来的对象了。
如果在你没有预期的情况下对象在外部发生了改变,检查一下是不是调用了的函数引起的,并且有必要的话当传入对象时进行拷贝。

参数匹配模型大纲:

  • 位置:从左至右进行匹配
  • 关键字参数:通过参数名进行匹配
  • 默认参数:为没有传入值的参数定义参数值
  • 可变参数:收集任意多基于位置或关键字的参数
  • 可变参数解包:传递任意多的基于位置或关键字的参数
  • Keyword-only参数:参数必须按时名称传递

函数参数匹配表

语法 位置 解释
func(value) 调用者 常规参数:通过位置进行匹配
func(name=value) 调用者 关键字参数:通过变量名匹配
func(*sequence) 调用者 以name传递所有的对象,并作为独立的基于位置的参数
func(**dict) 调用者 以name成对的传递所有的关键字/值,并作用独立的关键字参数
def func(name) 函数 常规参数:通过位置或变量名进行匹配
def func(name=value) 函数 默认参数值,如果没有在调用中传递的话
def func(*name) 函数 匹配并收集(在元组中)所有包含位置的参数
def func(**name) 函数 匹配并收集(在字典中)所有包含key的参数
def func(*args, name) 函数 参数必须在调用中按照关键字传递
def func(*,name=value) 函数 同上,之所以有*,是为了避免歧义(当成默认参数)

如果决定使用并混合特定的参数匹配模型,Python将会遵循下面有关顺序的法则。

  • 在函数调用中,参数必须以此顺序出现:任何位置参数(value),后面跟着任何关键字参数(name=value)和sequence形式的组合,后面跟着*dict形式。
  • 在函数头部,参数必须以此顺序出现:任何一般参数(name),紧跟着任何默认参数(name=value),如果有的话,后面是name(在Python3.0中是)的形式,后面跟着任何name或name=value keyword-only参数(python3.0中),后面跟着**name形式。

Python内部是使用以下的步骤来赋值前进行参数匹配的:

  1. 通过位置分配非关键字参数。
  2. 通过匹配变量名分配关键字参数。
  3. 其他额外的非关键字参数分配到*name元组中。
  4. 其他额外的关键字参数分配到**name字典中。
  5. 用默认值分配给在头部未得到分配的参数。

在python3.0中apply已经废弃,apply(func,pargs,kargs) 替换成 func(pargs,*kargs)

第19章 函数的高级话题

函数设计的指导方针:

  • 耦合性:对于输入使用参数并且对于输出使用return语句。
  • 耦合性:只有在真正必要的情况下使用全局变量。
  • 偶尔性:不要改变可变类型的参数,除非调用者希望这样做。
  • 聚合性:每一个函数都应该有一个单一的、统一的目标。
  • 大小:每一个函数应该相对较小。
  • 耦合:避免直接改变在另一个模块文件中的变量。

函数对象:属性和注解
函数内省:
内省工具允许我们探索实现细节–例如,函数已经附加了代码对象,代码对象提供了函数的本地变量和参数等方面的细节:

>>> def func(a):
...     b = 'spam'
...     return b * a
...
>>> func.__name__
'func'
>>> func.__code__
<code object func at 0x10a2f9eb0, file "<stdin>", line 1>
>>> dir(func.__code__)
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
>>> func.__code__.co_varnames
('a', 'b')
>>> func.__code__.co_argcount
1

工具管理者可以利用这些信息来管理函数。

函数属性
函数对象不仅有系统定义的属性,我们也可以向函数附加任意的用户定义的属性:

>>> func.count = 0
>>> func.count +=1
>>> func.count
1

这样的属性可以用来直接把状态信息附加到函数对象,而不必使用全局、非本地和类等其他技术,从某种意义上讲,这很像其它语言的”静态本地变量“。

Python3.0中的函数注解
注解进一步使函数头部语法通用化。
对于参数,它们出现在紧随参数名之后的冒号之后;对于返回值,它们编写于紧跟在参数列表之后的一个->之后。当提供了注解的时候,Python将它们收集到字典中并且将它们附加给函数对象自身。

>>> def func(a:'spam', b:(1,10), c:float) -> int:
...     return a+b+c
...
>>> func.__annotations__
{'return': <class 'int'>, 'a': 'spam', 'c': <class 'float'>, 'b': (1, 10)}
>>>

匿名函数:lambda
lambda的一般形式是关键字lambda,之后是一个或多个参数(与一个def头部内用括号括起来的参数列表极其相似),紧跟的是一个冒号,之后是一个表达式:
lambda argument1,argument2,… argumentN: expression using arguments。
lambda与def创建的函数区别:

  • lambda是一个表达式,而不是一个语句。
  • lambda的主体是一个单个的表达式,而不是一个代码块。

第20章 迭代和解析,第二部分

列表解析把任意一个表达式而不是一个函数应用于一个迭代对象中的元素。
使用map这种函数式编程工具,可以把一个函数映射遍一个序列,列表解析把一个表达式映射一个序列。

>>> res = list(map(ord,'spam'))
>>> res
[115, 112, 97, 109]
>>> res = [ord(x) for x in 'spam']
>>> res
[115, 112, 97, 109]
>>>
>>>

效率对比:
列表解析>map>for循环

重访迭代器:生成器
有两种语言结构尽可能地延迟结果创建。

  1. 生成器函数:编写为常规的def语句,但是使用yield语句一次返回一个结果,在每个结果之间挂起和继续它们的状态。
  2. 生成器表达式类似于列表解析,但是,它们返回按需要产生结果的一个对象,而不是构建一个结果列表。

生成器函数:yield VS return
和返回一个值并退出的常规函数不同,生成器函数自动在生成值的时刻挂起并继续函数的执行。因此,它们对于提前计算整个一系列值以及在类中手动保存和恢复状态都很有用。
生成器函数和常规函数之间的主要的代码不同之处在于,生成器yields一个值,而不是返回一个值。yield语句挂起该函数并向调用者发送回一个值,但是,保留足够的状态以使得函数能够从它离开的地方继续。

函数包含一条yield语句后,该语句特别编译为生成器。当调用时,它们返回一个迭代器对象,该对象支持用next的自动创建的方法来继续执行的接口。从调用者角度来看,生成器的next方法继续函数并且运行到下一个yield结果返回或引发一个StopIteration异常。

扩展生成器函数的协议:send和next
yield现在是一个表达式的形式,可以返回传入的元素来发送,而不是一个语句[yield X或者A = (yield X)]。表达式必须包含在括号中,除非它是赋值语句右边的唯一一项。例如,X = yield Y没问题,就如同X = (yield Y)+42。

当使用这一额外的协议时,值可以通过调用G.send(value)发送给一个生成器G。之后恢复生成器的代码,并且生成器中的yield表达式返回了为了发送而传入的值。如果提前调用了正常的G.next方法,yield返回None。

>>> def gen():
...     for i in range(10):
...             x = yield i
...             print(x)
...
>>> G = gen()
>>> next(G)
0
>>> G.send(77)
77
1
>>> G.send(88)
88
2
>>> next(G)
None
4

生成器表达式:迭代器遇到列表解析
从语法上讲,生成器表达式就像一般的列表解析一样,但是它们是括在圆括号中而不是方括号中的。

>>> [x ** 2  for x in range(4)]
[0, 1, 4, 9]
>>> ( x ** 2 for x in range(4))
<generator object <genexpr> at 0x1046f75c8>

生成器是单迭代器对象

用遵守迭代协议的类来实现任意的用户定义的生成器对象。
这样的类定义一个特别的iter方法,它由内置的iter函数调用,将返回一个对象,
该对象有一个next方法,该方法由next内置函数调用(一个getitem索引方法作为迭代的退而求其次的选项也是可以的)。

被赋值的变量X在函数内部是当作本地变量名对待的,而不是仅仅在赋值语句以后的语句中才被当作本地变量

>>> X = 99
>>> def selector():
...     print(X)
...     X = 88
...
>>> selector()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in selector
UnboundLocalError: local variable 'X' referenced before assignment
>>>

在函数中,不可能同时使用同一个简单变量名的本地变量和全局变量。如果真的是希望打印全局变量,并在之后设置一个有着相同变量名的本地变量,导入上层的模块,并使用模块的属性标记来获得其全局变量。

>>> X = 99
>>> def selector():
...     import __main__
...     print(__main__.X)
...     X = 88
...     print(X)
...
>>> selector()
99
88

默认参数和可变对象
默认参数是在def语句运行时评估并保存的,而不是在这个函数调用时。从内部来讲,Python会将每一个默认参数保存成一个对象,附加在这个函数本身。这是有用的,他可以利用作用域规则的LEGB中的E,把E中的变量值保存下来。如果没有理解到默认参数是在def语句运行时评估并保存的,而不是在这个函数调用时,很可能发生下面的错误使用。

>>> def saver(x=[]):
...     x.append(1)
...     print(x)
...
>>> saver([2])
[2, 1]
>>> saver()
[1]
>>> saver()#可能你期待的结果是[1], 但因为默认参数是在def语句运行时评估并保存的,事实上会保留一次的值,并且一直追加下去。
[1, 1]
>>> saver()
[1, 1, 1]

第五部分 模块

第21章 模块:宏伟蓝图

每一个文件都是一个模块,并且模块导入其他模块之后就可以使用导入模块定义的变量名。模块可以由两个语句和一个重要的内置函数进行处理。
import 使客户端(导入者)以一个整体获取一个模块。
from 允许客户端从一个模块文件中获取特定的变量名。
imp.reload 在不中止Python程序的情况下,提供了一种重新载入模块文件代码的方法。

模块的三个角色:

  • 代码重用
  • 系统命名空间的划分
  • 实现共享服务和数据

如何组织一个程序

import如何工作
在Python中,导入并非只是把一个文件文本插入另一个文件而已,导入其实是运行时的运算,程序第一次导入指定文件时,会执行三个步骤。

  1. 找到模块文件。
  2. 编译成位码(需要时)。遍历模块搜索路径,找到符合import语句的源代码文件后,有必要的话,Python将其编译成字节码。
  3. 执行模块的代码来创建其所定义的对象。

搜索模块文件的路径sys.path:

  1. 程序的主目录。包含程序的顶层脚本文件的目录。
  2. PYTHONPATH目录(如果已经进行了设置)。
  3. 标准链接库目录。
  4. 任何.pth文件的内容(如果存在的话)。

模块文件选择
import b形式的import叙述可能会加载:

  • 源代码文件b.py。
  • 字节码文件b.pyc。
  • 目录b,包导入。
  • 编译扩展模块(通常用C/C++编写),导入时使用动态连接(例如,Linux的b.so以及Cygwin和Windows的b.dll或b.pyd)。
  • 用C编写的编译好的内置模块,并通过静态连接至Python。
  • ZIP文件组件,导入时会自动解压缩。
  • 内存内映像,对于frozen可执行文件。
  • Java类,在Jython版本的Python中。
  • .Net组件,在IronPython版本中的Python中。

第22章 模块代码编写基础

import vs from
import使一个变量名引用整个模块对象,我们必须通过模块名称来得到该模块的属性(例如,module1.printer)。
from会把变量名复制到另一个作用域,所以它就可以让我们直接在脚本中使用复制后的变量名,而不需要通过模块(例如,printer)。
from *则会取得模块顶层所有赋了值的变量名的拷贝。

从概念上讲,一个像这样的from语句:
from module import name1, name2
与下面这些语句是等效的:
import module
name1 = module.name1
name2 = module.name2
del module

import和from是赋值语句
和def一样,import和from是可执行的语句,而不是编译期间的声明,而且它们可以嵌套在if测试中,出现在函数def之中等,直到执行程序时,python执行到这些语句,才会进行解析。
和def一样,import和from都是隐性的赋值语句。

  • import 将整个模块对象赋值给一个变量名。
  • from将一个或多个变量名赋值给另一个模块中同名的对象。

模块命名空间
模块就是命名空间(变量名建立所在的场所),而存在于模块之内的变量名就是模块对象的属性。

文件生成命名空间

  • 模块语句会在首次导入时执行。系统中,模块在第一次导入时无论在什么地方,Python都会建立空的模块对象,并逐一执行该模块文件内的语句,依照文件从头到尾的顺序。
  • 顶层的赋值语句会创建模块属性。
  • 模块的命名空间能通过属性__dict__或dir(M)获取。
  • 模块是一个独立的作用域(本地变量就是全局变量)。

reload基础
与import和from不同的是:

  • reload是Python中的内置函数,而不是语句。
  • 传给reload的是已经存在的模块对象,而不是变量名。
  • reload是Python3.0中位于模块之中,并且必须导入自己。

reload细节:

  • reload会在模块当前命名空间内执行模块文件的新代码。重新执行模块文件的代码会覆盖其现有的命名空间,并非进行删除而进行重建。
  • 文件中顶层赋值语句会使得变量名换成新值。
  • 重载会影响所有使用import读取了模块的客户端。
  • 重载只会对以后使用from的客户端造成影响。之前使用from来读取属性的客户端并不会受到影响,那些客户端引用的依然是重载前所取出的旧对象。

第23章 模块包

包导入是把计算机上的目录变成另一个Python命名空间,而属性则对应于目录中所包含的子目录和模块文件。
import dir1.dir2.mod
from dir1.dir2.mod import x
上面的语句表明了机器上有个目录dir1,而dir1里有子目录dir2,而dir2内包含有一个名为mod.py(或类似文件)的模块文件。此外,这些导入意味着,dir1位于某个容器目录dir0中,这个目录可以在Python模块搜索路径中(sys.path)找到.

__init__.py包文件
包导入语句的路径中的每个目录内都必须有__init__.py这个文件,否则导入包会失败。

__init__.py可以防止有相同名称的目录不小心隐藏在模块搜索路径中,而之后才出现真正所需要的模块。
__init__.py可以完全是空的,也可以包含Python程序代码。更通常的情况下,__init__.py文件扮演了包初始化的钩子、替目录产生模块命名空间以及使用目录导入时实现from *(也就是from … import *)行为的角色。

  • 包的初始化。Python首次导入某个目录时,会自动执行该目录下__init__.py文件中的所有程序代码。
  • 模块命名空间的初始化。在包导入的模型中,脚本内的目录路劲,在导入会后变成真实的嵌套对象路径。这类文件为目录(没有实际相配的模块文件)所创建的模块对象提供了命名空间。路径中的每个目录名称会变成赋值了模块对象的变量,而模块对象的命名空间是由该目录下的__init__.py文件中所有赋值语句进行初始化的。
  • from*语句的行为。作为一个高级功能,你可以在__init__.py文件内使用__all__列表来定义目录以from*语句形式导入时,需要导出什么。在__init__.py文件中,__all__列表是指当包名称使用from *的时候,应该导入的子模块的名称清单。如果没有设定__all__,from*语句不会自动加载嵌套于该目录内的子模块。取而代之的是,只加载该目录的__init__.py文件中赋值语句定义的变量名,包括该文件中程序代码明确导入的任何子模块。

就像模块文件一样,任何已导入的目录也可以传递给reload,来强制该项目重新执行。

包相对导入
Python2.6首先在导入上隐式地搜索包目录,而Python3.0需要显示地相对导入语法。

相对导入基础知识
from语句现在可以使用前面的点号(“.”)来指定,它们需要位于同一包中的模块(所谓的包相对导入),而不是位于模块导入搜索路径上某处的模块(叫做绝对导入),也就是说:

  • 在Python3.0和Python2.6中,我们可以使用from语句前面的点号来表示,导入应该相对于外围的包–这样的导入将只是在包的内部搜索,并且不会搜索位于导入搜索路径(sys.path)上某处的同名模块。
  • 在Python2.6中,包的代码中的常规导入(没有前面的点号),目前默认一种先相对再绝对的搜索路径顺序,也就是说,它们首先搜索包自己的路径。然而,在Python3.0中,在一个包中导入默认是绝对的–在缺少任何特殊的点语法的时候,导入忽略了包含包自身并在sys.path搜索路径上的某处查找。

导入扔然是相对于CWD的

第24章 高级模块话题

在Python中,模块内的数据隐藏是一种惯例,而不是一种语法约束。

最小化from*的破坏:_X 和__all__
通过在变量名前面加_, 可以防止客户端使from*语句导入模块名时,把其中的那些变量名复制出去。
在模块顶层把变量名的字符串列表赋值给变量__all__,使用些功能时,from*语句只会把列在__all__列表中的这些变量名复制出来。

启用以后的语言特性
开启以后的语言特性,可以使用像以下形式的特定的import语句:
python
from future import featurename

混合用法模式: namemain__
每个模块都有个名为__name
的内置属性,Python会自动设置该属性:

  • 如果文件是以顶层程序文件执行,在启动时,__name__就会设置为字符串”__main__”
  • 如果文件被导入,__name__就会改设为客户端所了解的模块名。

修改模块搜索路径
模块搜索路径是一个目录列表,可以通过环境变量PYTHONPATH以及可能的.pth路径文件进行定制。
sys.path在程序启动后就会进行初始化,但在那之后,可以随意对其元素进行删除、附加和重设。

Import语句和from语句的as扩展
import和from语句都可以扩展,让模块可以在脚本中给予不同的变量名。下面的import语句:

import modulename as name

相当于:
python
import modulename
name = modulename
del modulename

模块是对象:元程序
因为模块通过内置属性显示了它们的大多数有趣的特性,因此,可很容易地编写程序来管理其他程序。
我们通常称这类管理程序为元程序(metaprogram),因为它们是在其他系统之上工作。
这也称为内省(introspection),因为程序能看见和处理对象的内部。

如下所有的表达式都会得到相同的属性和对象。

M.name
M.__dict__['name']
sys.modules['M'].name
getattr(M,'name')

用名称字符串导入模块

  1. 利用exec执行python语句来动态导入。
    python
    >>> exec("import " + modname)
    >>> string
    <module 'string' from '/usr/local/Cellar/python/2.7.12_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/string.pyc'>
    >>>
  2. 利用__import__函数来导入
>>> modname = "string"
>>> string = __import__(modname)
>>> string
<module 'string' from '/usr/local/Cellar/python/2.7.12_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/string.pyc'>
>>>

区别:
exec每次都会编译import语句。而import返回模块对象,需要用一个变量名来保存下。

过渡性模块重载
当我们重载一个模块时,Python只重新载入特殊模块的文件,它不会自动重载那些为了导入要重载文件的模块。
例如,如果要重载某个模块A,并且A导入了模块B和C,重载只适用于A,而不适用于B和C。
如果希望模块B和C也能自动重载,可以自己写个小工具,利用模块的dict和types模块。遍历dict获取属性,如果是types.ModuleType,则也reload下,为了避免同一个模块多次reload,可以用个dict存储下reload过的模块。

模块设计理念

  • 总是在Python的模块内编写代码。
  • 模块耦合要降到最低:全局变量。
  • 最大化模块的黏合性:统一目标。
  • 模块应该少去修改其他模块的变量。

顶层代码的语句次序的重要性

  • 在导入时,模块文件顶层的程序代码(不在函数内)一旦Python运行时,就会立刻执行。因为,该语句是无法引用文件后面位置赋值的变量名。
  • 位于函数主体内的代码直到函数被调用后才会运行。因为函数内的变量名在函数实际执行前都不会解析,通常可以引用文件内任意地方的变量。

几个陷进

  1. from复制变量名,而不是连接。
  2. from * 会让变量语义模糊。
  3. reload不会影响from导入。
  4. reload、from以及交互模式测试。reload不会影响之后from导入的,要更新的的话,必须重新调用from。
  5. 递归形式的from导入无法工作。解决办法是:
    1. 小心设计,通常可以避免这种导入循环
    2. 如果无法完全断开循环,就要使用import和点号运算(而不是from),将模块变量名的读取诉到后边,要么就是在函数中,或者在文件末尾附近去执行from(而不是在模块顶层),以延迟其执行。

第六部分

第25章 OOP:宏伟蓝图

属性继承搜索
Python中大多数OOP的故事,都可简化成这个表达式:
object.attribute
当类启用时,上边的Python表达式实际上等于下列自然语言。
找出attribute首次出现的地方,先搜索object,然后是该对象之上的所有类,由下至上,由左至右。

编写类树

  • 每个class语句会生成一个新的类对象。
  • 每次类调用时,就会生成一个新的实例对象。
  • 实例自动连接至创建了这些实例的类。
  • 类连接至其超类的方式是,将超类列在类头部的括号内。其从左至右的顺序会决定树中的次序。

类和属性:

  • 属性通常是在class语句中通过赋值语句添加在类中,而不是嵌入在函数的def语句内。
  • 属性通常是在类内,对传给函数的特殊参数(也就是self),做赋值运算而添加在实例中的。

每次从类产生实例时,Python会自动调用名为__init__的方法。

第26章 类代码编写基础

类产生多个实例对象
类对象和实例对象。类对象提供默认行为,是实例对象的工厂。实例对象是程序处理的实际对象:各自都有独立的命名空间,但是继承(可自动存取)创建该实例的类中的变量名。类对象来自于语句,而实例来自于调用。

类对象提供默认行为
执行class语句,就会得到类对象。以下是Python类主要特性的要点。

  • class语句创建类对象并将其赋值给变量名。
  • class语句内的赋值语句会创建类的属性。
  • 类属性提供对象的状态和行为。

实例对象是具体的元素
当调用类对象时,我们得到了实例对象。以下是类的实例内含的重要概要。

  • 像函数那样调用类对象会创建新的实例对象。
  • 每个实例对象继承类的属性并获得了自己的命名空间。
  • 在方法内对self属性做赋值运算会产生每个实例自己的属性。

Python的继承:继承是在属性点号运算时发生的,而且只与查找连接对象内的变量名有关。

就像Python中的其他事物,实例属性(有时被称作成员)并没有声明。首次赋值后,实例就会存在,就像简单的变量。

类通常是通过对self参数进行赋值运算从而建立实例的所有属性的,但不是必须如此。程序可以取出、修改或创建其所引用的任何对象的属性。

在Python中,实例从类中继承,而类继承于超类。以下是属性继承机制的核心观点。

  • 超类列在了类开头的括号中。
  • 类从其超类中继承属性。当读取属性时,如果它不存在于子类中,Python会自动搜索这个属性。
  • 实例会继承所有可读取类的属性。
  • 每个object.attribute都会开启新的独立搜索。
  • 逻辑的修改是通过创建子类,而不是修改超类。

类是模块内的属性

类可以截获Python运算符
以下是重载运算符主要概念的概要。

  • 以双下划线命名的方法(__X__)是特殊钩子。
  • 当实例出现在内置运算时,这类方法会自动调用。
  • 类可覆盖多数内置类型运算符。
  • 运算符覆盖方法没有默认值,而且也不需要。
  • 运算符可让类与Python的对象模型相集成。

每个实例都连接至其类以便于继承,如果你想看的话,这个连接叫做__class__
类也有一个__bases__属性,它是其超类的元组。

类和实例只是命名空间对象,属性是通过赋值语句动态建立。
Python中的OOP其实就是在已连接命名空间对象内寻找属性而已。

第27章 更多实例

在Python中,模块名使用小写字母开头,而类名使用一个大写字母开头,这是通用的惯例;就好像方法中使用self作为参数名,这可能不是语言所要求的,但是,这种违背惯例的做法很可能让随后阅读你的代码的人搞混淆了。

如下的常规方法调用:
instance.method(args…)
由Python自动地转换为如下的同等形式:
class.method(instance,args…)

在Python3.0中,像__str____getitem__这样的内置操作无法通过通用的属性管理器找到它们的隐式属性:__getattr__(针对未定义的属性运行)及其近亲__getattribute__(针对所有属性运行)都不会调用。

利用内省工具可以去掉硬编码:

  • 内置的instance.__class__属性提供了一个从实例到创建它的类的链接。类反过来有一个__name__,还有一个__bases__序列,提供了超类的访问。我们使用这些来打印创建一个实例的类的名字,而不是通过硬编码来做到。
  • 内置的object.__dict__属性提供了一个字典,带有一个键/值对,以便每个属性都附加到一个命名控制对象(包括模块、类和实例)。由于它是字典,因此我们可以获取键的列表、按时键来索引、迭代其键,等等,从而广泛地处理所有的属性。我们使用这些来打印出任何实例的每个属性,而不是在定制显示中编码。

实例与类属性的关系
继承的类属性只是附加到了类,而没有向下复制到实例。
有两种方式可以拿到向上继承类的属性:

  1. 通过__class__连接,拿到类的__dict__,再通过__bases__拿到超类,再拿超类的__dict__
  2. 直接利用dir函数,dir函数的结果列表中包含了继承的名称。

有两种惯例用法来避免方法被重载

  1. 方法前加_,如_X,这是命名惯例。
  2. 方法前加__,如__X,Python会自动扩展这样的名称,以包含类的名称,从而使它们变成真正的唯一。这一功能通常叫做伪私有类属性

对象持久化通过3个标准的库模块来实现,这3个模块在Python中都可用:

  • pickle:任意Python对象和字符串之间的序列化
  • dbm(在Python2.6中叫做anydbm):实现一个可通过键访问的文件系统,以存储字符串。
  • shelve:使用另两个模块按照键把Python对象存储到一个文件中。

第28章 类代码编写细节

Python的class并不是声明式的。就像def一样,class语句是对象的创建者并且是一个隐含的赋值运算–执行时,它会产生类对象,并把其引用值存储在前面所使用的变量名。就像def一样,class语句也是真正可执行代码。

在class语句内,任何赋值语句都会产生类属性,而且还有特殊名称方法重载运算符。
类几乎就是命名空间。当Python执行class语句(不是调用类),会从头至尾执行其主体内的所有语句。在这个过程中,进行的赋值运算会在这个类作用域中创建变量名,从而成为对应的类对象内的属性。
因此,类就像模块和函数:

  • 就像函数一样,class语句是本地作用域,由内嵌的赋值语句建立的变量名,就存在于这个本地作用域内。
  • 就像模块内的变量名,在class语句内赋值的变量名会变成类对象的属性。

类的主要的不同之处在于其命名空间也是Python继承的基础。在类或实例对象中找不到的所引用的属性,就会从其他类中获取。

对实例的属性进行赋值运算会在该实例内创建或修改变量名,而不是在共享的类中。
通常的情况下,继承搜索只会在属性引用时发生,而不是在赋值运算时发生:对对象属性进行赋值总是会修改该对象,除此之外没有其他的影响。

方法一般是通过实例调用的。不过,如果要保证子类的构造函数也会执行超类构造时的逻辑,一般都必须通过类明确地调用超类的__init__方法。

Python2.6和Python3.0的抽象超类
方法1:在需要由子类重载的方法中用assert或者raise NotImplementedError异常来指明子类必须重载。
方法2:特殊语法
在Python3.0中,我们在一个class头部使用一个关键字参数,以及特殊的@装饰器语法。

>>> from abc import ABCMeta,abstractmethod
>>> class Super(metaclass=ABCMeta):
...     @abstractmethod
...     def method(self):
...             pass
...
>>>

在Python2.6中,我们使用了一个类属性:

>>> from abc import ABCMeta,abstractmethod
>>> class Super:
...     __metaclass__ = ABCMeta
...     @abstractmethod
...     def method(self):
...             pass
...
>>>

当子类没有重载抽象方法时不能实例化的。

命名空间总结:

  • 无点号运算的变量名(例如,X)与作用域相对应。
  • 点号的属性名(例如,object.X)使用的是对象的命名空间。
  • 有些作用域会对对象的命名空间进行初始化(模块和类)。

Python命名空间的禅:赋值瘵变量名分类
在Python中,赋值变量的场所相当重要:这完全决定了变量名所在的作用域或对象。

>>> X = 11 #全局变量
>>> def f():
...     print(X) #引用全局变量
...
>>> def g():
...     X = 22 #本地变量
...     print(X) #引用本地变量
...
>>> class C:
...     X = 33 ##类对象属性
...     def m(self):
...             X = 44 #方法本地变量
...             self.X = 55 #实例对象属性
...
>>>

命名空间字典
模块的命名空间实际上是以字典的形式实现的,并且可以由内置属性__dict__显示这一点。类和实例对象也是如此:属性点号运算其实内部就是字典的索引运算,而属性继承其实就是搜索链接的字典而已。

类与模块的关系

  • 模块:是数据/逻辑包;通过编写Python文件或C扩展来创建;通过导入来使用。
  • 类:实现新的对象;由class语句来创建;通过调用来使用。总是位于一个模块中。

第29章 运算符重载

“运算符重载”只是意味着在类方法中拦截内置的操作–当类的实例出现在内置操作中,Python自动调用你的方法,并且你的方法的返回值变成了相应操作的结果。以下是对重载的关键概念的复习:

  • 运算符重载让类拦截常规的Python运算。
  • 类可重载所有Python表达式运算符。
  • 类也可重载打印、函数调用、属性点号运算等内置运算。
  • 重载使类实例的行为像内置类型。
  • 重载是通过提供特殊名称的类方法来实现的。

构造函数和表达式:init和__sub__
__init__是构造函数。__sub__重载”-“减法。

常见的运算符重载方法

方法 重载 调用
__init__ 构造函数 对象建立:X = Class(args)
__del__ 析构函数 X对象回收
__add__ 运算符+ 如果没有_iadd_,X+Y,X += Y
__or__ 运算符 (位OR)
__repr__,__str__ 打印、转换 print(X)、repr(X)、str(X)
__call__ 函数调用 X(*args,**kargs)
__getattr__ 点号运算 X.undefined
__setattr__ 属性赋值语句 X.any = value
__delattr__ 属性删除 del X.any
__getattribute__ 属性获取 X.any
__getitem__ 索引运算 X[key],X[i:j],没有__iter__时的for循环和其他迭代器
__setitem__ 索引赋值语句 X[key] = value, X[i:j] = sequence
__delitem__ 索引和分片删除 del X[key], del X[i:j]
__len__ 长度 len(X),如果没有__bool__,真值测试
__bool__ 布尔测试 bool(X),真测试(在Python2.6中叫做__nonzero__
__lt__,__gt__,__le__,__ge__,__eq__,__ne__ 特定的比较 X<Y,X>Y,X<= Y,X>=Y,X==Y,X!=Y(在Python2.6中,只有__cmp__)
__radd__ 右侧加法 Other+X
__iadd__ 实地(增加的加法 X += Y (or else __add__
__iter__,__next__ 迭代环境 I=iter(X), next(I);for loops, in if no __contains__, all comprehensions,map(F,X),其他(__next__在Python2.6中称为next)
__contains__ 成员关系测试 item in X (任何可迭代的)
__index__ 整数值 hex(X),bin(X), oct(X), O[X], O[X:](替代Python2中的__oct____hex__)
__enter__,__exit__ 环境管理器 with obj as var:
__get__,__set__,__delete__ 描述符属性 X.attr, X.attr = value, del X.attr
__new__ 创建 __init__之前创建对象

索引与分片:__getitem____setitem__
如果在类中定义了(或继承了)的话,则对于实例的索引运算,会自动调用__getitem__.

拦截分片
除了索引,对于分片表达式也调用getitem。正式地讲,内置类型以同样的方式处理分片。
正式地讲,内置类型以同样的方式处理分片。

>>> class Indexer:
...     data = [5,6,7,8,9]
...     def __getitem__(self,index):
...             print('getitem:',index)
...             return self.data[index]
...
>>> X = Indexer()
>>> X[0]
('getitem:', 0)
5
>>> X[2:4]
('getitem:', slice(2, 4, None))
[7, 8]
>>> X[slice(None,None,2)]
('getitem:', slice(None, None, 2))
[5, 7, 9]
>>>

__getitem__的第二个参数,可能是整数,也可能是分片对象。
在Python2.6中有专门的__getslice____setslice__,并且他们的优先级比__getitem____setitem__高,不过他们已经在Python3.0中移除掉了。

索引迭代:__getitem__
for语句的作用是从0到更大的索引值,重复对序列进行索引运算,直到检测到超出边界的异常。
任何会响应索引运算的内置或用户定义的对象,同样会响应迭代。

迭代对象:__iter____next__
Python中所有的迭代环境都会先尝试__iter__方法,再尝试__getitem__

从技术上讲,迭代环境是通过调用内置函数iter去尝试寻找__item__方法来实现的,而这种方法应该返回一个迭代器对象。如果已经提供了,Python就会重复调用这个迭代器对象的next方法,直到发生StopIteration异常。如果没有找到这类__iter__方法,Python会改用__getitem__机制,就像之前那样通过偏移量重复索引,直到引发IndexError异常。

有多个迭代器的对象
要达到多个迭代器的效果,__iter__只需替迭代器定义新的状态对象,而不是返回self。

成员关系:__contains____iter____getitem__
在迭代领域,类通常把in成员关系运算符实现为一个迭代,使用__iter__方法或__getitem__方法。
要支持更加特定的成员关系,类可能编写一个__contains__方法–当出现的时候,该方法优先于__iter__方法,__iter__方法优先于__getitem__方法。

属性引用:__getattr____setattr__
__getattr__方法是拦截属性点号运算。更确切地说,当通过对未定义(不存在)属性名称和实例进行点号运算时,就会用属性名称作为字符串调用这个方法。如果Python可通过继承树搜索流程找到这个属性,该方法就不会被调用。因为有这种情况,所以__getattr__可以做为钩子来通过通用的方式响应属性请求。如:

>>> class empty:
...     count=0
...     def __getattr__(self,attrname):
...             if attrname == 'age':
...                     return 40
...             else:
...                     raise AttributeError,attrname
...
>>>
>>> X = empty()
>>> X.count
0
>>> X.age
40
>>> X.name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __getattr__
AttributeError: name
>>>

__setattr__会拦截所有属性的赋值语句。如果定义了这个方法,self.attr = value会变成self.setattr(‘attr’, value)。
如果要使用这个方法,要确定是通过对属性字典做索引运算来赋值任何实例属性的。也就是说,是使用self.__dict__['name'] = x,而不是self.name = x(会导致无限循环).

模块实例属性的私有性:第一部分
如下示例可以模拟私有性,让每个子类都有自己的私有变量名列表,这些变量名无法通过其实例进行赋值(私有并不绝对,因为可以通过dict直接赋值。)

另有类装饰器可以更加通用地拦截和验证属性。即使私有性可以模拟,但实际应用中几乎不会这么做。

__repr____str__会返回字符串表达式

  • 打印操作会首先尝试__str__和str内置函数(print运行的内部等价形式)。它通常应该返回一个用户友好的显示。
  • __repr__用于所有其他的环境中:用于交互模式下提示回应以及repr函数,如果没有使用__str__,会使用print和str。它通常应该返回一个编码字符串,可以用来重新创建对象,或者给开发者一个详细的显示。

根据一个容器的字符串转换逻辑,__str__的用户友好的显示可能只有当对象出现在一个打印操作顶层的时候才应用,嵌套到较大对象中的对象可能用其__repr__或默认方法打印。

右侧加法和原处加法:__radd____iadd__
只有当+右侧的对象是类实例,而左边对象不是类实例时,Python才会调用__radd__。在其他所有情况下,则由左侧对象调用__add__方法。

为了实现+=原处扩展相加,编写一个__iadd____add____iadd__更加高效,一般返回对象自身。

Call表达式:__call__
如果定义了__call__,Python就会为实例 应用函数调用表达式 运行__call__方法。这样可以让类实例的外观和用法类似于函数。

函数接口和回调代码
利用实例对象保存属性,然后实现__call__把实例对象像函数一样调用,这可能是Python语言中保留状态信息的最好方式,比之前针对函数所讨论的技术更好(全局变量、嵌套函数作用域引用以及默认可变参数等)。

比较:__lt____gt__和其他方法
比较运算符有如下限制:

  • 与前面讨论的__add__/__radd__对不同,比较方法没有右端形式。相反,当只有一个运算数支持比较时,使用其对应方法(例如,__lt____gt__互为对应)。
  • 比较运算符没有隐式关系。例如,==并不意味着!=是假的,因此,__eq____ne__应该定义为确保两个运算符都正确地作用。
  • 在Python2.6中,还有__cmp__方法。它返回一个小于、等于或大于0的数,以表示比较其两个参数的结果。__cmp__在Python3.0中已删除。

布尔测试:__bool____len__
在布尔环境中,Python首先尝试__bool__来获取一个直接的布尔值,然后,如果没有该方法,就尝试__len__类根据对象的长度确定一个真值。

对象析构函数:__del__
每当实例产生时,就会调用__init__构造函数。每当实例空间被收回时(在垃圾收集时),它的对立面__del__,也就是析构函数,就会自动执行。

基于两个原因,在Python中,析构函数不像其他OOP语言那么常用。

  1. 因为Python在实例收回时,会自动收回该实例所拥有的所有空间,对于空间管理来说,是不需要析构函数的。
  2. 无法轻易地预测实例何时收回,通常最好是在有意调用的方法中(或者try/finally语句)编写代码去终止活动。

第30章 类的设计

Python的OOP实现可以概括为三个概念,如下所示。

  • 继承。继承是基于Python中的属性查找的(在X.name表达式中)。
  • 多态。在X.method方法中,method的意义取决于X的类型(类)。
  • 封装。方法和运算符实现行为,数据隐藏默认为是一种惯例。

OOP和委托:“包装”对象
在Python中,委托通常是以__getattr__钩子方法实现的,因为这个方法会拦截对不存在属性的读取,包装类(有时称为代理类)可以使用getattr把任意读取转发给被包装的对象。

变量名压缩概览
变量名压缩的工作方式:class语句内开头有两个下划线,但结尾没有两个下划线的变量名,会自动扩张,从而包含了所在类的名称。例如,像Spam类内__X这样的变量名会自动变成_Spam__X:原始的变量名会在头部加入一个下划线,然后是所在类名称。

Python更关心的是开放而不是约束,这意味着你愿意的话,可以随便的破坏代码

为什么使用伪私有属性

在这个例子中,X是个容易混淆的属性。C1不知道C2和C3在用,C2也不知道C1和C3在用,这里有3个类在使用X,但只存在1个X属性,即I.X。

解决的办法就是用伪私有属性。

方法是对象:绑定或无绑定
方法也是一种对象,并且可以用与其他对象大部分相同的方式来广泛地使用–可以对它们赋值、将其传递给函数、存储在数据结构中,等等。
由于类方法可以从一个实例或一个类访问,它们实际上在Python中有两种形式。

  • 无绑定类方法对象:无self。通过对类进行点号运算从而获取类的函数属性,会传回无绑定(unboud)方法对象(即self参数没有绑定实例对象)。调用该方法时,必须明确提供实例对象作为第一个参数。
  • 绑定实例方法对象:self+函数对。通过对实例进行全运算从而获取类的函数属性,会传回绑定(bound)方法对象(即self参数绑定了实例对象)。Python在绑定方法对象中自动把实例和函数打包,所以,不用传递实例去调用该方法。

在Python3.0中,无绑定方法是函数
在Python3.0中,删除了无绑定方法,把它当成了一个简单函数对待。

绑定方法和其他可调用对象
就像简单函数一样,绑定方法可以任意地在一个程序中传递。

和简单函数一样,绑定方法对象拥有自己的内省信息,包括让它们配对的实例对象和方法函数访问的属性。

类也属于可调用对象的范畴,但是,我们通常调用它们产生实例而不是做实际的工作。

为什么要在意:绑定方法和回调函数
因为绑定方法会自动让实例和类方法函数配对,因此可以在任何希望得到简单函数的地方使用。
另外类通过重载__call__方法可以达到同样的目的。

多重继承:”混合“类
搜索属性时:

  • 在传统类中(默认的类,直到Python3.0),属性搜索处理对所有路径深度优先,直到继承树的顶端,然后从左到右进行。
  • 要新式类(以及Python3.0的所有类中),属性搜索处理沿着树层级、经更加广度优先的方式进行。

第31章 类的高级主题

扩展内置类型

  1. 组合。
  2. 继承。

从Python2.2起,所有内置类型现在都能直接创建子类。像list、str、dir以及tuple这些类型转换函数都变成内置类型的名称:虽然脚本看不见,但是类型转换调用其实主是启用了类型的对象构造函数。

新式类
在Python2.2中,引入一种新的类,称为”新式类“。

  • 对于Python3.0来说,所有的类都是我们所谓的”新式类“,不管它们是否显式地继承自object。所有的类都继承自object,不管显示的还是隐式的,并且,所有的对象都是object的实例。
  • 在Python2.6及其以前的版本中,类必须继承自的类看做是”新式”object(或者其他的内置类型),并且获得所有新式类的特性。

新式类变化

  • 类和类型合并 类现在就是类型,并且类型现在就是类。
  • 继承搜索顺序 先横向搜索再纵向搜索,并且先宽度优先搜索,再深度优先搜索。
  • 针对内置函数的属性获取 __getattr____getattribute__方法不再针对内置运算的隐式属性获取而运行。这意味着,它们不再针对__X__运算符重载方法名而调用,这样的名称搜索从类开始,而不是从实例开始。
  • 新的高级工具 新式类有一组新的类工具,包括slot、特性、描述符和__getattribute__方法。

类型模式变化
在Python3.0中,类就是类型,但是类型(type)也是类(class)。
从技术上讲,每个类都是由一个元类生成–元类是这样的一个类,它要么是type自身,要么是它定制来的扩展或管理生砀类的一个子类。

类型测试的隐含意义
在Python3.0中,类实例的类型直接而有意义地比较,并且以与内置类型对象同样的方式进行。

对于Python2.6或更早版本中的经典类,比较实例类型几乎是无用的。因为所有的实例都具有相同的”实例“类型(type ‘instance’)。要真正地比较类型,必须要比较实例的class属性。

新式类的扩展
类属性__slots__ 限制了实例属性:只有__slots__列表内的这些变量名可赋值为实例属性。
Slot对于Python的动态特性来说是一种违背,而动态特性要求任何名称都可以通过赋值来创建。

有些带有slots的实例也许根本 没有__dict__属性字典。
必须小心使用比__dict__更为存储中立的工具,例如getattr、setattr和dir内置函数,它们根据__dict____slots__存储应用于属性。在某些情况下,两种属性源代码都需要查询以确保完整性。

通过在__slots__中包含__dict__仍然可以容纳额外的属性,额外的属性存储到了__dict__字典中。

由于实例可能没有__slots__,也可能没有__dict__,为了通用地列出所有实例属性,可以考虑下面的代码:

需要注意一下:__slots__是限制实例对象只能用__slots__列表里的属性名设置属性,并不是说实例对象有这些属性,在用之前还是得先有赋值语句。

超类中的多个__slots__列表

  • 如果一个子类继承自一个没有__slots__的超类,那么超类的__dict__属性总是可以访问的,使得子类中的一个__slots__无意义。
  • 如果一个类定义了与超类相同的slot名称,超类slot定义的名称版本只有通过直接从超类获取其描述符才能访问。
  • 由于一个__slots__声明的含义受到它出现其中的类的限制,所以子类将有一个__dict__,除非它们也定义了一个__slots__。
  • 通常从列出实例属性这方面来讲,多类中的slots可能需要手动类树爬升、dir用法,或者把slot名称当做不同的名称领域的政策。
>>> class A:pass
...
>>> class B(A):
...     __slots__ = ['a']
...
>>> b = B()
>>> b.d = 'd' #由于超类A没有slots,所以类B中的__slots__并不限制对象b的属性设置。
>>> b.__dict__
{'d': 'd'}
>>> b.__slots__
['a']
>>>
>>> class A:
...     __slots__ = ['a','b']
...
>>> class B(A):
...     pass
...
>>> b = B()
>>> b.hello = 'go2live.cn'#由于类B并没有设置__slots__,__dict__属性是可用的。并不会因为超类A而对实例对象b产生影响。
>>> b.__dict__
{'hello': 'go2live.cn'}
>>>

要使slots真正起作用,必须子类和超类都有slots属性。

类特性
有一种称为特性(property)的机制,提供另一种方式让新式类定义自动调用的方法,来读取或赋值实例属性。他是__getattr____setattr__重载方法的替代做法。

特性的产生是以三种方法(获得、设置以及删除运算的处理器)以及通过文档字符串调用内置函数property。如果任何参数以None传递或省略,该运算就不能支持。特性一般都是在class语句顶层赋值。

age的获取就会自动调用getage方法。

__getattribute__和描述符
__getattribute__方法只适用于新式类,可以让类拦截所有属性的引用,而不局限于未定义的引用(如同__getattr__)。

除了特性和运算符重载方法,Python支持属性描述符的概念–带有__get____set__方法的类,分配给类属性并且由实例继承,这拦截了对特定属性的读取和写入访问。

元类
元类是子类化了type对象并且拦截类创建调用的类。此外,它们还为管理和扩展类对象提供了一种定义良好的钩子。

静态方法和类方法
静态方法–嵌套在一个类中的没有self参数的简单函数,并且旨在操作类属性而不是实例属性。静态方法不会接受一个自动的self参数,不管是通过一个类还是一个实例调用。

类方法–类的一种方法,传递给它们的第一个参数是一个类对象而不是一个实例,不管是通过一个实例或一个类调用它们。即便是通过一个实例调用,这样的方法也可以通过它们的self类参数来访问类数据。

2.6和3.0中的静态方法:

  • 在Python2.6中,我们必须总是把一个方法声明为静态的,从而不带一个实例而调用它,不管是通过一个类或一个实例调用它。。不声明的话,就是未绑定方法,未绑定方法需要传入一个实例对象。
  • 在Python3.0中,如果方法只通过一个类调用的话,我们不需要将这样的方法声明为静态的,但是,要通过一个实例调用它,我们必须这么做。

2.x中的静态方法和类方法。


使用静态方法内置函数,我们的代码现在允许在Python2.6和Python3.0中通过类或其任何实例来调用无self方法:(如果不使用静态方法内置函数,Pyhton2.6不管是通过类还是实例,都无法调用无self方法,Python3.0则只允许通过类调用无self实例方法)

类方法示例如下:

使用类方法统计每个类的实例
实际上,由于类方法总是接收一个实例树中的最低类:

  • 静态方法和显式类名称可能对于处理一个类本地的数据来说是更好的解决方案。
  • 类方法 可能更适合 处理对层级中的每个类不同的数据。

装饰器和元类:第一部分
从语法上来讲,函数装饰器是它后边的函数的运行时的声明。函数装饰器是写成一行,就在定义函数或方法的def语句之前,而且由@符号、后面跟着所谓的元函数(metafunction)组成:也就是管理另一函数(或其他可调用对象)的函数。

如今的静态方法可以用下面的装饰器语法编写。

>>> class C:
...     @staticmethod
...     def meth():
...             pass
...
>>> C.meth()
>>>

从内部来看,这个语法和下面的写法有相同效果(把函数传递给装饰器,再赋值给最初的变量名)。

>>> class C:
...     def meth():
...             pass
...     meth = staticmethod(meth)
...
>>> C.meth()

类装饰器和元类
类装饰器类似于函数装饰器,但是,它们在一条class语句的末尾运行,并且把一个类名重新绑定到一个可调用对象。
代码结构如下:

def decorator(aClass):...

@decorator
class C:...

被映射为下列相当代码:

def decorator(aClass):...

class C:...
C = decorator(C)

元类是一种类似的基于类的高级工具,其用途往往与类装饰器有所重合。它们提供了一种可选的模式,会把一个类对象的创建导向到顶级type类的一个子类,在一条class语句的最后:

class Meta(type):
    def __new__(meta,classname,supers,classdict):...
class C(metaclass=Meta):...

在Python2.6中,在类头部使用一个类属性而不是一个关键字参数:

class C:
    __metaclass__ = Meta
    ...

元类通常重新定义type类的newinit方法,以实现对一个新的类对象的创建和初始化的控制。

类陷井

  1. 修改类属性的副作用: 会影响子类和实例对象。
  2. 修改可变的类属性也可能产生副作用:譬如通过实例修改类的一个list列表内容,其他所有共享这个列表的内容也都变了。
  3. 多重继承:顺序很重要。 搜索先左后右,广度优先。
  4. ”过度包装“:如果类层次太深,程序就会变得晦涩难懂。

第七部分 异常和工具

第32章 异常基础

几个形式:

  • try/except 捕捉由Python或你引起的异常并恢复
  • try/finally 无论异常是否发生,执行清理行为。
  • raise 手动在代码中触发异常。
  • assert 有条件地在程序代码中触发异常。
  • with/as 在Python2.6和后续版本中实现环境管理器

异常的角色

  1. 错误处理。
  2. 事件通知。
  3. 特殊情况处理。
  4. 终止行为。
  5. 非常规控制淤积。

第33章 异常编码细节

try/except/else语句
在Python2.5以前except和finally不能混在一个try语句中。

Python3.0中的一般形式。

try:
    <statements> # 主逻辑
except <name1>: #捕获异常的处理
    <statements>
except (name2,name3):
    <statements>
except <name4> as <data>:
    <statements>
except:
    <statements>
else: #没有异常时,执行
    <statements>

try/finally语句
一般形式:

try:
    <statements>
finally:
    <statements>

利用这个变体,Python可先执行try首行下的语句代码块。接下来发生的事情,取决于try代码块中是否发生异常。

  • 如果try代码块运行时没有异常发生,Python会跳至执行finally代码块,然后在整个try语句后继续执行下去。
  • 如果try代码块运行时有异常发生,python依然会回来运行finally代码块,但是接着会把异常向上传递到较高的try语句或顶层默认处理器。程序不会在try语句下继续执行。

现在完整的try版本:

try:
    main-action
except Exception1:
    handler1
except Exception2:
    handler2
...
else:
    else-block
finally:
    finally-block

raise语句
要显式地触发异常,可以使用raise语句: raise关键字,后面跟着可选的要引发的类或者类的一个实例。

raise <instance>
raise <class> #自动调用不带构造参数的类,以创建被引发的一个实例。
raise #把最近的异常重新触发下。

Python3.0异常链:raise from
Python3.0也允许raise语句拥有一个可选的from子句:

raise exception from otherexception

当使用from的时候,第二个表达式指定了另一个异常类或实例,它会附加到引发异常的__cause__属性。

assert语句
Python还包括了assert语句,assert可视为条件式的raise语句。

asser <test>,<data>

执行起来就像如下的代码。

if __debug__:
    if not <test>:
        raise AssertionError(<data>)

assert语句是附加的功能,如果使用-O Python命令行标志位,就会从程序编译后的字节码中移除,从而优化程序。

assert几乎就是用来收集用户定义的约束条件,而不是捕捉内在的程序设计错误。

with/as环境管理器
with/as语句的设计是作为常见try/finally用法模式的替代方案。就像try/finally语句,with/as语句也是用于定义必须执行的终止或”清理”行为,无论处理步骤中是否发生异常。
不过,和try/finally不同的是,with语句支持更丰富的基于对象的协议,可以为代码块定义支持进入和离开动作。

with语句的基本格式如下。

with expression [as varibalbe]:
    with-block

在这里的expression要返回一个对象,从而支持环境管理协议。

环境管理协议
以下是with语句实际的工作方式。

  1. 计算表达式,所得到的对象称为环境管理器,它必须有__enter____exit__方法。
  2. 环境管理器的__enter__方法会被调用。如果as子句存在,其返回值会赋值给as子句中的变量,否则,直接丢弃。
  3. 代码块中的嵌套的代码会执行。
  4. 如果with代码块引发异常,__exit__(type,value,traceback)方法就会被调用(带有异常细节)。这引起也是由sys.exc_info返回的相同值。如果此方法返回值为假,则异常会重新引发。否则,异常会终止。正常情况下异常是应该被重新引发,这样的话才能传递到with语句之外。
  5. 如果with代码块没有引发异常,__exit__方法依然会被调用,其type、value以及traceback参数都会以None传递。

示例代码如下:

以下是实际在Python3.0中运行的脚本:

第34章 异常对象

基于类的异常有如下特点。

  • 提供类型分类,对今后的修改有更好的支持。
  • 它们附加了状态信息。
  • 它们支持继承。

在Python2.6和Python3.0之前,可以使用类实例和字符串对象来定义异常。

字符串异常和类异常的主要差别在于,引发的异常在try语句中的excepte子句匹配时的方式不同。

  • 字符串异常是以简单对象识别来匹配的:引发的异常是由Python的is测试来匹配except子句的。
  • 类异常是由超类关系进行匹配的:只要excepte子句列举了异常的类或其任何超类名,引发的异常就会匹配该子句。

第35章 异常的设计

嵌套异常处理器
当发生异常时,Python会回到最近进入、具有相符except分句的try语句。因为每个try语句都会留下标识,Python可检查堆栈的标识,从而跳回到较早的try。
这种处理器的嵌套化,就是我们所谈到的异常向上传递至较高的处理器的意思:这类处理器就是在程序执行流程中较早进入的try语句。

异常的习惯用法

  1. 异常不总是错误。譬如EOFERROR用来标识文件结束。
  2. 函数信号条件和raise。有些时候,我们不能用返回值来代表不寻常的情况,这种时候用异常反而比较好。它代表了某种信号,而不是真的发生异常了。
  3. 关闭文件和服务器连接。
  4. 在try外进行调试。可以利用异常处理器,取代Python的默认顶层异常处理行为(一般用于开发期间调式)。

关于sys.exc_info
sys.exc_info允许一个异常处理器获取对最近引发的异常的访问。如果没有处理器正在处理,就返回包含了三个None值的元组。否则,将会返回(type、value和traceback):

  • type是正在处理的异常的异常类型。
  • value是引发的异常类实例。
  • traceback是一个traceback对象,代表异常最初发生时所调用的堆栈。

与异常有关的技巧
try应该包装什么?
* 经常会失败的运算一般都应该包装在try语句内。如文件开启、套接字调用等。
* 应该在try/finally中实现终止动作,从而保证它们的执行,除非环境管理器作为一个with/as选项可用。
* 偶尔,把对大型函数的调用包装在单个try语句内。

捕捉太多:避免空except语句
Python可选择要捕捉哪些异常,有时候必须小心,不要涵盖太广。
try带空except时,可能会不知不觉阻止重要的结束,如下面文件所示:

经验法则是,尽量让处理器具体化:空except子句很方便,但是可能容易出错。

捕捉过少:使用基于类的分类

第八部分 高级话题

第36章 Unicode和字节字符串

高级的字符串表示法在Python当前版本中已经产生了分歧:

  • Python3.0为二进制数据提供了一种替代字符器类型,并且在其常规的字符串类型中支持Unicode文本(ASCII看作是Unicode的一种简单类型)
  • Python2.6为非ASCII Unicode文本提供了一种替代字符串类型,并且在其常规的字符串类型中支持简单文本和二进制数据。

Python3.0中的字符串修改
Python2.x的str和unicode类型 已经融入了Python3.0的str和bytes类型,并且增加了一种新的可变的类型bytearray。

ASCII– 0~127
Latin-1 — 0~255
8位的字节不够表示更多的字符,于是出现了unicode。

同样的一个字符串可以有多少编码方式,譬如’a’可以按ASCII编码也可以按unicode编码。
字节和字符串之间的来回转换由两个术语定义:

  • 编码是根据一个想要的编码名称,把一个字符串翻译为其原始字节形式。
  • 解码是根据其编码名称,把一个原始字节串翻译为字符串形式的过程。

Python的字符串类型
Python2.X有一种通用的字符串类型来表示二进制数据和像ASCII这样的8位文本,还有一种特定的类型用来表示多字节Unicode文本:

  • str表示8位文本和二进制数据。
  • unicode用来表示宽字符的Unicode文本。

Python3.X带有3种字符串对象类型– 一种用于文本数据,两种用于二进制数据:

  • str表示Unicode文本。
  • bytes表示二进制数据。
  • bytearray,是一种可变的bytes类型。

Python3.0的str类型定义为一个不可改变的字符序列
Python3.0中,bytes类型定义为一个8位整数的不可变序列,表示绝对的字节值。为了方便起见,bytes对象打印为字符串而不是整数。

文本和二进制数据
Python现在在文本文件和二进制文件之间做了一个明显的独立于平台的区分:

  1. 文本文件。当一个文件以文本模式打开的时候,读取其数据会自动将其内容解码,并且将其内容返回为一个str,写入会接受一个str,并且在将其传输到文件之间自动编码。
  2. 二进制文件。通过在内置的open调用的模式字符串参数添加一个b(只能小写),以二进制模式打开一个文件的时候,读取其数据不会以任何方式解码它,而是直接返回其内容raw并且未经修改,写也一样不会修改。

在Python3.0中,所有当前字符串常量形式,’xxx’、”xxx”和三引号字符串块,都产生一个str;在它们任何一种前面添加一个b或B,则会创建一个bytes。

Python3.0基本上要求遵守一种类型或另一种类型,或者手动执行显式转换:

  • str.encode()和bytes(S,encoding)把一个字符串转换为其raw bytes形式,并且在此过程中根据一个str创建一个bytes。
  • bytes.decode()和str(B,encoding)把raw bytes转换为其字符串形式,并且在此过程中根据一个bytes创建一个str。

要编码非ASCII字符,可能在字符串中使用十六进制(“xNN”)或Unicode转义(“uNNNN”和UNNNNNNNN);
十六进制转义限制于单个字节的值;”uNNNN”用来编码1个2字节字符码;”UNNNNNNNN”编码4字节宽度的字符。

有两点需要注意:
首先,Python3.0允许特殊的字符以十六进制和Unicode转义的方式编码到str字符串中,但是,只能以十六进制转义的方式编码到bytes字符串中:Unicode转义会默默地逐字转换为字节常量,而不是转义。

其次,字节常量要求字符要么是ASCII字符,要么如果它们的值大于127就进行转义。
另一方面,str字符串允许常量包含源字符集中的任何字符:

在Python2.6中编码Unicode字符串
为了存储任意的编码的Unicode文本,用u’xxx’常量形式创建一个unicode对象(这个常量在Python3.0中不再可用,因为Python3.0中的所有字符串都支持Unicode)。

源文件字符集编码声明
有两种形式

# -*- coding: latin-1 -*-
# coding=UTF-8

尽管Python3.0中所有的三种字符串类型都可以包含字符值并且支持很多相同的操作,但我们总是应该:

  • 对文本数据使用str;
  • 对二进制数据使用bytes;
  • 对想要原处修改的二进制数据使用bytearray。

用哪种模式打开一个文件,它决定了在脚本中将要使用哪种对象类型表示文件的内容。文本模式意味着str对象,二进制模式意味着bytes对象:

  • 文本模式文件根据Unicode编码来解释文件内容,要么是平台的默认编码,要么是我们传递进的编码名。通过传递一个编码名来打开文件,我们可以强行进行Unicode文件的各种类型的转换。文本模型的文件也执行通用的行末转换:默认地,所有的行末形式(包括’r’,’n’,’rn’)映射为脚本中的一个单个的’n’字符,而不管在什么平台上运行。正如前面所描述的,文本文件也负责阅读和写入在某些Unicode编码方案中存储文件开始处的字节顺序标记(Byte Order Mark, BOM).
  • 二进制模式文件不会返回原始的文件内容,而是作为表示字节值的整数的一个序列,没有编码或解码,也没有行末转换。

在Windows下文本文件自动把n行末字符和rn相互映射,而二进制文件不这么做。

在Python3.0中处理BOM
一些编码方式在文件的开始处存储了一个特殊的字节顺序标记(BOM)序列,来指定数据的大小尾方式或编码类型。如果编码名暗示了BOM的时候,Python在输入和将其输出的时候都会忽略该标记,但是有时候必须使用一个特定的编码名称来迫使显式地处理BOM。

Python3.0中其他字符串工具的变化

  1. re模式匹配模块。这个模块已经泛化为可以用于Python3.0中的任何字符串类型的对象–str、bytes和bytearray,并且返回同样类型的结果子字符串作为目标字符串。
  2. Struct二进制数据模块。打包的数据只是作为bytes和bytearray对象显示,而不是str对象。

    附加两个struct格式说明表格:

  1. Pickle对象序列化模块。pickle模块的Python3.0版本总是创建一个bytes对象。所以写入文件时,需要以二进制模式打开。
  2. XML解析工具。SAX和DOM解析模式。

第37章 管理属性

插入在属性访问时运行的代码

  1. __getattr__和__setattr__方法,把未定义的属性获取和所有的属性赋值指向通用的处理器方法。
  2. __getattribute__方法,把所有属性获取都指向Python2.6的新式类和Python3.0的所有类的一个泛处理器方法。
  3. property内置函数,把特定属性访问定位到get和set处理器函数,也叫做特性(Property)。
  4. 描述符协议,把特定属性访问定位到具有任意get和set处理方法的类的实例。

特性
特性协议允许我们把一个特定属性的get和set操作指向我们所提供的函数或方法,使得我们能够插入在属性访问的时候自动运行的代码,拦截属性删除,并且如果愿意的话,还可为属性提供文档。

attribute = property(fget,fset,fdel,doc)

四个参数如果没有传的话,默认为None,意味着相应的操作不支持,如果调用了就会引发一个异常。

利用装饰器语法可以简化特定的写法。如下示例:

对于Python2.6,property对象也有getter、setter和deleter方法,这些方法指定相应的特性访问器方法赋值并且返回特性自身的一个副本。

描述符
描述符也管理一个单个的、特定的属性。特性实际中只是创建一种特写描述符的方便方法。
描述符作为单独的类编写,并且针对想要拦截的属性访问操作提供特定命名的访问器方法–当以相应的方式访问分配给描述符类实例的属性时,描述符类中的获取、设置和删除等方法自动运行:

class Descriptor:
    "docstring goes here"
    def __get__(self,instance,owner):... #返回属性值
    def __set__(self,instance,value):... #返回None
    def __delete__(self,instance):... #返回None

带有任何这些方法的类都可以看作是描述符,并且当它们的一个实例分配给另一个类的属性的时候,它们的这些方法是特殊的–当访问属性的时候,会自动调用它们。
和特性不同,省略一个__set__意味着允许这个名字在一个实例中重新定义,因此,隐藏了描述符–要使得一个属性是只读的,我们必须定义__set__来捕获赋值并引发一个异常。

当获取X.attr的时候,就好像发生了如下的转换:

X.attr-> Descriptor.__get__(Subject.attr,X,Subject)

当描述符的实例参数为None的时候,该描述符知道将直接访问它。

描述符可以使用实例状态和描述符状态,或者二者的任何组合:

  • 描述符状态用来管理内部用于描述符工作的数据。
  • 实例状态记录了和客户类相关的信息,以及可能由客户类创建的信息。

特性和描述符是如何相关的
可以用下面的描述符来模拟property内置函数。

__getattr__和__getattribute__
不同于特性和描述符只针对单个属性,__getattr____getattribute__(类似的还有__setattr__,__delattr__)更通用:

  • __getattr__针对未定义的属性运行–也就是说,属性没有存储在实例上,或者没有从其类之一继承。
  • __getattribute__针对每个属性,因此,当使用它的时候,必须小心避免通过把属性访问传递给超类而导致递归循环。
def __getattr__(self,name): ..#获取未定义的属性时运行,如obj.name
def __getattribute__(self,name):...#获取所有属性时运行,如obj.name
def __setattr__(self, name,value):...#设置所有属性时运行,如obj.name=value
def __delattr__(self,name):...#删除属性时运行。如del obj.name

避免属性拦截方法中的循环

  1. 利用命名空间字典。
def __setattr__(self,name,value):
    self.__dict__['other'] = value #ok
    self.other = value #bad,会再次调用__setattr__从而导致死循环。

2.利用超类方法调用。

def __getattribute__(self, name):
    x = self.other #bad,self.other会再次调用__getattribute__,从而导致死循环
    x = object.__getattribute__(self,'other') #ok,利用超类方法调用 没问题

拦截内置操作属性
对于隐式地使用内置操作获取的方法名属性,这些方法(__getattr____getattribute__等)可能根本不会运行。这意味着操作符重载方法调用不能委托给被包装的对象,除非包装类自己重新定义这些方法。

如针对__str____add____getitem__方法的属性获取分别通过打印、+表达式和索引隐式运行,而不会指向Python3.0中的类属性拦截方法。特别是:

  • 在Python3.0中,__getattr____getattribute__都不会针对这样的属性而运行。
  • 在Python2.6中,如果属性在类中未定义的话,__getattr__会针对这样的属性运行。
  • 在Python2.6中,__getattribute__只对于新式类可用,并且在Python3.0中也可以使用。

在Python2.X中,这样的操作调用的方法在运行时从实例中查找,就像所有其他属性一样;在Python3.0中,这样的方法在中查找。

第38章 装饰器

装饰是为函数和类指定管理代码的一种方式。装饰器本身的形式是处理其他的可调用对象的可调用对象(如函数)。
装饰器提供了一种方法,在函数和类定义语句的末尾插入自动运行代码

通过针对随后的调用安装包装器对象可以实现:

  • 函数装饰器安装包装器对象,以在需要的时候拦截随后的函数调用并处理它们。
  • 类装饰器安装包装器对象,以在需要的时候拦截随后的实例创建调用并处理它们。

Flask Web开发 基于Python的Web应用开发实战 这本书里有好多装饰器的使用,可以参考下。

为什么使用装饰器?

  1. 装饰器有一种非常明确的语法,这使得它们比那些可能任意地远离主体函数或类的辅助函数调用更容易为人们发现。
  2. 当主体函数或类定义的时候,装饰器应用一次;在对类或函数的每次调用的时候,不必添加额外的代码。
  3. 由于前面两点,装饰器使得一个API的用户不太可能忘记根据API需要扩展一个函数或类。

函数装饰器是一种关于函数的运行时声明,函数的定义需要遵守此声明。
装饰器在紧挨着定义一个函数或方法的def语句之前的一行编写,并且它由@符号以及紧随其后的对于元函数的一个引用组成–这是管理另一个函数的一个函数。

在编码方面,函数装饰器自动将如下的语法:

@decorator #Decorate function
def F(arg):
    ...
F(99) #调用函数

映射为这一对等的形式,其中装饰器是一个单参数的可调用对象,它返回与F具有相同数目的参数的一个可调用对象:

def F(arg):
    ...

F = decorator(F) #rebind function name to decorator result
F(99) # Essentially calls decorator(F)(99)

这一自动名称重绑定在def语句上有效,不管它针对一个简单的函数或是类中的一个方法。当随后调用F函数的时候,它自动调用装饰器所返回的对象,该对象可能是实现了所需的包装逻辑的另一个对象,或者是最初的函数本身。

装饰器自身是一个返回可调用对象的可调用对象
有一种常用的编码模式–装饰器返回了一个包装器,包装器把最初的函数保持到一个封闭的作用域中:

当随后调用名称func的时候,它硬实调用装饰器所返回的包装器函数;随后包装器函数可能会运行最初的func,因为它在一个封闭的作用域中仍然可以使用。当以这种方式编码的时候,每个装饰器的函数都会产生一个新的作用域来保持状态。

我们也可以通过对类来重载call方法,从而把类转成一个可调用对象,并且使用实例属性而不是封闭的作用域:

有一点需要注意,通过类实现的装饰器对象并不能工作在类方法上。
因为:当一个方法名绑定只是绑定到一个简单的函数时,Python向self传递了隐含的主体实例;当它是一个可调用类的实例的时候,就传递这个类的实例。
从技术上讲,当方法是一个简单函数的时候,Python只是创建了一个绑定的方法对象,其中包含了主体实例。
反而是利用封闭作用域的嵌套函数工作的更好,既能支持简单函数,也能支持实例方法。

类装饰器和函数装饰器很类似,只不过管理的是类。
通过函数实现,返回了一个包装器类。

每个被装饰的类都创建一个新的作用域,它记住了最初的类。

工厂函数通常在封闭的作用域引用中保持状态,类通常在属性中保持状态。

需要注意通过类实现的类装饰器,看如下的错误示例:

每个被装饰的类都返回了一个Decorator的实例。
但是对给定的类创建多个实例时出问题了—会对一个Decorator实例反复调用call方法,从而后面的的实例创建调用都覆盖了前面保存的实例。。(也许我们可以利用这个特性来实现单例模式??)

装饰器嵌套
为了支持多步骤的扩展,装饰器语法允许我们向一个装饰的函数或方法添加包装器逻辑的多个层。
这种形式的装饰器语法:

@A
@B
@C 
def f(...):
    ...

如下这样运行:

def f(...):
    ...

f = A(B(C(f)))

类装饰器类似。。

装饰器参数
函数装饰器和类装饰器似乎都能接受参数,尽管实际上这些参数传递给了真正返回装饰器的一个可调用对象,而装饰器反过来又返回了一个可调用对象。例如,如下代码:

@decorator(A,B)
def F(arg):
    ...
F(99)

自动地映射到其对等的形式,其中装饰器是一个可调用对象,它返回实际的装饰器。返回的装饰器反过来返回可调用的对象,这个对象随后运行以调用最初的函数名:

def F(arg):
    ...
F = decorator(A,B)(F) #Rebind F to result of decorator's return value
F(99) #Essentially calls decorator(A,B)(F)(99)

装饰器参数在装饰发生之前就解析了,并且它们通常用来保持状态信息供随后的调用使用。
例如,这个例子中的装饰器函数,可能采用如下的形式:

def decorator(A,B):
    #save or use A,B
    def actualDecorator(F):
        #Save or use function F
        #Return a callable:nested def, class with __call__, etc.
        return callable
    return actualDecorator

换句话说,装饰器参数往往意味着可调用对象的3个层级:接受装饰器参数的一个可调用对象,它返回一个可调用对象以作装饰器,该装饰器返回一个可调用对象来处理对最初的函数或类的调用。这3个层级的每一个都可能是一个函数或类,并且可能以作用域或类属性的形式保存了状态。

装饰器管理函数和类
装饰器不光可以管理随后对函数和类的调用,还能管理函数和类本身。如下所示,返回函数和类本身:

def decorator(o):
    #Save or augment function or class o
    return o
    
@decorator
def F():... #F=decorator(F)

@decorator
class C:... #C = decorator(C)

函数装饰器有几种办法来保持装饰的时候所提供的状态信息,以便在实际函数调用过程中使用:

  1. 实例属性。
  2. 全局变量
  3. 非局部变量
  4. 函数属性。

在运用描述符的情况下,我们也能把通过类实现的装饰器运用到 类方法上,只是有点复杂,如下所示:

当person实际调用giveRaise的时候,先是获取giveRaise属性会触发描述符tracer的get调用。get返回了wrapper对象。而wrapper对象又保持了tracer实例和person实例。
当调用giveRaise时,其实是调用的wrapper实例的call方法。wrapper实例的call方法又回调
tracer的call方法,利用wrapper保持的person实例把person实例当成参数也传了回去。
调用顺序如下:

person.giveRaise()->wrapper.__call__()->tracer.__call__()

这个例子中把wrapper类改成嵌套的函数也可以,而且代码量更少,如下:

类装饰器(函数装饰器)的两个潜在缺陷:

  • 类型修改。当插入包装器的时候,一个装饰器函数或类不会保持其最初的类型–其名称重新绑定到一个包装器对象,在使用对象名称或测试对象类型的程序中,这可能会很重要。
  • 额外调用。通过装饰添加一个包装层,在每次调用装饰对象的时候,会引发一次额外调用所需要的额外性能成本–调用是相对耗费时间的操作。

与管理器(即辅助)函数解决方案相比,装饰器提供:

  • 明确的语法
  • 代码可维护性
  • 一致性

函数内省

参数假设

  • 在调用中,所有的位置参数出现在所有关键字参数之前。
  • 在def中,所有的非默认参数出现在所有的默认参数之前。

装饰器参数 VS 函数注解
利用函数注解,可以简化功能的实现:因为状态信息现在在函数自身,装饰器不再需要一层封闭的作用域来保持状态。
对比下装饰器参数代码和函数注解的代码:

第39章 元类

从某种意义上讲,元类只是扩展了装饰器的代码插入模式。
元类主要是针对那些构建API和工具供他人使用的程序员。

Python构建工具:

  1. 内省属性。如__class__、__dict__
  2. 运算符重载方法。如__str__、__add__
  3. 属性拦截方法。如__getattr__、__setattr__、__getattribute__。
  4. 类特性。内置函数property。拦截特定的属性。
  5. 类属性描述符。拦截特定的属性。特定只是定义根据访问自动运行函数的属性描述符的一种简洁方式。
  6. 函数和类装饰器。
  7. 元类。

元类允许我们在在一条class语句的末尾,插入当创建一个类对象的时候自动运行的逻辑。
这个逻辑不会把类名重新绑定到一个装饰器可调用对象,而是把类自身的创建指向特定的逻辑。

和类装饰器不同,它通常是添加实例创建时运行的逻辑,元类在类创建时运行。
同样的,它们都是通常用来管理或扩展类的钩子,而不是管理其实例。

通过声明一个元类,我们告诉Python把类对象的创建路由到我们所提供的另一个类:

由于创建类的时候,Python在class语句的末尾自动调用元类,因此它可以根据需要扩展、注册或管理类。

类也是某物的实例:

  • 在Python3.0中,用户定义的类对象是名为type的对象的实例,type本身是一个类。
  • 在Python2.6中,新式类继承自object,它是type的一个子类;传统类是type的一个实例,并且并不创建自一个类。
    实例创建自类,而类创建自type。
    类是类型,类型也是类
  • 类型由派生自type的类定义。
  • 用户定义的类是类型类的实例。
  • 用户定义的类是产生它们自己的实例的类型。

类根本不是一个独立的概念:它们就是用户定义的类型,并且type自身也是由一个类定义的。

由于类实际上是type类的实例,从type的定制的子类创建类允许我们实现各种定制的类。
在Python3.0中以及在Python2.6的新式类中:

  • type是产生用户定义的类的一个类。
  • 元类是type类的一个子类。
  • 类对象是type类的一个实例,或一个子类。
  • 实例对象产生自一个类。

换句话说,为了控制创建类以及扩展其行为的方式,我们所需要做的只是指定个用户定义的类创建自一个用户定义的元类,而不是常规的type类。

从技术上讲,Python遵从一个标准的协议来使这发生:在一条class语句的末尾,并且在运行了一个命名控件词典中的所有嵌套代码之后,它调用type对象来创建class对象:

class = type(classname, superclasses,attrbuteddict)

type对象反过来定义了一个__call__运算符重载方法,当调用type对象的时候,该方法运行两个其他方法:

type.__new__(typeclass, classname,superclasses,attributeddict)
type.__init__(class,classname,superclasses,attributedict)

__new__方法创建并返回了新的class对象,并且随后__init__方法初始化了新创建的对象。
这是type的元类子类通常用来定制类的钩子。

尽管重新定义type超类的__new____init__方法是元类向类对象创建过程插入逻辑的最常见方法,其他方案也是可能的。
实际上任何可调用对象都可以用作一个元类,只要它接收传递的参数并且返回与目标类兼容的一个对象。

另外,我们也可以重定义元类的__call__, 以拦截创建调用。

实例与继承的关系

  • 元类继承自type类。
  • 元类声明由子类继承。在用户定义的类中,metaclass=M声明由该类的子类继承,因此,对于在超类链中继承了这一声明的每个类的构建,该元类都将运行。
  • 元类属性没有由类实例继承。元类声明指定了一个实例关系,它和继承不同。由于类是元类的实例,所以元类中定义的行为应用于类,而不是类随后的实例。实例从它们的类和超类获取行为,但是,不是从任何元类获取行为。从技术上讲,实例属性查找通过只是搜索实例及期所有类的__dict__字典;元类不包含在实例查找中。

元类与类装饰器在功能上有重合。

  • 在class语句末尾,类装饰器把类名重新绑定到一个函数的结果。
  • 元类通过在一条class语句的末尾把类对象创建过程路由到一个对象来工作。
Tags:
11 Comments