布尔值
布尔值可以用and、or和not运算。
and运算是与运算,只有所有都为 True,and运算结果才是 True。
or运算是或运算,只要其中有一个为 True,or 运算结果就是 True。
not运算是非运算,它是一个单目运算符,把 True 变成 False,False 变成 True。
空值是Python里一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值。
定义动态变量
(较类似指针#具体内容可以查看这个网址从C/C++软件工程师角度看Python的动态类型)
a=1
print (a)
同一个变量可以反复赋值,而且可以是不同类型的变量
当我们写:a = 'ABC'时,Python解释器干了两件事情:
在内存中创建了一个'ABC'的字符串;
在内存中创建了一个名为a的变量,并把它指向'ABC'。
也可以把一个变量a赋值给另一个变量b,这个操作实际上是把变量b指向变量a所指向的数据
这种变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。
静态语言
静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。
python字符串
Python字符串用\进行转义。
要表示字符串 Bob said "I'm OK".
'Bob said \"I\'m OK\".'
\n 表示换行
\t 表示一个制表符
\ 表示 \ 字符本身
注意:转义字符 \ 不计入字符串的内容中。
如果一个字符串包含很多需要转义的字符,对每一个字符都进行转义会很麻烦。为了避免这种情况,我们可以在字符串前面加个前缀 r ,表示这是一个 raw 字符串,里面的字符就不需要转义了。
例如:
r'\(~_~)/ \(~_~)/'
但是r'...'表示法不能表示多行字符串,也不能表示包含'和 "的字符串
如果要表示多行字符串,可以用'''...'''表示:
'''Line 1
Line 2
Line 3'''
上面这个字符串的表示方法和下面的是完全一样的:
'Line 1\nLine 2\nLine 3'
还可以在多行字符串前面添加 r ,把这个多行字符串也变成一个raw字符串,但是要注意'''之后没有空格即使是这样(r''''xx''''):
r'''Python is created by "Guido".
It is free and easy to learn.
Let's start learn Python in imooc!'''
Unicode
Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。
python3默认unicode,so,不用u'..'
Python2添加了对Unicode的支持,以Unicode表示的字符串用u'...'表示,
比如:
print u'中文'
中文
注意:python2 不加 u ,中文就不能正常显示
Unicode字符串除了多了一个 u 之外,与普通字符串没啥区别,转义字符和多行表示法仍然有效:
转义:
u'中文\n日文\n韩文'
多行:
u'''第一行
第二行'''
raw+多行:
ur'''Python的Unicode字符串支持"中文",
"日文",
"韩文"等多种语言'''
如果中文字符串在Python环境下遇到 UnicodeDecodeError,这是因为.py文件保存的格式有问题。可以在第一行添加注释
# -*- coding: utf-8 -*-
目的是告诉Python解释器,用UTF-8编码读取源代码。然后用Notepad++ 另存为... 并选择UTF-8格式保存。
and,or,not
在Python中,布尔类型还可以与其他数据类型做 and、or和not运算,
请看下面的代码:
a = True
print a and 'a=T' or 'a=F'
计算结果不是布尔类型,而是字符串 'a=T',这是为什么呢?
因为Python把0、空字符串''和None看成 False,其他数值和非空字符串都看成 True,所以: True and 'a=T' 计算结果是 'a=T' 继续计算 'a=T' or 'a=F' 计算结果还是 'a=T'
要解释上述结果,又涉及到 and 和 or 运算的一条重要法则:
短路计算:
在计算 a and b 时,如果 a 是 False,则根据与运算法则,整个结果必定为 False,因此返回 a;如果 a 是 True,则整个计算结果必定取决与 b,因此返回 b。
在计算 a or b 时,如果 a 是 True,则根据或运算法则,整个计算结果必定为 True,因此返回 a;如果 a 是 False,则整个计算结果必定取决于 b,因此返回 b。
所以Python解释器在做布尔运算时,只要能提前确定计算结果,它就不会往后算了,直接返回结果。
非运算:把True变为False,或者把False变为True:
not True # ==> False
not False # ==> True
List
Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates # 打印classmates变量的内容
['Michael', 'Bob', 'Tracy']
由于Python是动态语言,所以list中包含的元素并不要求都必须是同一种数据类型,我们完全可以在list中包含各种数据:
>>> L = ['Michael', 100, True]
一个元素也没有的list,就是空list:
>>> empty_list = []
-1 索引表示最后一个元素
>>> L = ['Adam', 'Lisa', 'Bart']
>>> print L[-1]
Bart
Bart同学表示躺枪。
类似的,倒数第二用 -2 表示,倒数第三用 -3 表示,倒数第四用 -4 表示:
>>> print L[-2]
Lisa
>>> print L[-3]
Adam
>>> print L[-4]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
L[-4] 报错了,因为倒数第四不存在,一共只有3个元素。
使用倒序索引时,也要注意不要越界。
现在,班里有3名同学:
>>> L = ['Adam', 'Lisa', 'Bart']
今天,班里转来一名新同学 Paul,如何把新同学添加到现有的 list 中呢?
第一个办法是用 list 的 append() 方法,把新同学追加到 list 的末尾:
>>> L = ['Adam', 'Lisa', 'Bart']
>>> L.append('Paul')
>>> print L
['Adam', 'Lisa', 'Bart', 'Paul']
append()总是把新的元素添加到 list 的尾部。
list的 insert()方法,它接受两个参数,第一个参数是索引号,第二个参数是待添加的新元素:
>>> L = ['Adam', 'Lisa', 'Bart']
>>> L.insert(0, 'Paul')
>>> print L
['Paul', 'Adam', 'Lisa', 'Bart']
L.insert(0, 'Paul') 的意思是,'Paul'将被添加到索引为 0 的位置上(也就是第一个),而原来索引为 0 的Adam同学,以及后面的所有同学,都自动向后移动一位。
Paul同学刚来几天又要转走了,那么我们怎么把Paul 从现有的list中删除呢?
要把Paul踢出list,我们就必须先定位Paul的位置。由于Paul的索引是3,因此,用 pop(3)把Paul删掉:
>>> L.pop(3)
'Paul'
>>> print L
['Adam', 'Lisa', 'Bart']
如果Paul同学排在最后一个,我们可以用list的pop()方法删除:
>>> L = ['Adam', 'Lisa', 'Bart', 'Paul']
>>> L.pop()
'Paul'
>>> print L
['Adam', 'Lisa', 'Bart']
pop()方法总是删掉list的最后一个元素,并且它还返回这个元素,所以我们执行 L.pop() 后,会打印出 'Paul'。
假设现在班里仍然是3名同学:
>>> L = ['Adam', 'Lisa', 'Bart']
现在,Bart同学要转学走了,碰巧来了一个Paul同学,要更新班级成员名单,
我们可以先把Bart删掉,再把Paul添加进来。
另一个办法是直接用Paul把Bart给替换掉:
>>> L[2] = 'Paul'
>>> print L
L = ['Adam', 'Lisa', 'Paul']
对list中的某一个索引赋值,就可以直接用新的元素替换掉原来的元素,list包含的元素个数保持不变。
由于Bart还可以用 -1 做索引,因此,下面的代码也可以完成同样的替换工作:
>>> L[-1] = 'Paul'
创建tuple
tuple是另一种有序的列表,中文翻译为“ 元组 ”。tuple 和 list 非常类似,但是,tuple一旦创建完毕,就不能修改了。
同样是表示班里同学的名称,用tuple表示如下:
>>> t = ('Adam', 'Lisa', 'Bart')
创建tuple和创建list唯一不同之处是用( )替代了[ ]。
现在,这个 t 就不能改变了,tuple没有 append()方法,也没有insert()和pop()方法。所以,新同学没法直接往 tuple 中添加,老同学想退出 tuple 也不行。
获取 tuple 元素的方式和 list 是一模一样的,我们可以正常使用 t[0],t[-1]等索引方式访问元素,但是不能赋值成别的元素,
不信可以试试:
>>> t[0] = 'Paul'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
创建包含1个元素的 tuple 呢?来试试:
>>> t = (1)
>>> print t
1
好像哪里不对!t 不是 tuple ,而是整数1。为什么呢?
因为()既可以表示tuple,又可以作为括号表示运算时的优先级,结果 (1) 被Python解释器计算出结果 1,导致我们得到的不是tuple,而是整数 1。
因为用()定义单元素的tuple有歧义,所以 Python 规定,单元素 tuple 要多加一个逗号“,”,这样就避免了歧义:
>>> t = (1,)
>>> print t
(1,)
Python在打印单元素tuple时,也自动添加了一个“,”,为了更明确地告诉你这是一个tuple。
多元素 tuple 加不加这个额外的“,”效果是一样的:
>>> t = (1, 2, 3,)
>>> print t
(1, 2, 3)
可变”的tuple
前面我们看到了tuple一旦创建就不能修改。现在,我们来看一个“可变”的tuple:
>>> t = ('a', 'b', ['A', 'B'])
注意到 t 有 3 个元素:'a','b'和一个list:['A', 'B']。list作为一个整体是tuple的第3个元素。list对象可以通过 t[2] 拿到:
>>> L = t[2]
然后,我们把list的两个元素改一改: python >>> L[0] = 'X' >>> L[1] = 'Y'
再看看tuple的内容:
>>> print t
('a', 'b', ['X', 'Y'])
不是说tuple一旦定义后就不可变了吗?怎么现在又变了?
定义的时候tuple包含的3个元素: 当我们把list的元素'A'和'B'修改为'X'和'Y'后 表面上看,tuple的元素确实变了,但其实变的不是 tuple 的元素,而是list的元素。
tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。 即指向'a',就不能改成指向'b',指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!
Python代码的缩进规则
Python代码的缩进规则。具有相同缩进的代码被视为代码块,上面的3,4行 print 语句就构成一个代码块(但不包括第5行的print)。如果 if 语句判断为 True,就会执行这个代码块。 缩进请严格按照Python的习惯写法:4个空格,不要使用Tab,更不要混合Tab和空格,否则很容易造成因为缩进引起的语法错误。
if else
利用 if ... else ... 语句,我们可以根据条件表达式的值为 True 或者 False ,分别执行 if 代码块或者 else 代码块。
注意: else 后面有个“:”
用 if ... 多个elif ... else ... 的结构:
if age >= 18:
print 'adult'
elif age >= 6:
print 'teenager'
elif age >= 3:
print 'kid'
else:
print 'baby'
elif 意思就是 else if。这样一来,我们就写出了结构非常清晰的一系列条件判断。
for
Python的 for 循环就可以依次把list或tuple的每个元素迭代出来:
L = ['Adam', 'Lisa', 'Bart']
for name in L:
print name
注意: name 这个变量是在 for 循环中定义的,意思是,依次取出list中的每一个元素,并把元素赋值给 name,然后执行for循环体(就是缩进的代码块)。
while循环
和 for 循环不同的另一种循环是 while 循环,while 循环不会迭代 list 或 tuple 的元素,而是根据表达式判断循环是否结束。 比如要从 0 开始打印不大于 N 的整数
n= 10
x = 0
while x < n:
print x
x = x + 1
while循环每次先判断 x < n,如果为True,则执行循环体的代码块,否则,退出循环。
while True 就是一个死循环True要大写
break
break退出循环
continue
continue跳过后续循环代码,继续下一次循环。
dict
Python用 dict 表示“名字”-“成绩”的查找表如下:
key:value
d = {
'Adam': 95,
'Lisa': 85,
'Bart': 59
}
我们把名字称为key,对应的成绩称为value,dict就是通过 key 来查找 value。
花括号 {} 表示这是一个dict,然后按照 key: value, 写出来即可。最后一个 key: value 的逗号可以省略。
由于dict也是集合,len() 函数可以计算任意集合的大小:
>>> len(d)
3
可以简单地使用 d[key] 的形式来查找对应的 value,这和 list 很像,不同之处是,list 必须使用索引返回对应的元素,而dict使用key:
>>> print d['Adam']
95
>>> print d['Paul']
Traceback (most recent call last):
File "index.py", line 11, in <module>
print d['Paul']
KeyError: 'Paul'
注意: 通过 key 访问 dict 的value,只要 key 存在,dict就返回对应的value。如果key不存在,会直接报错:KeyError。
要避免 KeyError 发生,有两个办法:
第一种是先判断一下 key 是否存在,用 in 操作符:
if 'Paul' in d:
print d['Paul']
如果 'Paul' 不存在,if语句判断为False,自然不会执行 print d['Paul'] ,从而避免了错误。
第二种是使用dict本身提供的一个 get 方法,在Key不存在的时候,返回None:
>>> print d.get('Bart')
59
>>> print d.get('Paul')
None
dict的特点
dict的第一个特点是查找速度快,无论dict有10个元素还是10万个元素,查找速度都一样。而list的查找速度随着元素增加而逐渐下降。 不过dict的查找速度快不是没有代价的,dict的缺点是占用内存大,还会浪费很多内容,list正好相反,占用内存小,但是查找速度慢。 由于dict是按 key 查找,所以,在一个dict中,key不能重复。
dict的第二个特点就是存储的key-value序对是没有顺序的!这和list不一样:
d = {
'Adam': 95,
'Lisa': 85,
'Bart': 59
}
当我们试图打印这个dict时
>>> print d
{'Lisa': 85, 'Adam': 95, 'Bart': 59}
打印的顺序不一定是我们创建时的顺序,而且,不同的机器打印的顺序都可能不同,这说明dict内部是无序的,不能用dict存储有序的集合。
dict的第三个特点是作为 key 的元素必须不可变,Python的基本类型如字符串、整数、浮点数都是不可变的,都可以作为 key。但是list是可变的,就不能作为 key。 可以试试用list作为key时会报什么样的错误。
不可变这个限制仅作用于key,value是否可变无所谓:
{
'123': [1, 2, 3], # key 是 str,value是list
123: '123', # key 是 int,value 是 str
('a', 'b'): True # key 是 tuple,并且tuple的每个元素都是不可变对象,value是 boolean
}
最常用的key还是字符串,因为用起来最方便。
更新dict
dict是可变的,也就是说,我们可以随时往dict中添加新的 key-value。比如已有dict:
d = {
'Adam': 95,
'Lisa': 85,
'Bart': 59
}
要把新同学'Paul'的成绩 72 加进去,用赋值语句:
>>> d['Paul'] = 72
如果 key 已经存在,则赋值会用新的 value 替换掉原来的 value
set
set 持有一系列元素,set的元素没有重复,而且是无序的。
创建 set 的方式是调用 set() 并传入一个 list,list的元素将作为set的元素:
>>> s = set(['A', 'B', 'C'])
因为set不能包含重复的元素,所以,当我们传入包含重复元素的 list 会怎么样呢?
>>> s = set(['A', 'B', 'C', 'C'])
>>> print s
set(['A', 'C', 'B'])
>>> len(s)
3
结果显示,set会自动去掉重复的元素,原来的list有4个元素,但set只有3个元素。
set的特点
set的内部结构和dict很像,唯一区别是不存储value,因此,判断一个元素是否在set中速度很快。 set存储的元素和dict的key类似,必须是不变对象,因此,任何可变对象是不能放入set中的。 最后,set存储的元素也是没有顺序的。
如果事先创建好一个set,包含'MON' ~ 'SUN':
weekdays = set(['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN'])
再判断输入是否有效,只需要判断该字符串是否在set中;
x = '???' # 用户输入的字符串
if x in weekdays:
print 'input ok'
else:
print 'input error'
这样一来,代码就简单多了。
更新set
由于set存储的是一组不重复的无序元素,因此,更新set主要做两件事:
一是把新的元素添加到set中,二是把已有元素从set中删除。
添加元素时,用set的add()方法:
如果添加的元素已经存在于set中,add()不会报错,但是不会加进去了:
删除set中的元素时,用set的remove()方法:
如果删除的元素不存在set中,remove()会报错:
所以用add()可以直接添加,而remove()前需要判断。
编写函数
在Python中,定义一个函数要使用 def 语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用 return 语句返回。
我们以自定义一个求绝对值的 my_abs 函数为例:
def my_abs(x):
if x >= 0:
return x
else:
return -x
如果没有return语句,函数执行完毕后也会返回结果,只是结果为 None
return None可以简写为return。
返回多值
import math
def move(x, y, step, angle):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
这样我们就可以同时获得返回值:
>>> x, y = move(100, 100, 60, math.pi / 6)
>>> print x, y
151.961524227 70.0
但其实这只是一种假象,Python函数返回的仍然是单一值:
>>> r = move(100, 100, 60, math.pi / 6)
>>> print r
(151.96152422706632, 70.0)
用print打印返回结果,原来返回值是一个tuple!
但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。
汉诺塔问题:
汉诺塔
A B C
=| | |
==| | |
===| | |
----- ---- --->
def move(n, a, b, c):
if n ==1:
print a, '-->', c
return
move(n-1, a, c, b)
print a, '-->', c
move(n-1, b, a, c)
move(n, a, b, c)表示的是有n个盘子,将要由a柱子转移到b柱子上面去
move(盘子数,起始地,中转地,目的地)
def move(n, a, b, c):
如果a柱子上面只有一个盘子,则直接移到c柱子上面去并输出路径,结束递归
if n == 1:
print a, '-->', c
return
表示的是将由上到下的n-1个盘子从a柱子上面移到b柱子上面去
(a上 的n-1个,a---->b)
move(n-1 ,a, c, b)
输出最下面的那个盘子从a移到c的路径,(a上的第n个,a---->c)
print a, '-->', c
将b柱子上面的n-1个盘子移动到c柱子上面
(b上n-1个,b---->c)
move(n-1 ,b, a, c)
定义默认参数
定义函数的时候,还可以有默认参数。
我们来定义一个计算 x 的N次方的函数并且计算平方的次数最多,我们就可以把 n 的默认值设定为 2:
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
这样一来,计算平方就不需要传入两个参数了:
>>> power(5)
25
由于函数的参数按从左到右的顺序匹配,所以默认参数只能定义在必需参数的后面:
定义(个数)可变参数
如果想让一个函数能接受任意个参数,我们就可以定义一个可变参数:
def fn(*args):
print args
可变参数的名字前面有个 * 号,我们可以传入0个、1个或多个参数给可变参数:
>>> fn()
()
>>> fn('a')
('a',)
>>> fn('a', 'b')
('a', 'b')
Python解释器会把传入的一组参数组装成一个tuple传递给可变参数, 因此,在函数内部,直接把变量 args 看成一个 tuple 就好了
例如:
def average(*args):
sum=0.0
if len(args) == 0:#使用的是args而不是*args
return sum
for n in args:
sum=sum+n
return sum/len(args)
对list进行切片
取一个list的部分元素是非常常见的操作。比如,一个list如下:
>>> L = ['Adam', 'Lisa', 'Bart', 'Paul']
取前N个元素,也就是索引为0-(N-1)的元素,可以用循环:
>>> r = []
>>> n = 3
>>> for i in range(n):
r.append(L[i])
对这种经常取指定索引范围的操作,用循环十分繁琐,因此,Python提供了切片(Slice)操作符,能大大简化这种操作。
对应上面的问题,取前3个元素,用一行代码就可以完成切片:
例:
注意:索引与索引所代表的元素的区别
L[起始索引a:b],索引b - 索引a = 算上索引a所代表的一共取多少个元素(即由索引a所对应的开始取(b-a)个元素)
>>> L[0:3]
['Adam', 'Lisa', 'Bart']
L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。
L[0:3]表示:由0开始一共取3-0个
如果第一个索引是0,还可以省略:
>>> L[:3]
['Adam', 'Lisa', 'Bart']
也可以从索引1开始,取出2个元素出来:
>>> L[1:3]
['Lisa', 'Bart']
只用一个 : ,表示从头到尾:
>>> L[:]
['Adam', 'Lisa', 'Bart', 'Paul']
因此,L[:]实际上复制出了一个新list。
切片操作还可以指定第三个参数:
>>> L[::2]
['Adam', 'Bart']
第三个参数表示每N个取一个,上面的 L[::2] 会每两个元素取出一个来,也就是隔一个取一个。 即:
2: 0101010101
0123456789
0 2 4 6 8
所以:2 是01,逢 0 就取
3: 0120120120
0123456789
0 3 6 9
所以:3 是012,逢 0 就取
如果把list换成tuple,切片操作完全相同,只是切片的结果也变成了tuple。
倒序切片
0 , 1, 2, 3, 4, 5, 6, 7, 8, 9,
-10, -9, -8, -7,-6, -5, -4,-3, -2, -1,一个邪恶的未知元素索引
所以,如果想表示最后一个元素的话,就必须L[-1:]
because,-1+1=0,and 0是最开始的地方,so,L[-1:](不可以写0)。但是,在大脑中计算时可以想象为0) 其他方面和正常切片差不多
>>> L = ['Adam', 'Lisa', 'Bart', 'Paul']
>>> L[-2:]
['Bart', 'Paul']
>>> L[:-2]#如果前方为空即为由起点开始不可以0进行计算,但是可以写为L[0:-2]
['Adam', 'Lisa']
>>> L[-3:-1]
['Lisa', 'Bart']
>>> L[-4:-1:2]
['Adam', 'Bart']
利用倒序切片对 1 - 100 的数列,取出最后10个5的倍数
L[4::5][-10:]
记住倒数第一个元素的索引是-1
对字符串切片
字符串 'xxx'和 Unicode字符串 u'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:
>>> 'ABCDEFG'[:3]
'ABC'
>>> 'ABCDEFG'[-3:]
'EFG'
>>> 'ABCDEFG'[::2]
'ACEG'
Python没有针对字符串的截取函数,只需要切片一个操作就可以完成。
迭代
迭代操作就是对于一个集合,无论该集合是有序还是无序,我们用 for 循环总是可以依次取出集合的每一个元素。
注意: 集合是指包含一组元素的数据结构,我们已经介绍的包括:
有序集合:list,tuple,str和unicode;
无序集合:set
无序集合并且具有 key-value 对:dict
而迭代是一个动词,它指的是一种操作,在Python中,就是 for 循环。
迭代与按下标访问数组最大的不同是,后者是一种具体的迭代实现方式,而前者只关心迭代结果,根本不关心迭代内部是如何实现的。
索引迭代
Python中,迭代永远是取出元素本身,而非元素的索引。
对于有序集合,元素确实是有索引的。有的时候,我们确实想在 for 循环中拿到索引,怎么办?
方法是使用enumerate() 函数:
>>> L = ['Adam', 'Lisa', 'Bart', 'Paul']
>>> for index, name in enumerate(L):
... print index, '-', name
...
0 - Adam
1 - Lisa
2 - Bart
3 - Paul
使用 enumerate() 函数,我们可以在for循环中同时绑定索引index和元素name。但是,这不是 enumerate() 的特殊语法。实际上,enumerate() 函数把: ['Adam', 'Lisa', 'Bart', 'Paul']
变成了类似:[(0, 'Adam'), (1, 'Lisa'), (2, 'Bart'), (3, 'Paul')] 把list每一个元素与对应的索引绑在了一起因此,迭代的每一个元素实际上是一个tuple
for t in enumerate(L):
index = t[0]
name = t[1]
print index, '-', name
如果我们知道每个tuple元素都包含两个元素,for循环又可以进一步简写为:
for index, name in enumerate(L):
print index, '-', name
这样不但代码更简单,而且还少了两条赋值语句。
可见,索引迭代也不是真的按索引访问,而是由 enumerate() 函数自动把每个元素变成 (index, element) 这样的tuple,再迭代,就同时获得了索引和元素本身。
迭代dict的value
如果我们希望迭代 dict 对象的value,应该怎么做?
dict 对象有一个 values() 方法,这个方法把dict转换成一个包含所有value的list,这样,我们迭代的就是 dict的每一个 value:
>>>d = {
'Adam': 95,
'Lisa': 85,
'Bart': 59
}
>>>print d.values()
[85, 95, 59]
>>>for v in d.values():
print v
85
95
59
如果仔细阅读Python的文档,还可以发现,dict除了values()方法外,还有一个 itervalues()python3无此语法方法,用 itervalues() 方法替代 values() 方法,迭代效果完全一样:
d = { 'Adam': 95, 'Lisa': 85, 'Bart': 59 }
print d.itervalues()(python3无此语法)
<dictionary-valueiterator object at 0x106adbb50>
for v in d.itervalues()(python3无此语法):
print v
85
95
59
```python
values() 方法实际上把一个 dict 转换成了包含 value 的list。
但是 itervalues() (python3无此语法)方法不会转换,它会在迭代过程中依次从 dict 中取出 value,所以 itervalues() 方法比 values() 方法节省了生成 list 所需的内存。
如果一个对象说自己可迭代,那我们就直接用 for 循环去迭代它, 可见,迭代是一种抽象的数据操作,它不对迭代对象内部的数据有任何要求。
迭代dict的key和value
-我们了解了如何迭代 dict 的key和value,那么,在一个 for 循环中,能否同时迭代 key和value?
答案是肯定的。
首先,我们看看 dict 对象的 items() 方法返回的值:
>>> d = { 'Adam': 95, 'Lisa': 85, 'Bart': 59 }
>>> print d.items()
[('Lisa', 85), ('Adam', 95), ('Bart', 59)]
可以看到,items() 方法把dict对象转换成了包含tuple的list,我们对这个list进行迭代,可以同时获得key和value:
>>> for key, value in d.items():
... print key, ':', value
...
Lisa : 85
Adam : 95
Bart : 59
```python
和 values() 有一个 itervalues() 类似, items() 也有一个对应的 iteritems(),iteritems() 不把dict转换成list,而是在迭代过程中不断给出 tuple,所以, iteritems() 不占用额外的内存。
生成列表
要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],我们可以用range(1, 11):
>>> range(1, 11)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
但如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做?
方法一是循环:
>>> L = []
>>> for x in range(1, 11):
... L.append(x * x)
...
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
提示:range(1, 100, 2) 可以生成list [1, 3, 5, 7, 9,...],与切片类似 这种写法就是Python特有的列表生成式。利用列表生成式,可以以非常简洁的代码生成 list。
写列表生成式时,把要生成的元素 x * x 放到前面,后面跟 for 循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法
复杂表达式
使用for循环的迭代不仅可以迭代普通的list,还可以迭代dict。
假设有如下的dict:
d = { 'Adam': 95, 'Lisa': 85, 'Bart': 59 }
完全可以通过一个复杂的列表生成式把它变成一个 HTML 表格:
tds = ['<tr><td>%s</td><td>%s</td></tr>' % (name, score) for name, score in d.items()]
print '<table>'
print '<tr><th>Name</th><th>Score</th><tr>'
print '\n'.join(tds)
print '</table>'
注:字符串可以通过 % 进行格式化,用指定的参数替代 %s。字符串的join()方法可以把一个 list 拼接成一个字符串。
把打印出来的结果保存为一个html文件,就可以在浏览器中看到效果了:
Name Score
Lisa 85
Adam 95
Bart 59
条件过滤
列表生成式的 for 循环后面还可以加上 if 判断。例如:
如果我们想要偶数的平方,可以加上 if 来筛选:
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
多层表达式
for循环可以嵌套,因此,在列表生成式中,也可以用多层 for 循环来生成列表。
对于字符串 'ABC' 和 '123',可以使用两层循环,生成全排列:
>>> [m + n for m in 'ABC' for n in '123']
['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']
翻译成循环代码就像下面这样:
L = []
for m in 'ABC':
for n in '123':
L.append(m + n)
list 如下:
L = ['Adam', 'Lisa', 'Paul', 'Bart']
Paul的索引是2,Bart的索引是3,如果我们要把Paul和Bart都删掉, 请解释下面的代码为什么不能正确运行:
L.pop(2)
L.pop(3)
怎样调整代码可以把Paul和Bart都正确删除掉?
当首先删除索引为 2 的Paul时,L变成了:
['Adam', 'Lisa', 'Bart']
这时,注意到Bart的索引已经从原来的3变成2了!
参考代码:
L = ['Adam', 'Lisa', 'Paul', 'Bart']
L.pop(3)#由后到前
L.pop(2)
print L
set,list,tuple,dict
set 添加元素时,用set的add()方法
删除set中的元素时,用set的remove()方法
list append()总是把新的元素添加到 list 的尾部。
insert()方法,它接受两个参数,第一个参数是索引号,第二个参数是待添加的新元素
用 pop(索引)把索引对应的元素删掉,并且它还返回这个元素
pop()方法总是删掉list的最后一个元素,并且它还返回这个元素
tuple tuple一旦创建完毕,就不能修改了。
dict 用 pop(索引)把索引对应的元素删掉,并且它还返回这个元素 dict[key]=value
注意python内层函数可以访问外层函数中定义 的变量,但不能重新赋值(rebind)
高阶函数
把函数作为参数
例:
import math
def add(x, y, f):
return f(x) + f(y)
print add(25, 9,math.sqrt)
map()函数
map()是 Python 内置的高阶函数,它接收一个函数 f 和一个 list, 并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回。
例如,对于list [1, 2, 3, 4, 5, 6, 7, 8, 9] 如果希望把list的每个元素都作平方,就可以用map()函数:
因此,我们只需要传入函数f(x)=x*x,就可以利用map()函数完成这个计算
def fun(x):
return x*x
print map(fun, [1, 2, 3, 4, 5, 6, 7, 8, 9])#python2的map()
print(list(map(fun, [1, 2, 3, 4, 5, 6, 7, 8, 9])))#python3的map()
reduce()函数
reduce()函数也是Python内置的一个高阶函数。reduce()函数接收的参数和 map()类似, 一个函数 f,一个list,但行为和 map()不同,reduce()传入的函数f 必须接收两个参数, reduce()对list的每个元素反复调用函数f,并返回最终结果值。
例如,编写一个f函数,接收x和y,返回x和y的和:
def f(x, y):
return x + y
调用 reduce(f, [1, 3, 5, 7, 9])时,reduce函数将做如下计算:
先计算头两个元素:f(1, 3),结果为4;
再把结果和第3个元素计算:f(4, 5),结果为9;
再把结果和第4个元素计算:f(9, 7),结果为16;
再把结果和第5个元素计算:f(16, 9),结果为25;
由于没有更多的元素了,计算结束,返回结果25。
上述计算实际上是对 list 的所有元素求和。虽然Python内置了求和函数sum(),但是,利用reduce()求和也很简单。
reduce()还可以接收第3个可选参数,作为计算的初始值。如果把初始值设为100,计算:
reduce(f, [1, 3, 5, 7, 9], 100)###python2
import functools###python3
functools.reduce(f, [1, 3, 5, 7, 9], 100)
结果将变为125,因为第一轮计算是:
计算初始值和第一个元素:f(100, 1),结果为101。
filter()函数
filter()函数是 Python 内置的另一个有用的高阶函数,filter()函数接收一个函数 f 和一个list, 这个函数 f 的作用是对每个元素进行判断,返回 True或 False,
filter()根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新list。
例如,要从一个list [1, 4, 6, 7, 9, 12, 17]中删除偶数,保留奇数,
首先,要编写一个判断奇数的函数:
def is_odd(x):
return x % 2 == 1
然后,利用filter()过滤掉偶数:
filter(is_odd, [1, 4, 6, 7, 9, 12, 17])
结果:[1, 7, 9, 17]
利用filter(),可以完成很多有用的功能,例如,删除 None 或者空字符串:
def is_not_empty(s):
return s and len(s.strip()) > 0
filter(is_not_empty, ['test', None, '', 'str', ' ', 'END'])
结果:['test', 'str', 'END']
注意: s.strip(rm) 删除 s 字符串中开头、结尾处的 rm 序列的字符。
当rm为空时,默认删除空白符(包括'\n', '\r', '\t', ' '),如下:
a = ' 123'
a.strip()
结果:'123'
a='\t\t123\r\n'
a.strip()
结果:'123'
自定义排序函数
Python内置的 sorted()函数可对list进行排序:
>>>sorted([36, 5, 12, 9, 21])
[5, 9, 12, 21, 36]
但 sorted()也是一个高阶函数,它可以接收一个比较函数来实现自定义排序,
比较函数的定义是,传入两个待比较的元素 x, y,如果 x 应该排在 y 的前面, 返回 -1,如果 x 应该排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0。
因此,如果我们要实现倒序排序,只需要编写一个reversed_cmp函数:
def reversed_cmp(x, y):
if x > y:
return -1
if x < y:
return 1
return 0
这样,调用 sorted() 并传入 reversed_cmp 就可以实现倒序排序:
>>> sorted([36, 5, 12, 9, 21], reversed_cmp)
[36, 21, 12, 9, 5]
python3
>>>import functools
>>>sorted([36, 5, 12, 9, 21], key=functools.cmp_to_key(reversed_cmp))
在python3中其实可以
>>>sorted([36, 5, 12, 9, 21], reverse=True)#记住True的T要大写
可以用key=abs来排列绝对值
sorted()也可以对字符串进行排序,字符串默认按照ASCII大小来比较:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']
'Zoo'排在'about'之前是因为'Z'的ASCII码比'a'小。
python3
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
如果想忽略大小写可以调用upper或lower函数
>>> sorted(['bob', 'about', 'Zoo', 'Credit'],key=str.upper)
返回函数
Python的函数可以返回函数!
定义一个函数 f(),我们让它返回一个函数 g,可以这样写:
def f():
print 'call f()...'
def g():
print 'call g()...'
return g # 返回函数g
返回函数可以把一些计算延迟执行。
由于可以返回函数,我们在后续代码里就可以决定到底要不要调用该函数
闭包
内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包(Closure)。
闭包的特点是返回的函数还引用了外层函数的局部变量,
所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变
例如:具体细节请看错题2
匿名函数
以map()函数为例,计算 f(x)=x2 时,除了定义一个f(x)的函数外, 还可以直接传入匿名函数:
>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])###python2
[1, 4, 9, 16, 25, 36, 49, 64, 81]
>>>list( map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))###python3
[1, 4, 9, 16, 25, 36, 49, 64, 81]
通过对比可以看出,匿名函数 lambda x: x * x 实际上就是:
def f(x):
return x * x
关键字lambda 表示匿名函数,冒号前面的 x 表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不写return,返回值就是该表达式的结果。
返回函数的时候,也可以返回匿名函数:
>>> myabs = lambda x: -x if x < 0 else x
>>> myabs(-1)
1
>>> myabs(1)
1
装饰器decorator
例:
def f(x):
return x*2
def new_fn(f):#装饰器函数
def fn(x):
print f._name_+'say 'hello''
return f(x)
return fn
所以调用new_fn(f)时,
1.定义了fn(x)
2.return fn即调用了fn
so,
3.print
4.return f(x)
5.f=new_fn(f)
6.print f1(5)#f1的原始定义函数被彻底隐藏了
python内置的@语法就是为了简化装饰器调用
编写无参数decorator
使用 decorator 用Python提供的 @ 语法, 这样可以避免手动编写 f = decorate(f) 这样的代码。
一个@log的定义:
def log(f):
def fn(x):
print 'call ' + f.__name__ + '()...'
return f(x)
return fn
对于阶乘函数
@log
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)
结果:
call factorial()...
3628800
但是,对于参数不是一个的函数,调用将报错:
@log
def add(x, y):
return x + y
print add(1, 2)
因为 add() 函数需要传入两个参数,但是 @log 写死了只含一个参数的返回函数。
要让 @log 自适应任何参数定义的函数,可以利用Python的 *args 和 **kw, 保证任意个数的参数总是能正常调用:
def log(f):
def fn(*args, **kw):
print 'call ' + f.__name__ + '()...'
return f(*args, **kw)
return fn
现在,对于任意函数,@log 都能正常工作。
编写带参数decorator
考察上一节的 @log 装饰器:
def log(f):
def fn(x):
print 'call ' + f.__name__ + '()...'
return f(x)
return fn
发现对于被装饰的函数,log打印的语句是不能变的(除了函数名)。
如果有的函数非常重要,希望打印出'[INFO] call xxx()...',有的函数不太重要, 希望打印出'[DEBUG] call xxx()...',这时,log函数本身就需要传入'INFO'或'DEBUG'这样的参数,
类似这样:
@log('DEBUG')
def my_func():
pass
所以,带参数的log函数首先返回一个decorator函数,再让这个decorator函数接收my_func并返回新函数:
def log(name):
def log_decorator(fun):
def wrapper(*args, **kw):
print '[%s] %s()...' % (name, fun.__name__)
return f(*args, **kw)
return wrapper
return log_decorator
@log('DEBUG')
def test():
pass
print test()
执行结果:
>>>test()
[DEBUG] test()...
None
完善decorator
有decorator的情况下,打印函数名:
def log(f):
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper
@log
def f2(x):
pass
print f2.__name__
>>>f2._name_
wrapper
可见,由于decorator返回的新函数函数名已经不是'f2',而是@log内部定义的'wrapper'。 这对于那些依赖函数名的代码就会失效。decorator还改变了函数的__doc__等其它属性。 如果要让调用者看不出一个函数经过了@decorator的“改造”, 就需要把原函数的一些属性复制到新函数中:
例:
def log(f):
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
wrapper.__name__ = f.__name__
wrapper.__doc__ = f.__doc__#############这里的__是两个_
return wrapper
这样写decorator很不方便,因为我们也很难把原函数的所有必要属性都一个一个复制到新函数上, 所以Python内置的functools可以用来自动化完成这个“复制”的任务:
例:
import functools
def log(f):
@functools.wraps(f)###记住@functools.wraps()的位置在刚把函数当作参数的函数的第一行
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper
最后需要指出,由于我们把原函数签名改成了(*args, **kw), 因此,无法获得原函数的原始参数信息。即便我们采用固定参数来装饰只有一个参数的函数:
def log(f):
@functools.wraps(f)
def wrapper(x):
print 'call...'
return f(x)
return wrapper
也可能改变原函数的参数名,因为新函数的参数名始终是 'x', 原函数定义的参数名不一定叫 'x'。
偏函数
当一个函数有很多参数时,调用者就需要提供多个参数。 如果减少参数个数,就可以简化调用者的负担。 比如:
假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦, 于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
def int2(x, base=2):
return int(x, base)
这样,我们转换二进制就非常方便了:
>>> int2('1000000')
64
>>> int2('1010101')
85
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(), 可以直接使用下面的代码创建一个新的函数int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)##(函数,参数)
>>> int2('1000000')
64
>>> int2('1010101')
85
我们在sorted这个高阶函数中传入自定义排序函数就可以实现忽略大小写排序。 请用functools.partial把这个复杂调用变成一个简单的函数:
要固定sorted()的cmp参数,需要传入一个排序函数作为cmp的默认值。
参考代码:
import functools
sorted_ignore_case = functools.partial(sorted, cmp=lambda s1, s2: cmp(s1.upper(), s2.upper()))
print sorted_ignore_case(['bob', 'about', 'Zoo', 'Credit'])
模块和包
#test.py <-------------------------------自身模块名test
import math <--------------------------引用math模块
print (math.pow(2,10)) <-------------调用math模块的函数
包
{
模块
{
函数
}
}
bao.mo.fun
#test.py <-------------------------------自身模块名test
import bao.mo <-----------------------引用math模块
print (bao.mo.fun(2,10)) <------------调用math模块的函数
在文件系统中
包就是文件夹
模块就是xxx.py文件
但是包和普通文件夹的区别是:
包下都会有__init__.py文件
注意:每层都必须有
导入模块
import math
>>> math.pow(2, 0.5) # pow是函数
1.4142135623730951
>>> math.pi # pi是变量
3.141592653589793
如果我们只希望导入用到的math模块的某几个函数,而不是所有函数,可以用下面的语句:
from math import pow, sin, log
这样,可以直接引用 pow, sin, log 这3个函数,但math的其他函数没有导入进来:
>>> pow(2, 10)
1024.0
>>> sin(3.14)
0.0015926529164868282
如果遇到名字冲突怎么办?比如math模块有一个log函数,logging模块也有一个log函数, 如果同时使用,如何解决名字冲突?
如果使用import导入模块名,由于必须通过模块名引用函数名,因此不存在冲突
如果使用 from...import 导入 log 函数,势必引起冲突。这时,可以给函数起个“别名”来避免冲突:
from math import log
from logging import log as logger # logging的log现在变成了logger
print log(10) # 调用的是math的log
logger(10, 'import from logging') # 调用的是logging的log
from在前
动态导入模块
如果导入的模块不存在,Python解释器会报 ImportError 错误: 有的时候,两个不同的模块提供了相同的功能, 比如 StringIO 和 cStringIO 都提供了StringIO这个功能。
同样的功能,StringIO 是纯Python代码编写的,而 cStringIO 部分函数是 C 写的, 因此 cStringIO 运行速度更快。
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
上述代码先尝试从cStringIO导入,如果失败了(比如cStringIO没有被安装),再尝试从StringIO导入。 这样,如果cStringIO模块存在,则我们将获得更快的运行速度,如果cStringIO不存在, 则顶多代码运行速度会变慢,但不会影响代码的正常执行。
使用__future__
Python的新版本会引入新的功能,但是,实际上这些功能在上一个老版本中就已经存在了。 要“试用”某一新的特性,就可以通过导入__future__模块的某些功能来实现
例如,Python 2.7的整数除法运算结果仍是整数:
>>> 10 / 3
3
但是,Python 3.x已经改进了整数的除法运算,“/”除将得到浮点数,“//”除才仍是整数:
>>> 10 / 3
3.3333333333333335
>>> 10 // 3
3
要在Python 2.7中引入3.x的除法规则,导入__future__的division:
>>> from __future__ import division
>>> print 10 / 3
3.3333333333333335
当新版本的一个特性与旧版本不兼容时,该特性将会在旧版本中添加到__future__中, 以便旧的代码能在旧版本中测试新特性。
在Python 3.x中,字符串统一为unicode,不需要加前缀 u,而以字节存储的str则必须加前缀 b。
安装第三方模块
pip install xxx.py
python
>>>import xxx
map,reduce,filter,sorted
python2:map(fun,alist)
python3:list(map(fun,alist))
fun(x)函数分别作用于对list每一个元素
例:
def fun(x):
return x*x
print map(fun, [1, 2, 3, 4, 5, 6, 7, 8, 9]) ###python2的map()
print(list(map(fun, [1, 2, 3, 4, 5, 6, 7, 8, 9]))) ###python3的map()
reduce(fun,alist,intializer) (intializer:初始值)
传入的函数fun(x,y)必须接收两个参数
reduce()对list的每两个个元素反复调用函数f,即(0,1)返回a,(a,2)返回b,(b,3),(c,4),(d,5)。。。
例:
时间线---------------->------------------->
def f(x, y): # x , y : 1.即 0 ,1 2.即 a ,2
return x + y # x+y : 即 a 即 b
所以此时b=0+1+2
reduce(f, [1, 3, 5, 7, 9], 100)###python2
import functools###python3
functools.reduce(f, [1, 3, 5, 7, 9], 100)
结果将变为125,
因为第一轮计算是:计算初始值和第一个元素:f(100, 1)。
filter(fun,alist)
接收一个函数 f 和一个list,这个函数 f 的作用是对每个元素进行判断,返回 True或 False, filter()根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新list。
例:去除偶数
def is_odd(x):
return x % 2 == 1
filter(is_odd, [1, 4, 6, 7, 9, 12, 17])
结果:[1, 7, 9, 17]
python2:sorted(aliist,fun)
python3:sorted(alist,key,reverse) #reverse:颠倒
可以接收一个比较函数来实现自定义排序, 比较函数的定义是,传入两个待比较的元素 x, y,如果 x 应该排在 y 的前面, 返回 -1,如果 x 应该排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0。 sorted(alist)函数还可以直接进行排序
>>>sorted([36, 5, 12, 9, 21])`
[5, 9, 12, 21, 36]
例:倒序
def reversed_cmp(x, y):
if x > y:
return -1
if x < y:
return 1
return 0
>>> sorted([36, 5, 12, 9, 21], reversed_cmp)
[36, 21, 12, 9, 5]
###python3###
>>>import functools
>>>sorted([36, 5, 12, 9, 21], key=functools.cmp_to_key(reversed_cmp))
在python3中其实可以
>>>sorted([36, 5, 12, 9, 21], reverse=True)#记住True的T要大写
面向对象编程
类:
class xxx(object):
...
实例:
aclass=xxx()
创建实例属性
由于Python是动态语言,对每一个实例,都可以直接给他们的属性赋值,
例如,给xiaoming这个实例加上name、gender和birth属性:
xiaoming = Person()
xiaoming.name = 'Xiao Ming'
xiaoming.gender = 'Male'
xiaoming.birth = '1990-1-1'
实例的属性可以像普通变量一样进行操作:
xiaohong.grade = xiaohong.grade + 1
初始化实例属性
在定义 Person 类时,可以为Person类添加一个特殊的__init__()方法, 当创建实例时,__init__()方法被自动调用,我们就能在此为每个实例都统一加上以下属性:
class Person(object):
def __init__(self, name, gender, birth):
self.name = name
self.gender = gender
self.birth = birth
__init__() 方法的第一个参数必须是 self(也可以用别的名字,但建议使用习惯用法), 后续参数则可以自由指定,和定义函数没有任何区别。
除了可以直接使用self.name = 'xxx'设置一个属性外,还可以通过 setattr(self, 'name', 'xxx') 设置属性 相应地,创建实例时,就必须要提供除 self 以外的参数:
xiaoming = Person('Xiao Ming', 'Male', '1991-1-1')
xiaohong = Person('Xiao Hong', 'Female', '1992-2-2')
有了__init__()方法,每个Person实例在创建时, 都会有 name、gender 和 birth 这3个属性,并且,被赋予不同的属性值,访问属性使用.操作符:
print xiaoming.name
# 输出 'Xiao Ming'
print xiaohong.birth
# 输出 '1992-2-2'
要特别注意的是,初学者定义__init__()方法常常忘记了 self 参数:
>>> class Person(object):
... def __init__(name, gender, birth):
... pass
...
>>> xiaoming = Person('Xiao Ming', 'Male', '1990-1-1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() takes exactly 3 arguments (4 given)
这会导致创建失败或运行不正常,因为第一个参数name被Python解释器传入了实例的引用, 从而导致整个方法的调用参数位置全部没有对上。
访问限制
Python对属性权限的控制是通过属性名来实现的,如果一个属性由双下划线开头(__), 该属性就无法被外部访问。
例:
class Person(object):
def __init__(self, name):
self.name = name
self._title = 'Mr'
self.__job = 'Student'
self.__a__ = 0
p = Person('Bob')
print p.name
# => Bob
print p._title
# => Mr
print b.__a__
# => 0
print p.__job
# => Error
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute '__job'
可见,只有以双下划线开头的"__job"不能直接被外部访问。
但是,如果一个属性以__xxx__的形式定义,那它就又可以被外部访问了, 以__xxx__定义的属性在Python的类中被称为特殊属性,有很多预定义的特殊属性可以使用, 通常我们不要把普通属性用__xxx__定义。
创建类属性
类是模板,而实例则是根据类创建的对象。
绑定在一个实例上的属性不会影响其他实例,但是,类本身也是一个对象, 如果在类上绑定一个属性,则所有实例都可以访问类的属性,并且, 所有实例访问的类属性都是同一个!也就是说,实例属性每个实例各自拥有, 互相独立,而类属性有且只有一份。
定义类属性可以直接在 class 中定义:
class Person(object):
address = 'Earth'
def __init__(self, name):
self.name = name
因为类属性是直接绑定在类上的,所以,访问类属性不需要创建实例,就可以直接访问:
print Person.address
# => Earth
对一个实例调用类的属性也是可以访问的,所有实例都可以访问到它所属的类的属性:
p1 = Person('Bob')
p2 = Person('Alice')
print p1.address
# => Earth
print p2.address
# => Earth
由于Python是动态语言,类属性也是可以动态添加和修改的:
Person.address = 'China'
print p1.address
# => 'China'
print p2.address
# => 'China'
因为类属性只有一份,所以,当Person类的address改变时,所有实例访问到的类属性都改变了。
class Person(object):
count=0
def __init__(self,name):
self.name=name
Person.count+=1###########
p1 = Person('Bob')
print Person.count
类属性和实例属性名字冲突怎么办
修改类属性会导致所有实例访问到的类属性全部都受影响, 但是,如果在实例变量上修改类属性会发生什么问题呢?
class Person(object):
address = 'Earth'
def __init__(self, name):
self.name = name
p1 = Person('Bob')
p2 = Person('Alice')
print 'Person.address = ' + Person.address
p1.address = 'China'
print 'p1.address = ' + p1.address
print 'Person.address = ' + Person.address
print 'p2.address = ' + p2.address
结果如下:
Person.address = Earth
p1.address = China
Person.address = Earth
p2.address = Earth
我们发现,在设置了 p1.address = 'China' 后,p1访问 address 确实变成了 'China', 但是,Person.address和p2.address仍然是'Earch',怎么回事?
原因是 p1.address = 'China'并没有改变 Person 的 address, 而是给 p1这个实例绑定了实例属性address ,对p1来说, 它有一个实例属性address(值是'China'),而它所属的类Person也有一个类属性address,
所以:
访问 p1.address 时,优先查找实例属性,返回'China'。
访问 p2.address 时,p2没有实例属性address,但是有类属性address,因此返回'Earth'。
可见,当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问。 当我们把 p1 的 address 实例属性删除后,访问 p1.address 就又返回类属性的值 'Earth'了:
del p1.address
print p1.address
# => Earth
可见,千万不要在实例上修改类属性,它实际上并没有修改类属性, 而是给实例绑定了一个实例属性。
定义实例方法
虽然私有属性无法从外部访问,但是,从类的内部是可以访问的。 除了可以定义实例的属性外,还可以定义实例的方法。
实例的方法就是在类中定义的函数,它的第一个参数永远是 self, 指向调用该方法的实例本身,其他参数和一个普通函数是完全一样的:
class Person(object):
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
get_name(self) 就是一个实例方法,它的第一个参数是self
调用实例方法必须在实例上调用:
p1 = Person('Bob')
print p1.get_name() # self不需要显式传入
# => Bob
class Person(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def get_grade(self):
if self.__score >= 80:
return 'A'
if self.__score >= 60:
return 'B'
return 'C'
p1 = Person('Bob', 90)
p2 = Person('Alice', 65)
p3 = Person('Tim', 48)
print p1.get_grade()
print p2.get_grade()
print p3.get_grade()
方法也是属性
我们在 class 中定义的实例方法其实也是属性,它实际上是一个函数对象:
class Person(object):
def __init__(self, name, score):
self.name = name
self.score = score
def get_grade(self):
return 'A'
p1 = Person('Bob', 90)
print p1.get_grade
# => <bound method Person.get_grade of <__main__.Person object at 0x109e58510>>
print p1.get_grade()
# => A
也就是说,p1.get_grade 返回的是一个函数对象,但这个函数是一个绑定到实例的函数, p1.get_grade() 才是方法调用。
因为方法也是一个属性,所以,它也可以动态地添加到实例上, 只是需要用 types.MethodType() 把一个函数变为一个方法:
import types
def fn_get_grade(self):
if self.score >= 80:
return 'A'
if self.score >= 60:
return 'B'
return 'C'
class Person(object):
def __init__(self, name, score):
self.name = name
self.score = score
p1 = Person('Bob', 90)
p1.get_grade = types.MethodType(fn_get_grade, p1, Person)
print p1.get_grade()
# => A
p2 = Person('Alice', 65)
print p2.get_grade()
# ERROR: AttributeError: 'Person' object has no attribute 'get_grade'
# 因为p2实例并没有绑定get_grade
给一个实例动态添加方法并不常见,直接在class中定义要更直观。
函数调用不需要传入 self,但是方法调用需要传入 self。
定义类方法
和属性类似,方法也分实例方法和类方法。
在class中定义的全部是实例方法,实例方法第一个参数 self 是实例本身。
要在class中定义类方法,需要这么写:
class Person(object):
count = 0
@classmethod
def how_many(cls):
return cls.count
def __init__(self, name):
self.name = name
Person.count = Person.count + 1
print Person.how_many()
p1 = Person('Bob')
print Person.how_many()
通过标记一个 @classmethod,该方法将绑定到 Person 类上,而非类的实例。 类方法的第一个参数将传入类本身,通常将参数名命名为 cls, 上面的 cls.count 实际上相当于 Person.count。
因为是在类上调用,而非实例上调用,因此类方法无法获得任何实例变量,只能获得类的引用。
继承
is 关系 即继承:student is person
class Student(Person):
def __init(self,aaa):
super(Student,self).__init__(aaaa)
has关系 即组合:student has book
class Student(object):
def __init__(self,book_name):
self.book=Book(book_name)
继承一个类
如果已经定义了Person类,需要定义新的Student和Teacher类时,可以直接从Person类继承:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
定义Student类时,只需要把额外的属性加上,例如score:
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
一定要用 super(Student, self).init(name, gender) 去初始化父类, 否则,继承自 Person 的 Student 将没有 name 和 gender。
函数super(Student, self)将返回当前类继承的父类,即 Person ,然后调用__init__()方法, 注意self参数已在super()中传入,在__init__()中将隐式传递,不需要写出(也不能写)。
判断类型
函数isinstance()可以判断一个变量的类型, 既可以用在Python内置的数据类型如str、list、dict, 也可以用在我们自定义的类,它们本质上都是数据类型。
假设有如下的 Person、Student 和 Teacher 的定义及继承关系如下:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
class Teacher(Person):
def __init__(self, name, gender, course):
super(Teacher, self).__init__(name, gender)
self.course = course
p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')
当我们拿到变量 p、s、t 时,可以使用 isinstance 判断类型:
>>> isinstance(p, Person)
True # p是Person类型
>>> isinstance(p, Student)
False # p不是Student类型
>>> isinstance(p, Teacher)
False # p不是Teacher类型
这说明在继承链上,一个父类的实例不能是子类类型,因为子类比父类多了一些属性和方法。
我们再考察 s :
>>> isinstance(s, Person)
True # s是Person类型
>>> isinstance(s, Student)
True # s是Student类型
>>> isinstance(s, Teacher)
False # s不是Teacher类型
s 是Student类型,不是Teacher类型,这很容易理解。 但是,s 也是Person类型,因为Student继承自Person, 虽然它比Person多了一些属性和方法,但是,把 s 看成Person的实例也是可以的。
这说明在一条继承链上,一个实例可以看成它本身的类型,也可以看成它父类的类型。
多态
类具有继承关系,并且子类类型可以向上转型看做父类类型, 如果我们从 Person 派生出 Student和Teacher ,并都写了一个 whoAmI() 方法:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def whoAmI(self):
return 'I am a Person, my name is %s' % self.name
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
def whoAmI(self):
return 'I am a Student, my name is %s' % self.name
class Teacher(Person):
def __init__(self, name, gender, course):
super(Teacher, self).__init__(name, gender)
self.course = course
def whoAmI(self):
return 'I am a Teacher, my name is %s' % self.name
在一个函数中,如果我们接收一个变量 x,则无论该 x 是 Person、Student还是 Teacher,都可以正确打印出结果:
def who_am_i(x):
print x.whoAmI()
p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')
who_am_i(p)
who_am_i(s)
who_am_i(t)
运行结果:
I am a Person, my name is Tim
I am a Student, my name is Bob
I am a Teacher, my name is Alice
这种行为称为多态。也就是说,方法调用将作用在 x 的实际类型上。 s 是Student类型,它实际上拥有自己的 whoAmI()方法以及从 Person继承的 whoAmI方法, 但调用 s.whoAmI()总是先查找它自身的定义,如果没有定义,则顺着继承链向上查找, 直到在某个父类中找到为止。
由于Python是动态语言,所以,传递给函数 who_am_i(x)的参数 x 不一定是 Person 或 Person 的子类型。 任何数据类型的实例都可以,只要它有一个whoAmI()的方法即可:
class Book(object):
def whoAmI(self):
return 'I am a book'
这是动态语言和静态语言(例如Java)最大的差别之一。动态语言调用实例方法, 不检查类型,只要方法存在,参数正确,就可以调用。
多重继承
除了从一个父类继承外,Python允许从多个父类继承,称为多重继承。
多重继承的继承链就不是一棵树了,它像这样:
class A(object):
def __init__(self, a):
print 'init A...'
self.a = a
class B(A):
def __init__(self, a):
super(B, self).__init__(a)
print 'init B...'
class C(A):
def __init__(self, a):
super(C, self).__init__(a)
print 'init C...'
class D(B, C):
def __init__(self, a):###
super(D, self).__init__(a)###
print 'init D...'
看下图:
爷 A
/ \
/ \
父 B C
\ /
\ /
孙 D
像这样,D 同时继承自 B 和 C,也就是 D 拥有了 A、B、C 的全部功能。
多重继承通过 super()调用__init__()方法时,A 虽然被继承了两次,但__init__()只调用一次:
>>> d = D('d')
init A...
init C...
init B...
init D...
多重继承的目的是从两种继承树中分别选择并继承出子类,以便组合功能使用。
通过多重继承,请定义“会打篮球的学生”和“会踢足球的老师”。
多重继承需要从两个或更多的类派生。
参考代码:
class Person(object):
pass
class Student(Person):
pass
class Teacher(Person):
pass
class SkillMixin(object):
pass
class BasketballMixin(SkillMixin):
def skill(self):
return 'basketball'
class FootballMixin(SkillMixin):
def skill(self):
return 'football'
class BStudent(Student, BasketballMixin):
pass###只有在父类需要参数时,调用super(BSTudent,self).__init(xxx,xxx)
class FTeacher(Teacher, FootballMixin):
pass###
s = BStudent()
print s.skill()
t = FTeacher()
print t.skill()
获取对象信息
拿到一个变量,除了用 isinstance() 判断它是否是某种类型的实例外, 还有没有别的方法获取到更多的信息呢?
例如,已有定义:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
def whoAmI(self):
return 'I am a Student, my name is %s' % self.name
首先可以用 type() 函数获取变量的类型,它返回一个 Type 对象:
>>> type(123)
<type 'int'>
>>> s = Student('Bob', 'Male', 88)
>>> type(s)
<class '__main__.Student'>
其次,可以用 dir() 函数获取变量的所有属性:
>>> dir(123) # 整数也有很多属性...
['__abs__', '__add__', '__and__', '__class__', '__cmp__', ...]
>>> dir(s)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__',
'__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', 'gender', 'name', 'score', 'whoAmI']
对于实例变量,dir()返回所有实例属性,包括__class__这类有特殊意义的属性。 注意到方法whoAmI也是 s 的一个属性。
如何去掉__xxx__这类的特殊属性,只保留我们自己定义的属性?回顾一下filter()函数的用法。
dir()返回的属性是字符串列表,如果已知一个属性名称,要获取或者设置对象的属性, 就需要用 getattr() 和 setattr( )函数了:
>>> getattr(s, 'name') # 获取name属性
'Bob'
>>> setattr(s, 'name', 'Adam') # 设置新的name属性
>>> s.name
'Adam'
>>> getattr(s, 'age') # 获取age属性,但是属性不存在,报错:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'
>>> getattr(s, 'age', 20) # 获取age属性,如果属性不存在,就返回默认值20:
20
对于Person类的定义:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
希望除了 name和gender 外,可以提供任意额外的关键字参数, 并绑定到实例,请修改 Person 的 __init__()定 义,完成该功能。
传入**kw 即可传入任意数量的参数,并通过 setattr() 绑定属性。
参考代码:
class Person(object):
def __init__(self, name, gender, **kw):
self.name = name
self.gender = gender
for k, v in kw.iteritems():###
setattr(self, k, v)
p = Person('Bob', 'Male', age=18, course='Python')
print p.age
print p.course
特殊方法
例
打印 用于 print的__str__
长度 用于len的__len__
比较 用于cmp的__cmp__
特殊方法定义在class中
不需要直接调用
Python的某些函数或操作符会调用对应的特殊方法
正确实现特殊方法
有关联性的特殊方法都必须实现
如果定义了__getattr__方法,就必须要定义__setattr__方法和__delattr__方法
__str__和__repr__
如果要把一个类的实例变成 str,就需要实现特殊方法__str__():
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __str__(self):
return '(Person: %s, %s)' % (self.name, self.gender)
现在,在交互式命令行下用 print 试试:
>>> p = Person('Bob', 'male')
>>> print p
(Person: Bob, male)
但是,如果直接敲变量 p:
>>> p
<main.Person object at 0x10c941890>
似乎__str__() 不会被调用。
因为 Python 定义了__str__()和__repr__()两种方法,__str__()用于显示给用户, 而__repr__()用于显示给开发人员。
有一个偷懒的定义__repr__的方法:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __str__(self):
return '(Person: %s, %s)' % (self.name, self.gender)
__repr__ = __str__
__cmp__
对 int、str 等内置数据类型排序时,Python的 sorted() 按照默认的比较函数 cmp 排序, 但是,如果对一组 Student 类的实例排序时,就必须提供我们自己的特殊方法 cmp():
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def __str__(self):
return '(%s: %s)' % (self.name, self.score)
__repr__ = __str__
def __cmp__(self, s):
if self.name < s.name:
return -1
elif self.name > s.name:
return 1
else:
return 0
上述 Student 类实现了__cmp__()方法,__cmp__用实例自身self和传入的实例 s 进行比较, 如果 self 应该排在前面(就是self比s小),就返回 -1,如果 s 应该排在前面, 就返回1,如果两者相当,返回 0。
Student类实现了按name进行排序:
>>> L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)]
>>> print sorted(L)
[(Alice: 77), (Bob: 88), (Tim: 99)]
注意: 如果list不仅仅包含 Student 类,则 cmp 可能会报错:
L = [Student('Tim', 99), Student('Bob', 88), 100, 'Hello']
print sorted(L)
请思考如何解决:个人认为在__cmp__中加入if判断是否为Student,isinstance(self,Student)
######python3
import functools
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def __str__(self):
return '(%s: %s)' % (self.name, self.score)
__repr__ = __str__
def __cmp__(self, s):
if self.name < s.name:
return -1
elif self.name > s.name:
return 1
else:
return 0
L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)]
print(sorted(L,key=functools.cmp_to_key(__cmp__)))
请修改 Student 的 cmp 方法,让它按照分数从高到底排序,分数相同的按名字排序。
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def __str__(self):
return '(%s: %s)' % (self.name, self.score)
__repr__ = __str__
def __cmp__(self, s):
if False == isinstance(s, Student):
return -1
return -cmp(self.score, s.score) or cmp(self.name, s.name)
L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
print sorted(L)
__len__
如果一个类表现得像一个list,要获取有多少个元素,就得用 len() 函数。
要让 len() 函数工作正常,类必须提供一个特殊方法__len__(),它返回元素的个数。
例如,我们写一个 Students 类,把名字传进去:
class Students(object):
def __init__(self, *args):
self.names = args
def __len__(self):
return len(self.names)
只要正确实现了__len__()方法,就可以用len()函数返回Students实例的“长度”:
>>> ss = Students('Bob', 'Alice', 'Tim')
>>> print len(ss)
3
斐波那契数列是由 0, 1, 1, 2, 3, 5, 8...构成。
请编写一个Fib类,Fib(10)表示数列的前10个元素,print Fib(10) 可以打印出数列的前 10 个元素, len(Fib(10))可以正确返回数列的个数10。
需要根据num计算出斐波那契数列的前N个元素。
参考代码:
class Fib(object):
def __init__(self, num):
a, b, L = 0, 1, []
for n in range(num):
L.append(a)
a, b = b, a + b
self.numbers = L
def __str__(self):
return str(self.numbers)
__repr__ = __str__
def __len__(self):
return len(self.numbers)
f = Fib(10)
print f
print len(f)
数学运算
Python 提供的基本数据类型 int、float 可以做整数和浮点的四则运算以及乘方等运算。
但是,四则运算不局限于int和float,还可以是有理数、矩阵等。
要表示有理数,可以用一个Rational类来表示分数:
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
p、q 都是整数,表示有理数 p/q。
如果要让Rational进行+-*/运算,需要正确实现
加法运算 + 调用__add__
减法运算 - 调用__sub__
乘法运算 * 调用__mul__
除法运算 / 调用__div__
如果运算结果是 6/8,在显示的时候需要归约到最简形式3/4。
参考代码:
def gcd(a, b):
if b == 0:
return a
return gcd(b, a % b)
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
def __add__(self, r):
return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
def __sub__(self, r):
return Rational(self.p * r.q - self.q * r.p, self.q * r.q)
def __mul__(self, r):
return Rational(self.p * r.p, self.q * r.q)
def __div__(self, r):
return Rational(self.p * r.q, self.q * r.p)
def __str__(self):
g = gcd(self.p, self.q)
return '%s/%s' % (self.p / g, self.q / g)
__repr__ = __str__
r1 = Rational(1, 2)
r2 = Rational(1, 4)
print r1 + r2
print r1 - r2
print r1 * r2
print r1 / r2
3/4
1/4
1/8
2/1
类型转换
Rational类实现了有理数运算,但是,如果要把结果转为 int 或 float 怎么办?
考察整数和浮点数的转换:
>>> int(12.34)
12
>>> float(12)
12.0
如果要把 Rational 转为 int,应该使用:
r = Rational(12, 5)
n = int(r)
要让int()函数正常工作,只需要实现特殊方法__int__():
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
def __int__(self):
return self.p // self.q
结果如下:
>>> print int(Rational(7, 2))
3
>>> print int(Rational(1, 3))
0
同理,要让float()函数正常工作,只需要实现特殊方法__float__()。
def __float__(self):
return 1.0*self.p/self.q
@property
考察Student类:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
当我们想要修改一个 Student 的 scroe 属性时,可以这么写:
s = Student('Bob', 59)
s.score = 60
但是也可以这么写:
s.score = 1000
显然,直接给属性赋值无法检查分数的有效性。
如果利用两个方法:
class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
def get_score(self):
return self.__score
def set_score(self, score):
if score < 0 or score > 100:
raise ValueError('invalid score')#########
self.__score = score
这样一来,s.set_score(1000) 就会抛异常。
但是写 s.get_score() 和 s.set_score() 没有直接写 s.score 来得直接。
有没有两全其美的方法?----有。
因为Python支持高阶函数,在函数式编程中我们介绍了装饰器函数, 可以用装饰器函数把 get/set 方法“装饰”成属性调用:
class Student(object):
def __init__(self, name, score):
self.name = name
self.__score = score
@property
def score(self):
return self.__score
@score.setter
def score(self, score):
if score < 0 or score > 100:
raise ValueError('invalid score')####
self.__score = score
注意: 第一个score(self)是get方法,用@property装饰, 第二个score(self, score)是set方法,用@score.setter装饰, @score.setter是前一个@property装饰后的副产品(有get就得有set),也就是score的判断语句。
现在,就可以像使用属性一样设置score了(即不用get_score和set_score,也可以对输入的score进行判断):
>>> s = Student('Bob', 59)
>>> s.score = 60
>>> print s.score
60
>>> s.score = 1000
Traceback (most recent call last):
...
ValueError: invalid score
说明对 score 赋值实际调用的是 set方法。
__slots__
由于Python是动态语言,任何实例在运行期都可以动态地添加属性。
如果要限制添加的属性,例如,Student类只允许添加 name、gender和score 这3个属性, 就可以利用Python的一个特殊的__slots__来实现。
顾名思义,__slots__是指一个类允许的属性列表:
class Student(object):
__slots__ = ('name', 'gender', 'score')
def __init__(self, name, gender, score):
self.name = name
self.gender = gender
self.score = score
现在,对实例进行操作:
>>> s = Student('Bob', 'male', 59)
>>> s.name = 'Tim' # OK
>>> s.score = 99 # OK
>>> s.grade = 'A'
Traceback (most recent call last):
...
AttributeError: 'Student' object has no attribute 'grade'
__slots__的目的是限制当前类所能拥有的属性,如果不需要添加任意动态的属性, 使用__slots__也能节省内存。
子类的__slots__只需要包含父类不包含的属性即可。
__call__
在Python中,函数其实是一个对象:
>>> f = abs
>>> f.__name__
'abs'
>>> f(-123)
123
由于 f 可以被调用,所以,f 被称为可调用对象。
所有的函数都是可调用对象。
一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法__call__()。
我们把 Person 类变成一个可调用对象:
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __call__(self, friend):
print 'My name is %s...' % self.name
print 'My friend is %s...' % friend
现在可以对 Person 实例直接调用:
>>> p = Person('Bob', 'male')
>>> p('Tim')
My name is Bob...
My friend is Tim...
单看 p('Tim') 你无法确定 p 是一个函数还是一个类实例,所以,在Python中,函数也是对象, 对象和函数的区别并不显著
改进一下前面定义的斐波那契数列
class Fib(object):
def __call__(self, num):
a, b, L = 0, 1, []
for n in range(num):
L.append(a)
a, b = b, a + b
return L
f = Fib()
print f(10)
典型错误:
1
def calc_prod(lst):
def g(lst):
s=1
for n in lst:
s*=n
return s
return g
f = calc_prod([1, 2, 3, 4])
print (f())
程序的第二行中的g函数:g(lst)表示创建一个新的lst参数,但是,在程序中并没有传入因此会报错
修改:
def calc_prod(lst):
def g():
s=1
for n in lst:
s*=n
return s
return g
f = calc_prod([1, 2, 3, 4])
print (f())
2
希望一次返回3个函数,分别计算1x1,2x2,3x3:
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果全部都是 9
原因就是当执行完count()函数后, i 的值就变成了3。由于f1、f2、f3并没有被调用,所以,此时他们并未计算 i*i,当 f1 被调用时:
>>> f1()
9 # 因为f1现在才被调用,而此时的i=3
闭包的特点是返回的函数还引用了外层函数的局部变量, 所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。
修改:
考察下面的函数 f:
def f(j):
def g():
return j*j
return g
它可以正确地返回一个闭包g,g所引用的变量j不是循环变量,因此将正常执行。
在count函数的循环内部,如果借助f函数,就可以避免引用循环变量i。
def count():
fs = []
for i in range(1, 4):
def f(j):
#------------------------------------------------------------------------------
def g():
return j*j
return g
k=f(i)
fs.append(k)#如果此处写fs.append(f)的话,则每次调用f时需要传入参数
#但是fs.append(k),中的k函数已经是传入值后的f了,例如k=f(1)
#-----------------------------------------------------------------------------
return fs
f1, f2, f3 = count()
当执行完count()函数后, i 的值就变成了3。
f1、f2、f3也没有被调用,但是,程序中的k并不受i的影响,因为k函数已经是传入值后的f了,
例如k=f(1),k已经确定了
当 f1 被调用时:
>>>f1()
9