所谓回炉,即回炉重练。以前学Python只是抱着玩玩的心态学,但现在下定决心要好好认真学习。于是决定完全重头开始学起,主要是根据Python2.7的官方文档和网上诸多的资料来学。由于资料繁多且杂乱无法一一列举,仅将回炉所学记录以便日后翻阅。
一、计算
/
——除法:返回的类型取决于它的操作数。如果两个操作数都是int,将采用floor除法并返回一个int。如果两个操作数中有一个是float,将采用传统的除法并返回一个float。//
——floor除法:无论操作数是什么类型,都将采用floor除法并返回一个int。%
——求余:求取余数- **——乘方:幂运算。
**
的优先级高于-
,-3**2
解析为-(3**2)
1
2
3
4
5
6
7
8
9
10
11
12>>> 17 / 3 # int / int -> int
5
>>> 17 / 3.0 # int / float -> float
5.666666666666667
>>> 17 // 3.0 # explicit floor division discards the fractional part
5.0
>>> 17 % 3 # the % operator returns the remainder of the division
2
>>> 5 * 3 + 2 # result * divisor + remainder
17
>>> 5 ** 2 # 5 squared
25
二、字符串
- 字符串用单引号或双引号括起,可用
\\
转义引号。 - 使用原始字符串,在引号前加上
r
:print r'C:\some\name'
(\n
则不会被当成换行符) 字符串可用
+
实现连接,用*
实现重复1
2
3
4
5
6>>> 3 * 'un' + 'ium'
'unununium'
>>> prefix = 'Py'
>>> prefix + 'thon'
'Python'索引
字符串可索引,第一个字符的索引值为0。Python没有单独的字符类型;一个字符就是一个简单的长度为1的字符串。1
2
3
4
5
6
7
8
9
10
11>>> word = 'Python'
>>> word[0] # character in position 0
'P'
>>> word[5] # character in position 5
'n'
>>> word[-1] # last character
'n'
>>> word[-2] # second-last character
'o'
>>> word[-6]
'P'切片
字符串还支持切片操作。切片的索引有默认值——省略的第一个索引默认为零,省略的第二个索引默认为切片的字符串的大小。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16>>> word[0:2] # characters from position 0 (included) to 2 (excluded)
'Py'
>>> word[2:5] # characters from position 2 (included) to 5 (excluded)
'tho'
#试图使用太大的索引会导致错误
>>> word[42] # the word only has 7 characters
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range
#当用于切片时,超出范围的切片索引则不会导致错误而是会被优雅处理
>>> word[4:42]
'on'
>>> word[42:]
''不可变性
Python中字符串(string)是不可变的,若赋值给字符串索引的位置会导致错误- 长度
内置函数len()
可返回字符串长度1
2
3>>> s = 'supercalifragilisticexpialidocious'
>>> len(s)
34
三、列表
索引 & 分片 & 连接
列表(list)和字符串一样支持索引、分片和连接操作1
2
3
4
5
6
7>>> squares = [1, 4, 9, 16, 25]
>>> squares[0]
1
>>> squares[-3:]
[9, 16, 25]
>>> squares + [36, 49, 64, 81, 100]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]可变性
与字符串的不可变性不同,列表是可变的类型,可改变列表的内容且常常使用append()方法在列表末尾添加新元素,用len()方法获取列表长度1
2
3
4
5
6
7
8>>> squares
[1, 4, 9, 16, 25]
>>> squares[3] = 64
>>> squares
[1, 4, 9, 64, 25]
>>> squares.append(100)
>>> squares
[1, 4, 9, 64, 25, 100]
四、控制流
if语句
1 | if x < 0: |
for语句
Python的for语句按照元素出现的顺序迭代任何序列(列表或字符串)1
2
3words = ['one', 'two', 'three']
for each_word in words:
print each_word, len(each_word)
迭代序列不会隐式地创建副本,若要修改正在循环的序列则需要使用分片创建副本1
2
3
4for each_word in words[:]:
if len(each_word) > 4:
words.insert(0, each_word)
print words
range()
产生数字序列。给定的终点永远不会在生成的列表中,可以指定一个不同的步进值默认为11
2
3
4>>> range(3)
[0, 1, 2]
>>> range(0, 5, 2)
[0, 2, 4]
定义函数
1 | def myfunc(n): |
def
:引入函数的定义myfunc(n)
:函数名及以括号标明的形式参数列表"""docstring for myfunc"""
(可选):文档字符串,可通过myfunc.__doc__
获取print n
:函数体,必须缩进
执行一个函数时会使用到函数的局部变量符号表,函数的所有赋值都将值存储在局部符号表中。变量引用的优先查找顺序:1
局部符号表——>上层函数的局部符号表——>全局符号表——>内置名字表
若函数中没有return
或存在不带表达式参数的return
,函数会直接返回None
默认参数值
函数的接收参数可提供默认值。如下函数myfunc(name, age=10)
中,age
参数的默认值即为10
,在调用函数myfunc()
是若不传递age
的值则使用默认值1
2
3
4
5def myfunc(name, age=10):
"""docstring for myfunc"""
print name, age
myfunc('mogl')
关于默认参数值有个非常需要注意的地方:默认参数值是在函数定义时就已经确定。定义后的所有函数调用,若参数不是显示的给予赋值的话,那么该默认参数一直都是引用函数定义时所确定的对象。
如果默认参数是一个不可变的对象(string/num/tuple),那么在函数体内修改该参数,该参数会会重新引用新的不可变对象的内存地址。
如果默认参数是一个可变的对象(list/dict),那么在函数体内修改该参数,实际上是对函数定义时就已确定的对象的修改。
默认参数值为不可变对象
1
2
3
4
5
6
7i = 5
def f(arg=i):
"""docstring for f"""
print arg
i = 6
f() #结果是5参数
arg
在函数定义时被确定,在函数定义时i=5
,故arg=5
。在后续调用函数时fu()
并没有显示的对arg
进行赋值,故arg
使用的是函数定义时确定的对象引用的内存地址。默认参数值为可变对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16def myfunc(a, L=[]):
"""docstring for myfunc"""
L.append(a)
return L
print myfunc(1)
print myfunc(2)
print myfunc(3)
b = []
print myfunc(4, b)
#结果为
#[1]
#[1, 2]
#[1, 2, 3]
#[4]由于默认参数在函数定义时确定,当
myfunc(1)
、myfunc(2)
和myfunc(3)
调用函数是并没有显示的对默认参数L
进行赋值,故三个函数调用使用的都是在函数定义时确定的对象,所以在函数体内进行修改也是对函数定义时就确定的对象进行修改,故三个函数调用共享使用函数定义时确定的对象L
。
任意位置参数、关键字参数
任意位置参数——*args
函数可使用*args
接受任意长度的位置参数,这些参数会被放入一个元组(tuple)中。但当*args
接收的就是是一个列表或元组时,则会对接收的列表/元组进行拆分。1
2
3
4
5
6
7
8
9
10
11def myfunc(*args):
"""docstring for myfunc"""
for arg in args:
print "In myfunc(*args):", arg
return args
print myfunc('one', 'two')
#In myfunc(*args): one
#In myfunc(*args): two
#('one', 'two') #返回的是一个元组(tuple)任意关键字参数——\*\*kwargs
函数可使用**kwargs
接受任意长度的关键字参数,这些参数会被存入一个字典(dict)中1
2
3
4
5
6
7
8
9
10
11
12def myfunc(**kwargs):
"""docstring for myfunc"""
for kw in kwargs.keys():
print kw, kwargs[kw]
return kwargs
print myfunc(name="mogl", age=10, gender='man')
#gender man
#age 10
#name mogl
#{'gender': 'man', 'age': 10, 'name': 'mogl'} #返回的是一个字典(dict)混合参数
混合参数按照此次序定义:必选参数——>可选参数——>任意位置参数——>任意关键字参数1
2
3
4
5
6
7
8
9
10
11def myfunc(name, test=None, *args, **kwargs):
"""docstring for myfunc"""
print "name: ", name
print "args: ", args
print "kwargs: ", kwargs
myfunc('mogl', 'moguoliang', 'mogl', age=10, gender='man')
#name: mogl
#args: ('moguoliang', 'mogl')
#kwargs: {'gender': 'man', 'age': 10}
lambda表达式
Lambda 函数可以用于任何需要函数对象的地方,用于构造匿名函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24def make_incrementor(n):
"""docstring for make_incrementor"""
return lambda x: x + n
func = make_incrementor(42)
print func(0)
print func(1)
#42
#43
#单参数
mylambda = lambda x: x * 2
print mylambda(3)
#6
#多参数
mylambda = lambda x, y, z: (x - y) * z
print mylambda(3, 1, 2)
#4
#结合map()函数
mylist = [2, 4, 6, 8, 10]
print map(lambda x: x * 2 + 10, mylist)
#[14, 18, 22, 26, 30]
五、数据结构
列表
列表常用方法
list.append(x)
:添加一个元素到列表的末尾list.extend(list2)
:将指定列表中的所有元素附加到另一个列表的末尾list.insert(i, x)
:在给定位置插入一个元素。第一个参数是插入的元素的索引,所以 a.insert(0, x) 在列表的最前面插入,a.insert(len(a), x) 相当于 a.append(x)list.remove(x)
:删除列表中第一个值为 x 的元素。如果没有这样的元素将会报错list.pop()/list.pop(i)
:删除列表中指定位置的元素并返回它。如果未指定索引,a.pop()将删除并返回列表中的最后一个元素list.index(x)
:返回列表中第一个值为 x 的元素的索引。如果没有这样的元素将会报错list.count(x)
:返回列表中 x 出现的次数list.sort(cmp=None, key=None, reverse=False)
:原地排序列表中的元素list.reverse()
:反转列表中的元素
栈
把列表(List)作为栈使用(后进先出),使用append()
在栈顶添加元素,使用pop()
将栈顶元素取出。
队列
把列表(List)作为队列使用(先进先出),为了能快速在列表开头添加/弹出元素使用collections.deque
快速在列表两端快速添加/弹出元素。1
2
3
4
5
6
7
8
9
10
11from collections import deque
mylist = ['one', 'two', 'three']
queue = deque(mylist)
queue.append('four')
print queue
queue.popleft()
print queue
#deque(['one', 'two', 'three', 'four'])
#deque(['two', 'three', 'four'])
filter()、map()、reduce()
filter()
filter(function, sequence)
,返回function(item)
结果为真的元素组成序列。如果sequence
是一个字符串或元组,结果将是相同的类型;否则,结果将始终是一个列表。1
2
3
4
5#返回不能被2和3整除的序列
def myfunc(x):
return x % 2 != 0 and x % 3 != 0
print filter(myfunc, range(2, 25))
#[5, 7, 11, 13, 17, 19, 23]map()
map(function, sequence)
序列中的每一个元素调用function(item)
函数并返回结果的列表1
2
3
4def cube(x):
return x ** 3
print map(cube, range(1, 11))
#[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]可传多个序列,但传入的函数必须有和序列数目一致的参数,执行时会依次用各序列上对应元素调用函数(若某个序列比另外一个短,就用 None 代替)。
1
2
3
4def add(x, y):
return x + y
print map(add, range(0, 5), range(10, 15))
#[10, 12, 14, 16, 18]map()
函数甚至可用于一列表的函数1
2
3
4
5
6
7
8
9def multiply(x):
return x * x
def add(x):
return x + x
funcs = [multiply, add]
for each_num in xrange(3):
print map(lambda x: x(each_num), funcs)reduce()
reduce(function, sequence)
只返回一个值,首先以序列的前两个元素调用函数function
,然后再以返回的结果和下一个元素继续调用function
,如此循环。如果序列中只有一个元素,将返回这个元素的值;如果序列为空,则引发异常。为防止序列为空引发异常,可传入蛋三个参数作为初始值,初始值与序列第一个元素调用function
(reduce(function, sequence, 0)
)。1
2
3
4def add(x, y):
return x + y
print reduce(add, range(1, 11))
#55
列表推导式
列表推导式主要用于快速简洁生成列表。列表推导式由括号括起来,括号里面包含一个表达式,表达式后面跟着一个for语句,后面还可以接零个或更多的for或if语句。结果是一个新的列表,由表达式依据其后面的for和if子句上下文计算而来的结果构成([ 表达式 for var in sequence if condition]
)。
以下三种方式输出结果都相同1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#方式一
squares = []
for x in range(10):
squares.append(x ** 2)
print squares
#[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
#方式二
squares = map(lambda x: x ** 2, range(10))
#方式三
squares = [x ** 2 for x in range(10)]
#列表推导式可带if语句
squares = [x ** 2 for x in range(10) if x > 3]
#[16, 25, 36, 49, 64, 81]
嵌套列表推导式
列表推导式中的表达式可以是任何表达式,当然包括表达式是一个列表推导式。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#行列置换
matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
]
print [[row[i] for row in matrix] for i in range(4)]
#[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
#拆分
transposed = []
for i in range(4):
transposed_row = []
for row in matrix:
transposed_row.append(row[i])
transposed.append(transposed_row)
zip()
其实Python有内置函数zip()可实现上面的功能。先说明zip()
函数
zip()
接收多个可迭代对象(list/dict/tuple)作为参数,将这些对象中对应的元素打包成若干个元组(tuple),最后返回这些元组(tuple)为元素所组成的列表(List),若传入参数的长度不等,则返回list的长度和参数中长度最短的对象相同。1
2
3
4
5
6
7list_a = [1, 2, 3]
list_b = [4, 5, 6]
list_c = [7, 8, 9]
print zip(list_a, list_b)
print zip(list_a, list_c)
#[(1, 4), (2, 5), (3, 6)]
#[(1, 7), (2, 8), (3, 9)]使用
zip()
函数实现行列置换1
2
3
4
5
6
7
8
9matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
]
print zip(*matrix)
print map(list, zip(*matrix))
#[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
#[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]参数加上星号表明为此参数为列表参数,即接收多个参数并将这些参数组成一个列表。但当此列表参数接收的是一个列表或元组时,则会对接收的列表进行拆分。
*matrix
参数会被拆分成[1, 2, 3, 4]
、[5, 6, 7, 8]
、[9, 10, 11, 12]
三个参数传递给zip()
函数(zip([1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12])
)
字典
字典(dict)可看做是无序的键:值(key:value)对集合,同一字典内键必须唯一。一个花括号将创建一个空字典{}
,花括号里用,
分隔各个键:值。字典的主要操作都是通过键(key)来进行。
字典有键(key)做索引(序列由数字做索引),键(key)
可是任意不可变类型。若元组只包含字符串、 数字或元组,此元组可以用作key;若元组直接或间接地包含任何可变对象,那么它不能用作key。不能用列表(list)作为key。
key()方法
key()
方法返回字典中所有键(key)组成的列表,列表中键(key)的顺序是随机的。可通过in
关键字检查键(key)是否存在于字典中。1
2
3
4
5
6
7
8
9
10
11
12tel = {'jack': 4098,
'sape': 4139
}
tel['guido'] = 4127
print tel
#{'sape': 4139, 'jack': 4098, 'guido': 4127}
print tel['jack']
#4098
print tel.keys()
#['sape', 'jack', 'guido']
print 'guido' in tel.keys()
#True
dict()方法
dict()
构造函数可直接从键值对序列创建字典。同时字典和列表一样都有推导式。1
2
3
4
5
6print dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
#{'sape': 4139, 'jack': 4098, 'guido': 4127}
print dict(sape=4139, guido=4127, jack=4098)
#{'sape': 4139, 'jack': 4098, 'guido': 4127}
print {x: x ** 2 for x in (2, 4, 6)}
#{2: 4, 4: 16, 6: 36}
遍历技巧
enumerate()
遍历序列时,enumerate()
可同时获得索引和值1
2
3
4
5
6mylist = ['tic', 'tac', 'toe']
for index, value in enumerate(mylist):
print index, value
#0 tic
#1 tac
#2 toezip()
同时遍历多个序列时,zip()
可成对读取元素1
2
3
4
5
6
7
8questions = ['name', 'quest', 'favorite color']
answers = ['lancelot', 'the holy grail', 'blue']
for q, a in zip(questions, answers):
print 'What is your {0}? It is {1}.'.format(q, a)
#What is your name? It is lancelot.
#What is your quest? It is the holy grail.
#What is your favorite color? It is blue.reversed()
反向遍历序列1
2
3for i in reversed(xrange(1, 6)):
print i,
#5 4 3 2 1sorted()
排序序列1
2
3
4
5basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
for f in sorted(set(basket)):
print f,
#apple banana orange pear
#set(basket)去除重复元素iteritems()
遍历字典时,iteritems()
方法可同时获得键和值1
2
3
4
5knights = {'gallahad': 'the pure', 'robin': 'the brave'}
for key, value in knights.iteritems():
print key, value
#gallahad the pure
#robin the brave修改遍历中的序列
若要修改正在遍历中的序列,则需先创建副本(序列循环不会隐式创建副本)。1
2
3
4
5words = ['cat', 'window', 'defenestrate']
for w in words[:]:
if len(w) > 6:
words.insert(0, w)
#['defenestrate', 'cat', 'window', 'defenestrate']
六、模块
模块是包含Python定义和声明的文件。模块中的定义可导入(import)到其他模块中。模块名可通过全局变量__name__
获得。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23#fibo.py
def fib(n):
a, b = 0, 1
while b < n:
print b,
a, b = b, a+b
def fib2(n):
result = []
a, b = 0, 1
while b < n:
result.append(b)
a, b = b, a+b
return result
#main.py
import fibo
fibo.fib(100)
print fibo.fib2(100)
print fibo.__name__
#1 1 2 3 5 8 13 21 34 55 89
#[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
#fibo
import fibo
只会把模块名fibo
导入符号表中,不会将fibo.py
中定义的函数名导入到符号表,故需要通过模块名访问fibo.py
定义的函数。
深入模块
模块可包含可执行语句和函数的定义,它们只在第一次导入时执行。
每个模块拥有自己的私有符号表,模块内定义的所有函数用该私有符号表作为全局符号表。
执行模块
若用以下方式执行fibo
模块,模块中的代码会被执行,但此时模块的__name__
不在是fibo
而是__main__
1
python fibo.py <arguments>
若在fibo.py
模块最后添加以下代码,则fibo.py
只有在作为可执行脚本调用是才执行以下代码,作为模块被导入时不会执行(因为作为模块被导入时__name__
==fibo
)1
2
3
4
5
6
7#fibo.py
if __name__ == "__main__":
import sys
fib(int(sys.argv[1]))
$> python fibo.py 100
1 1 2 3 5 8 13 21 34 55 89
模块搜索路径
若要导入(import)spam
模块,首先会搜索内置模块,然后在sys.path
变量中所给出的目录搜索spam.py
。
sys.path
变量初始值来自以下三个地方(注意优先级)
- 脚本所在的当前目录
- PYTHONPATH
- 与安装相关的默认值
包
包是一种管理 Python 模块命名空间的方式,采用点分模块名称。A.B
表示包A
中名为B
的子模块
若存在一个目录结构如下所示的包1
2
3
4
5
6
7
8
9
10
11
12
13
14sound/ Top-level package
__init__.py Initialize the sound package
formats/ Subpackage for file format conversions
__init__.py
wavread.py
wavwrite.py
...
effects/ Subpackage for sound effects
__init__.py
echo.py
...
filters/ Subpackage for filters
__init__.py
equalizer.py
为了Python能将目录当做包,目录下必须存在__init__.py
文件。__init__
文件可以是一个空文件,也可以为包执行初始化代码或设置__all__
变量。
对于不同的导入方式,如何使用模块中的内容也不同1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#假设echo.py模块中定义了函数echofilter(input, output)
#单独导入
import sound.effects.echo
#调用时必须使用完整名称
import sound.effects.echo.echofilter(input, output)
#导入子模块
from sound.effects import echo
#直接使用子模块
echo.echofilter(input, output)
#直接导入子模块函数/变量
from sound.effects.echo import echofilter
#直接使用函数/变量
echofilter(input, output)
七、输入输出
格式化输出
1 | #str.format() 格式化输出 |
读写文件
一般Python读写文件是先调用open(filename, mode)
函数创建一个文件对象,其中mode
有如下选项:
r
: 以读方式打开文件,可读取文件信息.文件必须已存在w
: 以写方式打开文件,可向文件写入信息。存在则清空,不存在创建a
: 以追加方式打开文件,文件指针自动移到文件尾。追加r+
: 以读写方式打开文件,可对文件进行读和写操作。w+
: 消除文件内容,然后以读写方式打开文件。a+
: 以读写方式打开文件,并把文件指针移到文件尾。b
: 以二进制模式打开文件,而不是以文本模式。该模式只对Windows或Dos有效,类Unix的文件是用二进制模式进行操作的U
: 通用换行符支持,任何系统下的文件, 不管换行符是什么, 使用U模式打开时, 换行符都会被替换为NEWLINE(\n)+
: 代表同时作为输入和输出文件,可以对相同文件进行读写b
: 代表二进制数据处理 和 r/w/a组合.r+ 使用读写方式打开, rb二进制读
使用open(filename, mode)
方法创建对象,操作完后需要手动调用close()
方法关闭以释放其所占用的系统资源。为了更加方便对文件进行读写操作Python提供了with关键字,with关键字在文件操作完后会自动关闭对象不用手动调用close()
。1
2with open(filename, 'r') as fd:
fd.read()
Python读取文件内容常用函数有:read()
、readline()
、readlines()
和xreadlines()
read()
:一次性读取所有内容并将其放到一个字符串变量中readline()
:逐行读取并存入列表中readlines()
:一次性读取所有内容并存入列表xreadlines()
:返回一个迭代器用于循环操作每一行
Python2.3后支持文件对象迭代功能,所以更推荐使用以下方法遍历文本:1
2
3with open('filename') as file_fd:
for each_line in file_fd:
do_things(each_line)
八、错误与异常
处理异常
使用try...except
处理异常1
2
3
4
5
6while True:
try:
x = int(raw_input("Please enter a number: "))
break
except (RuntimeError, TypeError, NameError, ValueError):
print "Oops! That was no valid number. Try again..."
异常处理流程:
- 执行try子句(try和except关键字之间的语句,即
x = int(raw_input("Please enter a number: "))
)。 - 如果未发生任何异常,忽略except子句且try语句执行完毕,无触发异常。
- 如果在try子句执行过程中发生异常,跳过该子句的其余部分。如果异常的类型与except关键字后面的异常名匹配, 则执行 except 子句,然后继续执行try语句之后的代码。
- 如果异常的类型与 except 关键字后面的异常名不匹配,它将被传递给上层的try语句;如果没有找到处理这个异常的代码,它就成为一个未处理异常,程序会终止运行并显示一条如上所示的信息。
引发异常
使用raise
可手动引发异常1
2
3
4
5try:
raise NameError('HiThere')
except NameError:
print 'An exception flew by!'
raise
自定义异常
继承Exception
类创建自定义异常1
2
3
4
5
6
7
8
9
10class MyError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
try:
raise MyError(2*2)
except MyError as e:
print 'My exception occurred, value:', e.value
else 与 finally
try...except
还支持else
和finally
语句。
else
:当try
子句中没有抛出异常时则执行else
子句。else
子句只能出现在所有except
子句之后finally
:不管异常是否发生,总是会执行finally
子句。通常用于释放外部资源
1 | def divide(x, y): |
九、类
命名空间
命名空间是从名称到对象的映射。目前为止,命名空间主要通过Python字典实现,键是变量名,值是对于的变量值。
命名空间例子:
- 内置命名空间:Python内置的函数和异常等,任何模块均可访问。
- 全局命名空间:每个模块所拥有的命名空间,记录模块的变量,包括函数、类和导入的模块等。模块的全局命名空间在读入模块定义时创建。
- 局部命名空间:每个函数所拥有的命名空间,记录函数内的变量和参数等。函数的局部命名空间在函数被调用时创建,在函数返回或引发内部没有处理的异常时被删除。
不同命名空间中的名称(如函数名)无任何关系(即使是名称相同也可以)。比如A模块中存在一个函数名为maximize
的函数,B模块同样也存在是完全没问题的,调用是需指定模块名A.maximize
、B.maximize
。
Python查找命名空间的顺序:
- 当前函数的局部命名空间搜索
- 父函数的局部命名空间搜索
- 模块的全局命名空间搜索
- 内置命名空间搜索
类定义
类定义最简单形式1
2
3
4
5class ClassName:
<statement-1>
.
.
<statement-N>
进入类定义后,会创建一个新的命名空间,所有的赋值会成为此命名空间的局部变量。类定义正常结束后,一个类对象便创建了。
类对象
类对象支持两种操作:
- 属性引用
- 实例化
为方便解释,先定义一个类MyClass
:1
2
3
4
5class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
属性引用
属性引用标准语法为obj.name
。有效的属性名为在类对象被创建时该类的命名空间中的所有名称。
MyClass.i
、MyClass.f为有效的属性引用,分别返回一个整数和一个函数对象。__doc__
也是有效对象,返回一串字符(A simple example class
)
实例化
类的实例化使用函数符号。可以把类对象看成是一个无参数的函数,该函数返回这个类的一个新实例。
创建MyClass
类的一个新实例,并将该对象赋给局部变量x
1
x = MyClass()
实例化操作会创建一个空对象,若希望创建的对象在初始状态时能自定义动作,则可定义一个__init__()
的特殊方法
一旦定义了__init__()
方法,在实例化创建新的类对象时会自动调用__init__()
方法。__init()
方法可带参数,在类实例化操作的参数将传递给__init__()
方法1
2
3
4
5
6
7class MyClass(object):
def __init__(self, name):
self.name = name
x = MyClass('mogl')
print x.name
#mogl
当通过实例化操作创建了一个实例对象后,可进行属性引用。有效的属性名大致分为两种:数据属性和方法
简单的说,数据属性可理解为类当中的变量;方法可理解为类当中的函数
方法对象
一般情况下,方法被绑定后就可直接调用。以之前定义的类MyClass
为例,在定义完类后,实例化创建类对象后可直接调用方法1
2
3
4
5
6
7
8
9#实例化创建类对象x
x = MyClass()
#直接调用方法f()
x.f()
#不直接调用:x.f是一个方法对象,可存储后调用
xf = x.f
xf()
类里面的函数def f(self)
是有参数的,但调用时却没有传递参数,这和类的方法有关。方法会将实例对象作为函数的第一个参数传递给函数,所以方法定义时带有self
参数。当调用x.f()
时等同于MyClass.f(x)
。
类 & 实例
可变对象不做类属性
实例变量用于对每一个实例都是唯一的数据
类变量用于类的所有实例共享的属性和方法
可变对象(列表/字典)不应用作为类变量,因为所有实例化后的类对象都共享同一个可变的类变量1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Dog(object):
tricks = [] #tricks作为类变量
def __init__(self, name):
self.name = name
#self.tricks = [] #应当将tricks作为实例变量
def add_tricks(self, trick):
"""docstring for add_tricks"""
self.tricks.append(trick)
d = Dog('Fido')
e = Dog('Buddy')
d.add_tricks('roll over')
e.add_tricks('play dead')
print d.tricks
#['roll over', 'play dead']
类属性 实例属性
Python是动态语言,类实例化后的实例可任意添加属性。如果实例属性和类属性具有相同的名称时,实例属性将屏蔽掉类属性。
Python默认是通过一个字典来保存实例的所有实例属性(通过instance.__dict__
可查看)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Man(object):
"""docstring for Man"""
age = 30
a = Man()
b = Man()
print a.age, b.age, Man.age
#30 30 30
#实例a添加实例属性age
a.age = 40
print a.age, b.age, Man.age
#40 30 30 #a实例属性age覆盖Man类属性
Man.age = 50
print a.age, b.age, Man.age
#40 50 50
print a.__dict__
print b.__dict__
print Man.__dict__
#{'age': 40}
#{}
#{'__dict__': <attribute '__dict__' of 'Man' objects>, 'age': 50, '__weakref__': <attribute '__weakref__' of 'Man' objects>, '__module__': '__main__', '__doc__': 'docstring for Man'}
slots
一般使用__slots__
常见的目的有2个:
- 减少内存的使用
正如上面所说,Python默认会使用字典存储实例的所有实例属性。但一旦需要创建大量实例时,每个实例都会产生一个字典用于存储其自身的实例属性,这样便会占用过多的内存资源。__slots__
能使Python不使用字典而且只提供一个固定集合的属性空间,所有的实例都用此空间。 - 限制添加新属性
为了限制实例添加新属性,可使用__slots__
来限制该类能使用的属性。
1 | class Man(object): |
继承
类的其中一个重要特性是继承。1
2
3
4class DerivedClassName(BaseClassName):
<statement-1>
.
<statement-N>
子类的类对象创建后,父类会被保存用于属性引用,当在子类中无法获取请求的属性时会向父类进行搜索。假若父类又是其他类的子类,那么会再向其父类进行搜索,不对递归进行。
子类能重写父类的方法。若在子类中想要调用父类方法可BaseClassName.methodname(self, arguments)
多继承
Python支持一定限度的多继承形式。对于多继承使用深度优先规则,从左到右进行搜索。当自Python2.3起采用C3算法,按照MRO(Method Resolution Order)进行搜索,可使用DerivedClassName
中找不到属性时,首先搜索Base1
,然后递归搜索Base1
的父类,在然后搜索Base2
以此类推进行搜索。Class.__mro__
查看MRO的搜索顺序。1
2
3
4
5
6class DerivedClassName(Base1, Base2, Base3):
<statement-1>
...
<statement-N>
print DerivedClassName.__mro__
迭代器
在Python中利用for
语句可方便的遍历很多对象,迭代器是这一用法的关键。for
语句在对象上调用iter()
,iter()
函数返回定义了next()
方法的迭代器对象用于逐一访问所有元素,当没有后续元素时,next()
方法引发StopIteration
异常通知for
终止循环。1
2
3
4
5
6
7
8
9
10
11
12>>> s = 'abc'
>>> it = iter(s)
>>> it.next()
'a'
>>> it.next()
'b'
>>> it.next()
'c'
>>> it.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
假若需要自定义类遍历,则只需要定义一个__iter__(self)
方法并返回含有next(self)
方法的对象即可,如果类中定义了next(self)
方法则__iter__(self)
可只返回self
Python使用内置的iter()
函数,iter()
函数通过调用__iter__(self)
方法获得对象的迭代器。有了迭代器才能遍历每个元素,遍历时Python使用内置next()
函数,next()
函数通过调用next(self)
方法对迭代器对象进行遍历。__iter__(self)
只会被调用一次来获取迭代器,next(self)
会被调用多次以遍历每个元素(next(self)
方法中需要注意设置结束条件来触发raise StopIteration
以避免死循环)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Reverse(object):
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def next(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
rev = Reverse("spam")
for char in rev:
print char
生成器
官方文档中对生成器解释为:生成器是创建迭代器的一种简单而强大的工具。写起来像正规的函数,只是需要在返回数据时使用yield
语句而非return
语句。每次调用next()
时,生成器会返回之前的状态并继续往下执行代码。
在我个人看了,生成器就是一个特殊的迭代器,只不过生成器会自动的创建__iter__()
方法和next()
方法,所以比起自己实现迭代器类简洁方便。另一个特殊点是生成器会自动保存局部变量和执行状态,生成结束时自动抛出StopIteration
异常(遇到return
会抛出StopIteration
异常)。
简而言之,一个带有yield
语句的函数就是一个生成器。当调用生成器时并不会执行任何函数里的代码,只有在调用next()
函数时(for
自动调用next()
函数)时才开始执行函数中的代码。但代码执行的流程不是像普通函数一样一次性从头到尾执行,而是当遇到yield
语句时产生中断并返回迭代值,然后保存当前执行状态,直到再次调用next()
函数时才恢复上次中断状态并从上次yield
语句的下一句继续执行。(yield
保存状态并暂停函数的执行,next()
从其暂停处恢复并继续往下执行函数代码。)1
2
3
4
5
6
7
8
9
10
11def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
#print data[index] #普通函数写法
#reverse('spam') #普通函数调用
#生成器可迭代调用
#即可迭代又免于class Reverse()的繁复
for char in reverse('spam'):
print char
关于yield
和生成器更多内容可参考如下几篇文章
生成器表达式
生成式表达式类似与一个yield
值的匿名函数。生成式表达式本身看起来像列表表达式, 但不是用方括号而是用圆括号包围起来:1
2
3
4>>> mylist = ['one', 'two', 'three']
>>> gen = (x for x in mylist)
>>> gen
<generator object <genexpr> at 0xb70a116c>
协程 & yield
协程又可称为微线程(coroutine),进程、线程需要由系统来进行调度,而协程是在代码里显示调度的,主动让出CPU时间,因此避免了进程/线程切换时的开销能充分利用并发优势,但协程是针对单个CPU的是一个线程执行的。
Python2中使用yield
来实现不完全的协程。yield
不仅能从生成器内返回状态而且还能从外部传递信息给生成器内部,一般的做法是通过将yield
关键字赋值给变量,并调用生成器固有的send()
方法将消息传入生成器内部。生成器在遇到next()
方法时才会继续执行而send()
方法能起到和next()
方法同样的作用,其实next()
相当于是send(None)
。如果没有变量接收yield
关键字,那么send()
传递的消息将被丢弃。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26def consumer():
thanks = 'Thanks'
while 1:
food = yield thanks
print "consumer get %s" % food
def product():
c = consumer()
#先调用next()让consumer()执行到yield等待接收food
first_rep = c.next()
print "first repose: %s" % first_rep
for food in ['rice', 'soup', 'meat']:
response = c.send(food)
print "product get response: %s" % response
c.close()
product()
#first repose: Thanks
#consumer get rice
#product get response: Thanks
#consumer get soup
#product get response: Thanks
#consumer get meat
#product get response: Thanks
consumer()
是一个生成器,在product()
中创建对象后调用c.next()
启动生成器product()
生产food
后通过c.send(food)
将消息传递给consumer()
,程序切换到consumer()
中继续执行。consumer()
通过yield
获取product()
传入的消息,处理后(print
)又通过yield
返回结果(thanks
)- 最后调用
c.close()
关闭协程
super()
在类的继承中,子类会在初始化时调用__init__(self)
方法,会将self
和该子类的实例对象绑定而忽视父类。当使用子类实例对象调用父类的属性或方法时便会提示该属性/方法不存在。为了避免此问题的出现,就会在子类(新式类)中使用super(subclass, self).__init__()
(Python2写法)(super()
是在Python2.2之后新式类中才支持的,之前的Python版本的经典类只能用父类名调用)。
一般提到super()
便会很自然的和父类联系起来,但其实super()
和父类没有必然的联系。super()
指向的本质上是MRO的下一个类,MRO的下一个类并不一定就是父类。super()
的实现大概如下:1
2
3def super(class, instance):
mro = instance.__class__.mro()
return mro[mro.index(class) + 1]
instance
:此参数是用于获得instance
实例的MRO(Method Resolution Order)class
:此参数主要用于定位当前类在MRO的位置
为了说明super()
指向的是MRO的下一个类,而MRO的下一个类并不一定是父类。请看如下例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31class A(object):
def __init__(self):
print "In Class A"
class B(A):
def __init__(self):
print "In Class B. Before super()"
super(B, self).__init__()
print "In Class B. After super()"
class C(A):
def __init__(self):
print "In Class C. Before super()"
super(C, self).__init__()
print "In Class C. After super()"
class D(B, C):
pass
d = D()
print d.__class__.mro()
#In Class B. Before super()
#In Class C. Before super()
#In Class A
#In Class C. After super()
#In Class B. After super()
#[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>]
先记住实例d
的MRO为[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>]
,此段代码的执行流程如下:
- 类D实例化
D()
。当类在进行实例化是会自动调用方法__init__(self)
,于是尝试调用类D的__init__(self)
方法,发现类D没有__init__(self)
方法,根据类继承规则,会查找类B中是否存在__init__(self)
方法,发现类B有此方法于是执行类B的__init__(self)
方法,打印In Class B. Before super()
- 执行类B
__init__(self)
方法中的第二句super(B, self).__init__()
。根据super()
的实现本质:self
:super(B, self)
,通过self
参数获得实例的MRO。由于已实例化,此self
表示的是实例对象d
,于是获得d
的MRO:D->B->C->A
。B
:super(B, self)
,通过B
参数获得当前类在MRO的位置并返回此位置的下一个类C
。
综上,super(B, self).__init__()
其实等价于C.__init__()
,于是执行类C的__init__()
方法,打印In Class C. Before super()
假若super()
总是指父类的话,那么super(B, self).__init__()
便会等价于A.__init__()
方法,便会打印”In Class A”。然而实际并不是,所有再次强调super()
指的是MRO中的下一个类而非父类。
- 执行类C
__init__(self)
方法的第二句super(C, self).__init__()
。简而言之,self
指d
,C
指当前类位置,返回A
。执行A.__init__()
,打印In Class A
- 沿路返回执行完剩下的语句
十、装饰器
装饰器在Python中是个比较重要的东西,Stackoverflow上有个比较详尽的解答——Decorator
Python为了能简洁明了地使用装饰器,专门有个语法糖用于装饰器的使用。一般Python中使用装饰器如下所示:1
2
3@decorator
def myfunc():
#code
实际上这是一种”简写”,完整的形式大概如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17from functools import wraps
#定义一个装饰器
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
#some code
return func(*args, **kwargs)
return wrapper
#被装饰函数
def myfunc():
#code
myfunc = decorator(myfunc)
首先定义了一个装饰器函数decorator(func)
,将被装饰函数作为参数传入装饰器函数中decorator(myfunc)
,最后将装饰器函数decorator(func)
的返回值赋值回给原来的被装饰函数对象myfunc
。因为需要赋值回给原来的函数,所以装饰器函数decorator(func)
的返回值需是一个函数对象wrapper
。
将装饰器函数decorator(func)
返回值赋值回给原来的函数对象myfunc
这种做法会引发一个问题。被装饰函数myfunc()
被重新赋值后其实已经是另外一个函数wrapper(*args, **kwargs)
了。原来myfunc()
的__name__
和docstring
已经被重写了。当执行print myfunc.__name__
时所得到的结果是wrapper
而非所期待的myfunc
。为了解决这个问题,Python提供了一个简单的函数functools.wraps
。其实这也是个装饰器函数,于是就有了@wraps(func)
。
累加装饰器
装饰器可多个进行累加,使用Stackoverflow那篇回答中的三明治例子就能很生动形象的加以解释,在火腿上添加面包和佐料两个装饰器。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36from functools import wraps
def bread(func):
"""面包装饰器(用于添加面包)"""
@wraps(func)
def wrapper():
print "</''''''\>"
func()
print "<\______/>"
return wrapper
def ingredients(func):
"""佐料装饰器(用于添加番茄和沙拉)"""
@wraps(func)
def wrapper():
print "#tomatoes#"
func()
print "~salad~"
return wrapper
@bread
@ingredients
def sandwich(food="--ham--"):
print food
#sandwich = bread(ingredients(sandwich))
sandwich()
#</''''''\>
##tomatoes#
#--ham--
#~salad~
#<\______/>
装饰器的参数传递
向装饰器内的装饰函数传递参数
Python中可以使用*args
和\*\*kwargs
,将被装饰函数中的参数myfunc('mogl')
传递给装饰器函数decorator(func)
中的装饰函数wrapper(*args, **kwargs)
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print "get args", args, kwargs
func(*args, **kwargs)
return wrapper
@decorator
def myfunc(name):
print "My name is", name
myfunc('mogl')
#get args ('mogl',) {}
#My name is mogl
向装饰器传递参数
向装饰器本身传递参数,可通过包裹函数实现。先创建包裹函数并在包裹函数内创建装饰器,然后再将参数传递给包裹函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23from functools import wraps
def decorator_arguments(name): #用于接收传递给装饰器的参数(接收参数name)
def decorator(func): #实际装饰器,用于接收被装饰函数(接收参数被装饰函数对象)
print "decorator_arguments: %s" % name
@wraps(func)
def wrapper(*args, **kwargs): #装饰函数,接收被装饰函数自带的参数/添加装饰
print "In wrapper(), args: %s, kwargs: %s" % (args, kwargs)
return func(*args, **kwargs)
return wrapper
return decorator
@decorator_arguments('mogl')
def myfunc(full_name):
pass
myfunc('moguoliang')
#decorator_arguments: mogl
#In wrapper(), args: ('moguoliang',), kwargs: {}
装饰器类
为了使得装饰器有继承的特性,一般可用类来构建装饰器,以类而非函数的方式构建装饰器。
一个类装饰器主要有两个成员方法__init__()
和__call__()
__init__()
:给某函数进行装饰时被调用__call__()
:调用被装饰的函数时__call__()
被调用
__call__()
是Python提供的一个方法,它可让类实例的行为表现得想函数一样(可调用/可将函数作为参数传递给另一函数)。简而言之,即使得x()
等价与x.__call__()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53from functools import wraps
#装饰器类
class Decorator(object):
def __init__(self, name='mogl'):
self.name = name
def __call__(self, func):
print "In Decorator.__call__(), name: %s" % self.name
@wraps(func)
def wrapper(*args, **kwargs):
print "In wrapper(), args: %s, kwargs: %s" % (args, kwargs)
self.extend()
return func(*args, **kwargs)
return wrapper
def extend(self):
pass
#演示类的继承特性
class DecoratorSubclass(Decorator):
def __init__(self, email, *args, **kwargs):
super(DecoratorSubclass, self).__init__(*args, **kwargs)
self.email = email
def extend(self):
print "DecoratorSubclass argument email=%s" % self.email
print "In DecoratorSubclass.extend()"
@Decorator()
def myfunc(fullname):
pass
myfunc('moguoliang')
@DecoratorSubclass('fatesai@gmail.com')
def myfunc2(fullname):
pass
myfunc2('moguoliang')
#In Decorator.__call__(), name: mogl
#In wrapper(), args: ('moguoliang',), kwargs: {}
#In Decorator.__call__(), name: mogl
#In wrapper(), args: ('moguoliang',), kwargs: {}
#DecoratorSubclass argument email=fatesai@gmail.com
#In DecoratorSubclass.extend()
十一、上下文管理器
上下文管理器(Context Manager)是Python2.5开始支持,规定在进入或离开特定代码块时会执行特殊的操作。最普遍的用法是对文件的操作,使用with
语句自动关闭打开的文件。
在对文件进行操作后,需要关闭打开的文件但经常会忘记导致文件描述符一直累积占用资源。上下文管理器可在文件操作结束后自动执行关闭文件的操作。1
2
3
4
5
6
7
8
9
10#使用try...finally实现
try:
f = open("test.txt", "r")
print f.read()
finally:
f.close()
#使用上下文管理器
with open("test.txt", "r") as f:
print f.read()
上下文管理器的实质
一个上下文管理器对象至少要实现__enter__()
和__exit__(type, valve, traceback)
方法。以wiht
语句为例子大致了解上下文管理器的执行过程:
- 遇到
with
关键字先加载__exit__()
方法以备用调用 - 执行
with
关键字后的语句open("test.txt", "r")
获取一个文件对象的上下文管理器 - 执行
__enter__()
方法,方法的返回值将传递给as
后的f
变量 - 执行
with
代码块的子句print f.read()
- 执行
__exit__()
,若with
代码块子句有异常则将type, value, traceback
传递给__exit__()
,无异常则传None
;__exit__()
接收值后若返回False
则将异常抛出交由外层处理,返回True
则忽略异常。
自定义上下文管理器
基于类实现上下文管理器
基于类的实现最简单的方式就是直接定义好__enter__(self)
方法和__exit__(self, type, value, traceback)
方法。1
2
3
4
5
6
7
8
9
10
11
12class FileContextManage(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, traceback):
self.file_obj.close()
with FileContextManage('test.txt', 'r') as fd:
print fd.read()
基于生成器实现上下文管理器
Python有contextlib
模块专门用于使用生成器来实现上下文管理器。1
2
3
4
5
6
7
8
9
10from contextlib import contextmanager
@contextmanager
def file_context_manage(file_name, method):
fd = open(file_name, method)
yield fd
fd.close()
with file_context_manage('test.txt', 'r') as fd:
print fd.read()
contextmanager
装饰器返回GeneratorContextManager
对象,即file_context_manage
函数被装饰后实际上是一个GeneratorContextManager
对象。yield
只能返回一个值,此值相当于__enter__()
的返回值,yield
后的语句相当于__exit__()
方法内的语句。
十二、描述符
描述符是Python2.2之后引入的,描述符其属性的访问被描述符协议方法覆写,即描述符将对象属性的获取、赋值和删除转化为调用描述符协议方法。描述符协议方法有三个:
__get__(self, instance, owner) --> return value
__set__(self, instance, value) --> return None
__delete__(self, instance) --> return None
简而言之,描述符就是实现了描述符协议方法的对象(至少实现__get__()
、__set__()
和__delete__()
方法的其中一个即可),描述符对属性的访问是通过调用描述符协议方法的。
根据实现的描述符协议方法的多少又可分成:
- 数据描述符(data descriptor):实现了
__get__()
和__set__()
方法 - 非数据描述符(non-data descriptor):仅实现
__get__()
方法
获取属性
对于描述符还有几点需要注意:
- 描述符只能在类级别上进行合法定义,而不能在实例级别上定义,即描述符只能是类属性而不能是实例属性。
- 数据描述符(data descriptor)不能被实例属性覆盖,即数据描述符对同名实例属性(非描述符)有屏蔽作用。非数据描述符(non-data descriptor)和普通类属性相同会被实例属性覆盖。换句话说这是数据描述符和非数据描述符之间的最主要区别是:相对于实例字典的优先级不同。如果实例字典中有与描述器同名的属性,如果描述器是资料描述器,优先使用资料描述器,如果是非资料描述器,优先使用字典中的属性。
在没有描述符之前获取属性相对较简单明了,但加入了描述符后就会变得稍微复杂,为方便对比来看一下没有描述符和有描述符的属性获取过程:1
2
3
4
5
6
7
8class MyClass(object):
class_attr = "Class attribute"
def class_method(self):
print "Class method"
my_instance = MyClass()
print my_instance.class_attr
不考虑描述符情况(Python2.2之前)
Python对象一般都是通过__dict__
字典动态管理对象的所有属性。
Python的属性搜索都是从MRO自下向上搜索
当通过my_instance.class_attr
方式访问属性时,无描述的情况大致如下:- 首先在实例对象
my_instance
的__dict__
内查找是否存在属性class_attr
,若存在则直接返回。 - 若不存在则在MRO链上的下一个类的
__dict__
查找,找到则返回否则继续在MRO链上的下一个类上查找。直到最后都没找到则返回异常。
- 首先在实例对象
考虑描述符情况(Python2.2之后)
对于实例对象而言,通过.
方式获取属性my_instance.class_attr
实际上是调用了object.__getattribute__()
方法(每次引用属性或方法时都会无条件的调用__getattribute__()
),而__getattribute__()
方法又会根据对象或类调用不同的__get__()
方法- 对象:
obj.x
,调用type(obj).__dict__['x'].__get__(obj, type(obj))
类:
class.x
,调用type(class).__dict__['x'].__get__(None, type(class)
最终
my_instance.class_attr
会被解析成type(my_instance).__dict__['class_attr'].__get__(my_instance, type(my_instance))
——>MyClass.__dict__['class_attr'].__get__(my_instance, MyClass)
通过my_instance.class_attr
方式获取属性时,考虑描述符情况的大致过程如下:
- 查找
MyClass
中是否覆写方法__getattribute__()
。若覆写则返回MyClass.__getattribute__(my_instance, 'class_attr')
;否则执行2
- 依次在
MyClass.__mro__
链上的类的__dict__
中查找class_attr
- 完全没找到
class_attr
,查找MyClass
中是否存在__getattr__()
方法,存在则调用MyClass.__getattr__(my_instance, 'class_attr')
获取实例中的class_attr
属性。若不存在__getattr__()
或__getattr__()
方法获取实例属性失败(实例无此属性)都将抛出AttributeError
异常。 - 找到第一个
class_attr
- 3 判断
class_attr
是否是数据描述符- 是:返回
Descr.__get__(class_attr, my_instance, MyClass)
- 否:查找实例的
__dict__
是否存在class_attr
,存在则返回并结束;不存在则执行4
。(存在之所以直接结束是因为实例属性可覆盖非数据描述符(4
)和类属性5
)
- 是:返回
- 4 判断
class_attr
是否是非数据描述符- 是:返回
Descr.__get__(class_attr, my_instance, MyClass)
- 否:执行
5
- 是:返回
- 5
class_attr
为普通类属性且my_instance
不存在该实例属性(3
-否),返回类属性class_attr
- 3 判断
- 完全没找到
- 对象:
设置属性
引入了描述符后设置属性my_instance.class_attr = 'test'
的大致过程如下:
- 查找
MyClass
类中是否覆写方法__setarrt__()
。若覆写则返回MyClass.__setattr__(self, 'class_attr', value)
;否则执行2
- 依次在
MyClass.__mro__
链上的__dict__
中查找class_attr
对于第一个找到的class_attr
,若class_attr
是数据描述符(data descriptor),则返回Descr.__set__(attr, my_instance, value)
结束;否则则意味着class_attr
是非数据描述符或类属性或找不到class_attr
- 在实例
my_instance
的__dict__
设置class_attr
属性
删除属性
引入了描述符后删除属性del my_instance.class_attr
的大致过程如下:
- 查找
MyClass
类中是否覆写方法__delarrt__()
。若覆写则返回MyClass.__delattr__(self, 'class_attr')
;否则执行2
- 依次在
MyClass.__mro__
链上的__dict__
中查找class_attr
对于第一个找到的class_attr
,若class_attr
是定义了__delete__
的描述符,则返回Descr.__delete__(attr, my_instance)
结束;否则则意味着class_attr
是没定义__delete__()
的描述符或类属性或找不到class_attr
- 若实例
my_instance
中存在属性class_attr
则删除,否则抛出AttributeError异常
十三、元类
元类(metaclass)是用来创建类(class)的,即元类(metaclass)是类的类。也就是说元类(metaclass)的实例化是类(class),类(class)的实例化是类实例对象(object)。1
2class = metaclass() #元类(metaclass)创建类(class)
instance = class() #类(class)创建实例(object)
在Python中默认使用type这个内建的元类(metaclass)来创建所有的类(class)。可使用type函数创建类,规定格式如下:1
type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
所以使用class
关键字创建的类其实和调用type
函数创建的类是一样的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#例子1
class MyClass(object):
pass
MyClass = type('MyClass', (), {})
#例子2
class MyClass(object):
name = 'mogl'
MyClass = type('MyClass', (), {'name': 'mogl'})
#例子3
class MyClass(object):
pass
class MySubClass(MyClass):
sub_name = 'mogl_child'
MySubClass = type('MySubClass', (MyClass,), {'sub_name': 'mogl_child'})
__metaclass__
__metaclass__
用于指明该类在创建时使用的元类,若没有__metaclass__
则默认使用type
来创建类,所以我们可以使用__metaclass__
来自定义元类(metaclass)。
当使用关键字class
创建类时,Python会做如下操作:1
2class MyClass(object):
__metaclass__ = xxxxx
- 遇到
class
关键字,class MyClass(object)
说明需要创建一个名为MyClass
的类 - Python在
MyClass
类定义中寻找是否存在__metaclass__
属性,Python对__metaclass__
的搜索按照class.__metaclass__ -> bases.__metaclass__ -> module.__metaclass__ -> type
顺序进行,存在则使用__metaclass__
指定的元类来创建类MyClass
,若最终都没有__metaclass__
则使用内建元类type
创建。
自定义元类
使用类来构建元类
使用类来构建元类一般都是继承type,即使用type作为父类然后覆写__new__()
方法并返回
__new__()
是在__init__()
之前就会被调用的特殊方法, 用来创建对象并返回。
__new__(self_class, future_class_name, future_class_parents, future_class_attr)
方法接收到的参数依次是:当前准备创建的类的对象; 类的名字; 类继承的父类集合; 类的方法集合1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class MyMetaClass(type):
def __new__(self_class, future_class_name, future_class_parents, future_class_attr):
print "self_class:%s, future_class_name:%s, future_class_parents:%s, future_class_attr:%s" % (self_class, future_class_name, future_class_parents, future_class_attr)
return type.__new__(self_class, future_class_name, future_class_parents, future_class_attr)
def __init__(self_class, future_class_name, future_class_parents, future_class_attr):
print "class: %s" % self_class
type.__init__(self_class, future_class_name, future_class_parents, future_class_attr)
class MyClass(object):
"""docstring for MyClass"""
__metaclass__ = MyMetaClass
name = 'mogl'
my_instance = MyClass()
print my_instance.__class__
#self_class:<class '__main__.MyMetaClass'>, future_class_name:MyClass, future_class_parents:(<type 'object'>,), future_class_attr:{'__module__': '__main__', '__metaclass__': <class '__main__.MyMetaClass'>, '__doc__': 'docstring for MyClass', 'name': 'mogl'}
#class: <class '__main__.MyClass'>
#<class '__main__.MyClass'>
使用函数构建元类
除了使用类来构建元类外,也可以使用函数来构建元类,这需要使用到type
函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17def my_metaclass(class_name, class_parents, class_attrs):
my_type = type(class_name, class_parents, class_attrs)
my_type.name = "mogl"
return my_type
class MyClass(object):
"""docstring for MyClass"""
__metaclass__ = my_metaclass
print MyClass.__metaclass__
print MyClass.__class__
print MyClass.name
#<unbound method MyClass.my_metaclass>
#<type 'type'>
#mogl