慕课网 笔记:2016/3/14
Python简介
1989 龟叔
python特点:优雅明确简单
适合开发:
web网站和各种网络服务
系统工具和脚本
作为胶水语言把其他语言开发的模块包装起来方便使用
python不适合的领域:
贴近硬件的代码
移动开发
游戏开发:C C++
运行速度 代码量
C 编译为机器码 非常快 非常多
Java 编译为字节码 快 多
Python 解释执行 慢 少
速度的瓶颈在于数据库和网络的调用
python源码不能加密:不卖源码 卖服务
python跨平台
2.7版 3.3版 不兼容
第三方库
python2.7.8
第三章 Python变量和数据类型
raw字符串和多行字符串:
raw字符串:
print r'a\t\n\\b'
print 'a\t\n\\b'
多行字符串
print '''a
b'''
print 'a
b'
Unicode字符串
字符串还有个编码问题,计算机只能处理数字,要想处理文本,就必须把文本转换为数字才能处理。最早的计算机在设计时采用8个比特作为一个字节,所以,一个字节能表示的最大整数就是255(二进制11111111=十进制255),0-255被用来表示小写字母、数字和一些符号,这个编码表成为ASCII编码,比如大写字母A的编码是65,小写字母z的编码是122
如果要表示中文,一个字节显然是不够的,至少要两个字节而且还不能和ASCII编码冲突,所以,中国制定了GB2312编码,用来把中文编进去。
类似的,日文和韩文等其他语言也有这个问题。为了统一所有文字的编码,Unicode应运而生,Unicode把所有语言都统一到一套编码里,这样就不会出现乱码了。
Unicode通常用两个字节表示一个字符,原有的英文编码从单字节变成双字节,只需要把高字节全部填充成0就可以。
因为python的诞生比Unicode标准发布的时间还要早,所以最早的python只支持ASCII编码,普通荣字符串'ABC'在python内部是ASCII编码的。
python在后来添加了对Unicode的支持,以Unicode标识的字符串用u'...'表示,比如:
print'中文'
print u'中文'
如果中文字符串在python环境下遇到UnicodeDecodeError,这是因为.Py文件保存的格式有问题。可以在第一行添加注释:
# -*- coding:utf-8 -*-
整数和浮点数
print 11/4
pring 11.0/4
为什么要区分整数运算和浮点数运算呢?这是因为整数运算的结果永远是精确的,二浮点数运算的结果不一定精确,因为计算机内存在打,也无法精确表示出无限循环的小数,比如0.1换成二进制表示就是无限循环的小数。
布尔类型
与或非 and or not
第四章 List和Tuple类型 dict和set类型
—————有序列表:可变有序列表 和 不可变有序列表(元祖)
字典dict:键值对 也是集合 可以len()
4.1创建list
Gender = ['Male','famale']
info = [122,[234,235],'sdf']
empty_list = []
增:info.append('sb')
info.insert(0,'sb')
删:info.pop(2)
改: info[-1] = 'hehe'
查:①按照索引访问list:
info[0]
info[1][2]
②倒序访问list:
info[-1]
4.2创建Tuple
Gender = ('Male','Female')
tuple没有append() insert() pop()
单元素tuple:为了避免歧义,加了个逗号
t = (1)
t = (1,)
print t
4.3创建字典
marks = {
'政治':86,
'数学':134,
'英语':87,
'专业课':143
}
增:
marks['专业课2'] = 145
删:
改:
marks['政治'] = 88
查:
print marks['数学']
print marks.get('专业课')
由于dict也是一个集合,所以,遍历dict和遍历list类似,都可以通过for循环实现。
for key in marks:
print marks[key]
字典的特点:
dict的第一个特点是查找速度快,无论dict有10个元素还是10w个元素,查找速度都一样,而list的查找速度都一样,而list的查找速度随着元素的增加而下降。
不过dict的查找速度模块不是有代价的,dict的缺点是占用内存大,还会浪费很多内容,list正好相反,占用内存小,但是查找速度很慢。
由于dict是按key查找,所以,在一个dict中key 不能重复。
dict的第二个特点是key-value没有顺序的,这和list不一样。
dict的第三个特点是key的元素必须是不可变。python的笔本数据类型如字符串,整数,浮点数,都是不可变的,都可以作为key。但是list是可变的,就不能作为key。
4.4 创建Set
>>> s = set(['Adam','Lisa','Bart',"Paul"])
>>> 'Bart' in s
True
增:
s.add("John")
删:
s.remove("Lisa")
改:先增后删
查:
遍历set
for name in s:
print name
set的特点:
set集合里的元素不会重复,无序的
dict的作用是建立一组key和一组value的映射关系,dict的key是不能重复的。
有的时候,我们只想要dict的key,不关心key对应的value,目的就是保证这个集合的元素不会重复,这是set就排上用场了。
set有一系列的元素,这个和list很像,但是set没有重复的元素。\
set的内部结构和dict很像,唯一区别就是不存储value,因此,判断一个元素是否在set很快。
set存储的元素和dict的key类似,必须是不变的对象,因此,任何可变对象是不能存入set中的。
set是无序的,这个和dict的key很像。
第六章 条件判断和循环
if-elif-else
while
break
continue
多重循环
第七章 函数 == 代码的抽象形式
只写一次,调用多次
抽象:比如加和符号 抽象记法易理解。
抽象可扩展 (通过传参)
可见,借助抽象我们才能不关心底层的具体实现,而是直接在更高层次上思考问题。
定义函数:
def my_abs(x):
if x>0:
return x
else:
return -x
函数返回多值:
import math
def move(x,y,step,angle):
nx = x+step*math.cos(angle)
ny=y - setp * math.sin(angle)
return nx,ny
>>>x,y = move(100,100,60,math.pi /6)
>>> print x,y
151.96152422706632,70
但其实这是一种假象,python函数返回的仍然是单一值:
>>>r = move(100,100,60,math.pi /6)
print r
(151.96152422706632,70) 元组
递归函数
在函数内部,可以调用其它函数。如果一个函数内部调用自己本身,这个函数就是递归函数。
fact(n)=n*fact(n-1)
于是:
def fact(n):
if n==1:
return 1
return n * fact(n-1)
递归的优点是定义简单,逻辑清晰,理论上,所有的递归函数都可以写成循环的方式,但循环的方式不如递归清晰。
使用递归函数需要注意防止栈溢出。在计算机中,函数的调用是通过栈(stack)这种数据结构来实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减少一层栈帧。由于栈的大小不是无限的,所以,递归调用次数过多,会导致栈溢出,可以试试计算fact(10000)
定义默认参数
def power(x,n=2):
s = 1
while n > 0:
n = n-1
s = s * x
return s
定义可变参数
如果想让一个函数能接受人一个参数,我们就可以定义一个可变参数:
def fn(*arg):
print arg
可变参数前面有个*,我们可以传入0个或者1个或者多个参数给可变参数。
>>> fn()
()
>>>fn('a')
('a',)
>>>fn('a','b')
('a','b')
>>>fn('a','b','c')
('a','b','c')
可变参数也不是很神秘,python解析器会把传入的一组参数组装成一个tuple传递个可变参数,因此,在函数内部,直接把变量args看成一个tuple就好了。
定义可变参数的目的也是为了简化调用,加入我们要计算任意个数的平均值,就可以定义一个可变参数。
第八章 切片
背景:
取前N个元素
>>> r = []
>>> n = 3
>>> for i in range(n):
r.append(L[i])
这种经常取指定范围的操作,用循环之分繁琐,python提供了切片slice操作符,能够大大简化这种操作
>>>L[0:3]
从索引0开始取,直到索引3为止,但不包括索引3,即索引0,1,2三个元素。
如果第一索引是0,还可以省略:
>>>L[:3]
倒序切片
对字符串进行切片
>>> 'ABCDEFG'[:3]
'ABC'
>>> 'ABCDERG'[-3:]
‘EFG’
>>>'ABCDEFG'[::2]
'ACEG'
在很多编程语言中,针对字符串提供了各种截取函数,其实目的就是对字符串切片。python没有针对字符串截取函数,只需要切片一个操作就可以完成,非常简单。
第九章 迭代
python的for循环不仅可以用在list或tupic上,还可以是用在其他任何可迭代对象上。
因此,迭代操作就是对于一个集合,无论这个集合是有序还是无序,我们用for循环总是可以一次取出集合的每一个元素。
注意:集合石指包含一组元素的数据结构,、
我们已经介绍的包括:
1、有序集合:list,tuple,str和unicode;
2、无序集合:set
3、无序集合且有key-value对:dict
索引迭代
python中,迭代永远是取出元素本身,而非元素的索引。
对于有续集和,元素确实是有索引的。有的时候,我们确实想在for循环中拿到索引,怎么办?
方法是使用enumerate()函数:
>>>L = ['Adam','Lisa','Bart','Paul']
>>> for index,nhame in enumerate(L):
print index,'-',name
使用enumerate(),我们可以在for循环中间时绑定索引index和元素name,但是,这不是enumerate()的特殊语法。实际上,enumerate()函数把
['Adam','Lisa','Bart','Paul'] 变成了类似[(0,'Adam'),(1,'Lisa'),(2,'Bart'),(3,'Paul')]
因此迭代的每个元素实际上是一个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,再迭代,就同时获得了索引和元素本身
第十章 列表生成式
生成列表
>>>range(1,11)
[1,2,3,4,5,6,7,8,9,10]
但如果要生成 [1x1,2x2,3x3,4x4,5x5,6x6,7x7,8x8,9x9,10x10]怎么做?
方法一:循环
>>> L = []
>>>for x in range(1,11):
>>>L.append(x*x)
>>>L
[1,4,9,16,25,36,49,64,81,100]
方法二:列表生成式
>>>[x * x for x in range(1,11)]
[1,4,9,16,25,36,49,64,81,100]
这种写法是python特有的列表生成式,把要生成的x*x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。
复杂表达式
for循环不仅可以迭代普通的list,还可以迭代字典:
假设有如下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.iteritems()]
print'<table>'
print '<tr><th>Name</th>'
<th>Score</th><tr>'
print '\n'.join(tds)
print '</table>'
条件过滤
列表生成的for循环后期还可以加上if判断,例如
>>> [x * x for x in range(1,11)]
[1,4,9,16,25,36,49,64,81,100]
如果我们只想要偶数的平方,不改动range()的情况下,可以加上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)
函数式编程
函数:function
函数式:functional ,一种编程范式
Python语言----->函数式
C语言 ------>函数
汇编语言
计算机硬件 ---->指令
函数式编程特点:
1、把计算机视为函数而非指令 ,不贴近于计算机而贴近于计算
2、纯函数式编程:不需要变量,没有副作用,测试简单
3、支持高阶函数,代码简洁
python支持的函数式编程特点:
1、不是纯函数式编程概念:允许有变量
2、支持高阶函数:函数也可以作为变量输入
3、支持闭包:有了闭包就能返回函数
4、有限度地支持匿名函数
高阶函数
变量可以指向函数,并且直接对这个变量进行调用和对这个函数进行调用的结果uoshi一样的
>>>f = abs
>>>f(-29)
29
函数名其实就是指向余函数的变量,函数名和普通的变量没有什么区别,它指向的只是一个函数对象
高阶函数 :能接受函数作为参数的函数
1、变量可以指向函数
2、函数的参数可以接收变量、
3、一个函数可以接受另一个函数作为参数
4、能接收函数作为参数的函数就是高阶函数。
demo:接收abs函数
定义一个函数:接收x,y,f三个参数
其中x,y是数值,f是函数
def add(x,y,f)
return f(x) + f(y)
demo:
>>> def add(x,y,f)
... return f(x) - f(y)
>>>add(-5,9,abs)
14
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 f(x):
return x * x
print map(f,[1,2,3,4,5,6,7.8,9])
输入结果
[1,4,9,16,25.36,49,64,81]
reduce()函数
reduce()函数接收的参数和map()类似,一个函数f,一个list,但行为和map()不同,reduce()传入的函数f必须接受两个参数,reduce()对list每隔元素反复调用函数f,并返回最终结果值。
>>def f(x,y):
return x+y
调用reduce(f,[1,3,5,7,9])时,reduce()函数将做如下计算:
先计算头两个元素:f(1,3),结果为4
再把结果和第三个元素相加,结果为9
...
返回25
上述计算实际上是对list的所有元素求和,虽然python内置了求和函数sum(),但是利用reduce()求和也很简单。
reduce()还可以接收第三个可选参数,作为计算的初始值。
>>>reduce(f,[1,3,5,7,9],100)
125
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,12]
利用filter(),可以完成很多有用的功能,例如,删除None或者空字符串:
def is_not_empty(s):
return s and len(s.strip())>0
filter(is_not,['test'],None,'str',' ','END')
a = ' 123'
a.strip()
结果: '123'
a='\t\t123\r\n'
a.strip()
结果:'123'
自定义排序函数
python内置的sorted()函数客队list进行排序:
>>>sorted([36,5,12,9,21])
但sorted()也是一个高阶函数,它可以接收一个比较函数来实现自定义排序,比较函数的定义是,出入两待比较的元素x,y,如果x应该排在y的前面,返回-1,如果x应该排在y的后面,返回1,如果x==y,返回0
def reversed_cmp(x,y):
if x > y:
return -1
elif x < y:
return 1
return 0
这样,调用sorted()并传入reversd_cmp就可以实现倒序排序
>>>sorted([36,5,12,9,21],reversed_cmp)
返回函数
python的函数不但可以返回int、str、dict等数据类型,还可以返回函数
def f():
print 'call f()...'
#定义函数g
def g():
print 'call g()...'
return g
仔细观察上面的函数定义,我们在函数f内部又定义了一个函数g,由于函数g也是一个对象,函数名g就是指向函数g的变量,所以,最外层函数g可以返回变量g,也就是函数g本身。
>>>x = f()
call f()...
>>>x
<function g at 0x1027bf320>
>>>x()
call g()...
返回函数可以把一些计算延迟执行。例如,如果定义一个普通的求和函数:
def calc_sum(lst):
return sum(lst)
调用calc_sum()函数时,将立刻计算并得到结果:
>>> calc_sum([1, 2, 3, 4])
10
但是,如果返回一个函数,就可以“延迟计算”:
def calc_sum(lst):
def lazy_sum():
return sum(lst)
return lazy_sum
# 调用calc_sum()并没有计算出结果,而是返回函数:
>>> f = calc_sum([1, 2, 3, 4])
>>> f
<function lazy_sum at 0x1037bfaa0>
# 对返回的函数进行调用时,才计算出结果:
>>> f()
10
由于可以返回函数,我们在后续代码中可以决定到底要不要调用这个函数。
闭包
在函数内部定义的函数和外部定义的函数是一样的,只是他们无法被被外部访问:
像calc_sum()函数那样,内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包
闭包的特点是返回的函数还引用了外层函数的局部变量,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。
举例如下:
# 希望一次返回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()函数返回了3个函数时,这3个函数所引用的变量 i 的值已经变成了3。由于f1、f2、f3并没有被调用,所以,此时他们并未计算 i*i,当 f1 被调用时:
>>> f1()
9 # 因为f1现在才计算i*i,但现在i的值已经变为3
因此,返回函数不要引用任何循环变量,或者后续会发生变化的变量。
匿名函数
高阶函数可以接受函数做参数,有些时候不需要显示地定义函数,直接传入匿名函数更方便。在python中,对匿名函数提供了有限的支持。还是以map()为例,计算f(x)=x^2 时,出了定义一个f(x)的函数外,还可以传入匿名函数:
>>>map(lambda x:x*x,range(1,10))
[1,4,9,16,25,36,49,64,81]
关键字 lambda表示匿名函数,就是只能有一个表达式,不写return,返回值就是该表达式的结果。
使用匿名函数,可以不必定义函数名,直接创建一个函数对象,很多时候可以简化代码。
>>>sorted([1,3,9,5,0],lambda x,y:-cmp(x,y))
装饰器
定义了一个函数,想在运行时动态增加功能,又不想改动函数本身代码
方法一:直接修改函数、
方法二:通过高阶函数返回新函数
def f1(x):
return x*2
def new_fn(f):
def fn(x):
print 'call' + f.__name__+'()'
return f(x)
return fn
这个fn(x)函数就叫做装饰器函数
调用装饰器函数:
方法一:
g1 = new_fn(f1)
print g1(5)
方法二:(推荐)
f1 = new_fn(f1)
print f1(5)
f1的原始定义被彻底隐藏掉了
装饰器
python 内置的 @ 语法就是为了简化装饰器的调用
@new_fn def f1(x):
def f1(x): =====>
return x*2 f1 = new_fn(f1)
装饰器的作用:
可以极大地简化代码,避免每隔函数编写重复性代码。
打印日志:@log
检测性能:@performance
数据库事务:@transaction
URL路由:@post(’/regester‘)
编写无参数decorator
python的decorator本身就是一个高阶函数,他接受一个函数作为参数,然后,返回一个新函数
使用decorator用python提供的@语法,这样可以避免手写f=decorate(f)这样的代码
考察一个@log的定义:
def log(f):
def fn(x):
print 'call' + f.__name__+'()...'
return f(x)
return fn
对于阶乘函数,@log工作做得好:
@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)
结果:
Traceback (most recent call last):
File "test.py", line 15, in <module>
print add(1,2)
TypeError: fn() takes exactly 1 argument (2 given)
因为 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装饰器:
完善decorator
@decorator可以动态实现函数功能的增加,但是经过@decorator”改造“后的函数,和原函数相比,除了功能多一点外,有没有其他不同的地方?
在没有decorator的情况下,打印函数名:
def f1(x):
pass
print f1.__name__
输出f1
在有decorator的情况下,再打印函数名:
def log(f):
def wrapper(*args,**kw):
print 'call...'
return f(*args,**kw)
return wrapper
@log f2(x):
pass
print f2.__name__
输出wrapper
可见,由于decorator返回的新函数名已不是'f2',而是@log内部定义的'wraper'。这对那些依赖函数名的代码就会失效。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.wrap(f)
def wrapper(*args,**kw)
print 'call...'
return f(*args,**kw)
return wrapper
最后需要指出,由于我们把原函数签名改成了(*args,**kw),因此,无法获得原函数的参数信息。即便我们采用固定参数来装饰只有一个函数的参数
def log(f):
@functools.wrap(f)
def wrapper(x):
print 'call...'
return f(x)
return wrapper
也可能改变原函数的参数名,因为新函数的参数名始终是'x',原函数定义的参数名不一定是'x'
偏函数
当一个函数有很多参数时,调用者就需要提供多个参数,如果减少参数个数,就可以简化调用者的负担。
比如,int()函数可以把字符串转化为整数,当仅传入字符串时,int()默认按照十进制转换:
>>>int('12345')
12345
但int()还提供而外的base参数,默认值是10.如果传入base参数,就可以做N进制的转换:
>>>int('12345',base=8)
5349
>>>int('12345',16)
74565
假设转换大量的二进制字符串,每次都传入int(x,base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去。
def int2(x,base=2):
return int(x,base)
这样,我们转换二进制就非常方便了:
>>>int2('100000')
64
>>>int2('1010101')
85
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2():
>>>import functools
>>>int2 = functools.partial(int,base=2)
>>>int2('100000')
64
所以,functools.partial可以把一个参数多的函数变成一个参数少的新函数,少的参数需要在创建时指定默认值,这样,心函数调用的难度就降低了。
第三章 模块和包的概念
将所有代码放入一个py文件:无法维护
分开:易于维护,同一个名字的变量在不同的py文件中互不影响
模块a 模块b
#a.py #b.py
x = 5 x = 'str'
def f1(): def f1(iterable):
pass pass
模块的名字就是py文件的文件名
在一个模块中如何引用其他模块?
#test.py 自身模块名
import math #引用math模块
print math.pow(2,10) #调用math模块的函数
模块多了以后,模块名容易冲突,解决方案是把不同的模块放入不同的包中就可以防止模块名冲突。
引用完整的模块名
#test.py
import pq.util
print p1.util.f(2,10)
在文件系统中,包就是文件夹,而模块就是.py文件。包可以有多级
如何区分包和普通目录
包下面有个__init__.py文件,注意每一层都有,这样python才能把文件夹当成一个包来处理。
导入模块
要使用一个模块,我们必须首先导入该模块,python使用import语句导入一个模块。
import math
你可以认为math就是一个指向已导入模块的变量,通过该变量,我们可以访问math模块中所定义的公开函数、变量和类
如果我们只希望用到math中的某几个函数,而不是所有函数,可以用下面的语句:
from math import pow,sin,log
这样可以直接用pow,sin,log这三个函数,但math的其他函数没有导进来。
>>>pow(2,10)
1024.0
如果遇到名字冲突怎么办?比如math模块有一个log函数,logging模块也有一个log函数,如果同时使用,如何解决名字冲突?
如果使用import导入模块名,由于必须通过模块名引用函数名,因此不存在冲突:
import math, logging
print math.log(10) # 调用的是math的log函数
logging.log(10, 'something') # 调用的是logging的log函数
如果使用 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
动态导入模块
如果导入的模块不存在,python解释器就会报importError错误:
>>> import something
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named something
有时候,两个不同的模块提供了相同的功能,比如StringIO和cSTringIO都提供了StingIO这个功能。、
这是因为python是动态语言,解释执行,因此python代码速度慢
如果提高python代码的速度,最简单的方法就是把某些关键函数用C语言重写,这样就能大大提高执行速度。
同样的功能,StringIO是纯用Python代码写的,而cStringIO部分函数是用C写的,因此cStringIO执行速度更快。
利用importError错误,我们经常在python中动态导入模块:
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
上述代码尝试从cStringIO中导入,如果失败了(比如cStringIO没有被安装),再尝试从StringIO导入。这样,如果cStringIO模块存在,则我们将获得更快的运行速度。如果不存在,顶多影响速度,而不会影响正常执行。
使用__future__
python的新版本会引入新的功能,但是,实际上这些功能在上一个老版本中就已经存在了。要”试用“某一个特征,就可以通过导入__future__模块的某些功能来实现。
例如,python2.7的整数除法的运算结果仍是整数
>>>10 / 3
3
但是 python 3.x已经改进了整数除法运算,/得到浮点数,//才是整数
>>> 10 / 3
3.3333333333333335
>>> 10 // 3
3
要在python2.7中引用3.x的除法法则,导入__future__的division
>>>from __future__ import devision
>>>print 10/3
3.333333333335
当新版本的一个特性与旧版本不兼容时,该特性会在旧版本中添加到__future__中,以便旧的代码能在旧的版本中测试新特性。
安装第三方模块
python提供的模块管理工具
--easy_install
--pip(推荐,已内置到python2.7.9)
cmd:
pip install web.py
如果要查询第三方模块的名字 可以到python.org中搜索相关关键字 web.py
python面向对象编程
面向对象编程是一种程序设计范式,把程序看作不同对象的相互调用,对现实世界建立对象模型
基本思想 类和实例
最重要的思想:数据封装
定义类并创建实例
class Person(object):
pass
类名以大写开头,紧接着是object,表示该类是从哪个类中继承下来的。
xiaoming = Person()
xiaohong = Person()
创建实例的属性
如何让每个实例拥有各自不同的属性?由于python是动态语言,对每个实例,都可以直接给他们的属性赋值,例如:
xiaoming = Person()
xiaoming.name = 'Xiao Ming'
xiaoming.gender = 'Male'
xiaoming.birth = '1990-2-3'
给小红加上的属性不一定和小明相同:
xiaohong = Person()
xiaohong.name = 'Xiao Hong'
xiaohong.school = 'No. 1 High School'
xiaohong.grade = 2
实例的属性可以像普通变量一样进行操作:
xiaohong.grade = xiaohong.grade + 1
>>> class Person(object):
pass
>>> xiaoming = Person()
>>> xiaoming.name = 'Xiao Ming'
>>> xiaohong = Person()
>>> xiaohong.grade = 2
>>> print xiaoming.grade
Traceback (most recent call last):
File "<pyshell#371>", line 1, in <module>
print xiaoming.grade
AttributeError: 'Person' object has no attribute 'grade'
>>> print xiaoming.name
Xiao Ming
初始化实例的属性
在定义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以外的参数。
xiaoming = Person('Xiao Ming','Male','1990-2-3')
访问限制
python对属性权限的控制是通过属性名来实现的,如果一个属性是由__双下划线开头,该属性就无法被外部访问。
class Person(object):
def __init__(self,name):
self.name = name
self.title = 'Mr'
self.__job = 'Student'
p = Person('Bob')
print p.name
Bob
print p.__job
Error
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute '__job'
但是一个属性已__xxx__形式定义,那他又可以被外部访问了,以__xxx__定义的属性在python的类中被称为特殊属性,很多预定义的特殊属性可以使用,通常我们不要把普通的变量用属性__xxx__形式定义。
单下划线开头的属性_xxx虽然也可以被外部访问,但是,按照习惯,他们不该被外部访问。
创建类属性
实例属性每个实例各自拥有,相互独立,而类属性有且只有一份。
定义类属性可以在类中定义:
class Person(object):
address = 'Earth'
def __init__(self,name):
self.name = name
因为类属性是直接绑定在类上的,所以,访问类属性不需要创建实例,就可以直接访问。
print Person.address
Earth
对一个实例调用类属性也是可以的,所有实例都可以访问到它所属的类属性:
由于python是动态语言,雷属性也是可以动态添加和修改的
Person.address = 'China'
类属性和实例属性名字冲突怎么办
修改类属性会导致所有实例访问到的类属性全部都受影响,但是,如果在实例变量上修改该类属性会发生什么问题能?
会只修改该实例 的对应类属性,但是其他实例类属性不被修改
可见,当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问。
可见,千万不要在实例上修改类属性,它实际上并没有修改类属性,而是给实例绑定了一个实例属性。
定义实例方法
实例的方法就是类中定义的函数,他的第一个参数永远是self,指向调用该方法的实例本身,其他参数和一个普通函数是完全一样的:
class Person(object):
def __init(self,name):
self.__name = name
def get_name(self):
return self.__name
在实例的内部,可以访问所有实例属性,这样,如果外部需要访问私有属性,可以通过方法调用获得,这种数据封装形式除了能保护内部的数据一致性外,还可以简化外部调用的难度。
方法也是属性
我们在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()才是方法调用
因为方法也是属性,所以,它可以动态地添加到实例上,只是需要用type.MethodType()把一个函数变为一个方法:
import types
def fn_get_grade(self):
if self.score >= 80:
return 'A'
elif 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(gn_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中定义要更直观。
定义类方法
和属性相类似,方法也分实例方法和类方法
在class中定义的放不是实例方法,实例方法第一个参数self是实例本身。
要在class中定义类方法,要这么写:
class Person(object):
count = 0
@classmethod
def how_many(cls):
return cls.count
def __init__(self,name):
Person.count = Person.count + 1
print Person.how_many()
p1.Person('Bob')
print Person.how_many
通过标记一个@classMethod,该方法绑定到Person类上,而非实例。 类方法的第一个参数传入到类本身,通常将参数名命名为cls,上面的cls.count实际上相当于Person.count
因为是在类上调用,而非实例上调用,因此类方法无法获得任何系能力变量,只能获得类的引用。
类的继承
class Person(object):
def __init__(self,name,gender):
self.name = name
self.gender = gender
class Student(Person):
def __init__(self,name,gender,school,score):
super(Student,self).__init__(name,gender):
self.school = school
self.score = score
继承是is关系 如:Student is a Person
如果两个类是has关系,则用组合。如:Student has a Book
class Student(Person):
def __init__(self,bookName):
self.book = Book(bookName)
Book类应该作为Student的一个属性来出现。
Python继承的特点
总是从单个类中继承
不要忘记调用super().__init__
def __init__(self,args):
super(SubClass,self).__init__(args)
pass
一定要用 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允许从霍格父类继承,成为多重继承
多重继承的继承链就不是一棵树了
像这样,D 同时继承自 B 和 C,也就是 D 拥有了 A、B、C 的全部功能。多重继承通过 super()调用__init__()方法时,A 虽然被继承了两次,但__init__()只调用一次:
>>> d = D('d')
init A...
init C...
init B...
init D...
多重继承的目的是从两种继承树中分别选择并继承出子类,以便组合功能使用。
举个例子,Python的网络服务器有TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer,而服务器运行模式有 多进程ForkingMixin 和 多线程ThreadingMixin两种。
要创建多进程模式的 TCPServer:
class MyTCPServer(TCPServer, ForkingMixin)
pass
要创建多线程模式的 UDPServer:
class MyUDPServer(UDPServer, ThreadingMixin):
pass
如果没有多重继承,要实现上述所有可能的组合需要 4x2=8 个子类。
获取对象信息
isinstance()判断是否是某种类型的实例
type(s)
dir(s) 获取变量的所有属性
>>> type(123)
<type 'int'>
>>> s = Student('Bob', 'Male', 88)
>>> type(s)
<class '__main__.Student'>
>>> 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')
>>>s.name
特殊方法
>>>print lst
[1,2,3]
>>>print p
<__main__.Person object at 0x10da9e850>
python 如何把任意变量变成一个str?
因为任何数据类型实例都有一个特殊方法
__str__()
打印list或者Person实例 都是调用的__str__()方法
如果给Person类加上一个__str__()这个特殊方法
就可以按照自己的想法打印出Person的实例来
python特殊方法:
__str__(): 用于print
__len__(): 用于len
__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)
>>> 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 Person(object):
def __init__(self,name,gender):
self.name = name
self.gender = gender
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类实现了按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)
__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
数学运算
python提供对基本数据类型int、float可以做整数和浮点数的四则运算以及乘方等运算。
但是,四则运算不仅局限于int和float,还可以是有理数、矩阵等。
如果要让Rational进行+运算,需要正确实现__add__:
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 __str__(self):
return '%s/%s' % (self.p, self.q)
__repr__ = __str__
现在可以试试有理数加法:
>>> r1 = Rational(1, 3)
>>> r2 = Rational(1, 2)
>>> print r1 + r2
5/6
类型转换
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__()。
@property,可以将python定义的函数“当做”属性访问,从而提供更加友好访问方式
@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) 就会报错。
这种使用 get/set 方法来封装对一个属性的访问在许多面向对象编程的语言中都很常见。
但是写 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装饰后的副产品。
现在,就可以像使用属性一样设置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__也能节省内存。
__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中,函数也是对象,对象和函数的区别并不显著。
Python简介
1989 龟叔
python特点:优雅明确简单
适合开发:
web网站和各种网络服务
系统工具和脚本
作为胶水语言把其他语言开发的模块包装起来方便使用
python不适合的领域:
贴近硬件的代码
移动开发
游戏开发:C C++
运行速度 代码量
C 编译为机器码 非常快 非常多
Java 编译为字节码 快 多
Python 解释执行 慢 少
速度的瓶颈在于数据库和网络的调用
python源码不能加密:不卖源码 卖服务
python跨平台
2.7版 3.3版 不兼容
第三方库
python2.7.8
第三章 Python变量和数据类型
raw字符串和多行字符串:
raw字符串:
print r'a\t\n\\b'
print 'a\t\n\\b'
多行字符串
print '''a
b'''
print 'a
b'
Unicode字符串
字符串还有个编码问题,计算机只能处理数字,要想处理文本,就必须把文本转换为数字才能处理。最早的计算机在设计时采用8个比特作为一个字节,所以,一个字节能表示的最大整数就是255(二进制11111111=十进制255),0-255被用来表示小写字母、数字和一些符号,这个编码表成为ASCII编码,比如大写字母A的编码是65,小写字母z的编码是122
如果要表示中文,一个字节显然是不够的,至少要两个字节而且还不能和ASCII编码冲突,所以,中国制定了GB2312编码,用来把中文编进去。
类似的,日文和韩文等其他语言也有这个问题。为了统一所有文字的编码,Unicode应运而生,Unicode把所有语言都统一到一套编码里,这样就不会出现乱码了。
Unicode通常用两个字节表示一个字符,原有的英文编码从单字节变成双字节,只需要把高字节全部填充成0就可以。
因为python的诞生比Unicode标准发布的时间还要早,所以最早的python只支持ASCII编码,普通荣字符串'ABC'在python内部是ASCII编码的。
python在后来添加了对Unicode的支持,以Unicode标识的字符串用u'...'表示,比如:
print'中文'
print u'中文'
如果中文字符串在python环境下遇到UnicodeDecodeError,这是因为.Py文件保存的格式有问题。可以在第一行添加注释:
# -*- coding:utf-8 -*-
整数和浮点数
print 11/4
pring 11.0/4
为什么要区分整数运算和浮点数运算呢?这是因为整数运算的结果永远是精确的,二浮点数运算的结果不一定精确,因为计算机内存在打,也无法精确表示出无限循环的小数,比如0.1换成二进制表示就是无限循环的小数。
布尔类型
与或非 and or not
第四章 List和Tuple类型 dict和set类型
—————有序列表:可变有序列表 和 不可变有序列表(元祖)
字典dict:键值对 也是集合 可以len()
4.1创建list
Gender = ['Male','famale']
info = [122,[234,235],'sdf']
empty_list = []
增:info.append('sb')
info.insert(0,'sb')
删:info.pop(2)
改: info[-1] = 'hehe'
查:①按照索引访问list:
info[0]
info[1][2]
②倒序访问list:
info[-1]
4.2创建Tuple
Gender = ('Male','Female')
tuple没有append() insert() pop()
单元素tuple:为了避免歧义,加了个逗号
t = (1)
t = (1,)
print t
4.3创建字典
marks = {
'政治':86,
'数学':134,
'英语':87,
'专业课':143
}
增:
marks['专业课2'] = 145
删:
改:
marks['政治'] = 88
查:
print marks['数学']
print marks.get('专业课')
由于dict也是一个集合,所以,遍历dict和遍历list类似,都可以通过for循环实现。
for key in marks:
print marks[key]
字典的特点:
dict的第一个特点是查找速度快,无论dict有10个元素还是10w个元素,查找速度都一样,而list的查找速度都一样,而list的查找速度随着元素的增加而下降。
不过dict的查找速度模块不是有代价的,dict的缺点是占用内存大,还会浪费很多内容,list正好相反,占用内存小,但是查找速度很慢。
由于dict是按key查找,所以,在一个dict中key 不能重复。
dict的第二个特点是key-value没有顺序的,这和list不一样。
dict的第三个特点是key的元素必须是不可变。python的笔本数据类型如字符串,整数,浮点数,都是不可变的,都可以作为key。但是list是可变的,就不能作为key。
4.4 创建Set
>>> s = set(['Adam','Lisa','Bart',"Paul"])
>>> 'Bart' in s
True
增:
s.add("John")
删:
s.remove("Lisa")
改:先增后删
查:
遍历set
for name in s:
print name
set的特点:
set集合里的元素不会重复,无序的
dict的作用是建立一组key和一组value的映射关系,dict的key是不能重复的。
有的时候,我们只想要dict的key,不关心key对应的value,目的就是保证这个集合的元素不会重复,这是set就排上用场了。
set有一系列的元素,这个和list很像,但是set没有重复的元素。\
set的内部结构和dict很像,唯一区别就是不存储value,因此,判断一个元素是否在set很快。
set存储的元素和dict的key类似,必须是不变的对象,因此,任何可变对象是不能存入set中的。
set是无序的,这个和dict的key很像。
第六章 条件判断和循环
if-elif-else
while
break
continue
多重循环
第七章 函数 == 代码的抽象形式
只写一次,调用多次
抽象:比如加和符号 抽象记法易理解。
抽象可扩展 (通过传参)
可见,借助抽象我们才能不关心底层的具体实现,而是直接在更高层次上思考问题。
定义函数:
def my_abs(x):
if x>0:
return x
else:
return -x
函数返回多值:
import math
def move(x,y,step,angle):
nx = x+step*math.cos(angle)
ny=y - setp * math.sin(angle)
return nx,ny
>>>x,y = move(100,100,60,math.pi /6)
>>> print x,y
151.96152422706632,70
但其实这是一种假象,python函数返回的仍然是单一值:
>>>r = move(100,100,60,math.pi /6)
print r
(151.96152422706632,70) 元组
递归函数
在函数内部,可以调用其它函数。如果一个函数内部调用自己本身,这个函数就是递归函数。
fact(n)=n*fact(n-1)
于是:
def fact(n):
if n==1:
return 1
return n * fact(n-1)
递归的优点是定义简单,逻辑清晰,理论上,所有的递归函数都可以写成循环的方式,但循环的方式不如递归清晰。
使用递归函数需要注意防止栈溢出。在计算机中,函数的调用是通过栈(stack)这种数据结构来实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减少一层栈帧。由于栈的大小不是无限的,所以,递归调用次数过多,会导致栈溢出,可以试试计算fact(10000)
定义默认参数
def power(x,n=2):
s = 1
while n > 0:
n = n-1
s = s * x
return s
定义可变参数
如果想让一个函数能接受人一个参数,我们就可以定义一个可变参数:
def fn(*arg):
print arg
可变参数前面有个*,我们可以传入0个或者1个或者多个参数给可变参数。
>>> fn()
()
>>>fn('a')
('a',)
>>>fn('a','b')
('a','b')
>>>fn('a','b','c')
('a','b','c')
可变参数也不是很神秘,python解析器会把传入的一组参数组装成一个tuple传递个可变参数,因此,在函数内部,直接把变量args看成一个tuple就好了。
定义可变参数的目的也是为了简化调用,加入我们要计算任意个数的平均值,就可以定义一个可变参数。
第八章 切片
背景:
取前N个元素
>>> r = []
>>> n = 3
>>> for i in range(n):
r.append(L[i])
这种经常取指定范围的操作,用循环之分繁琐,python提供了切片slice操作符,能够大大简化这种操作
>>>L[0:3]
从索引0开始取,直到索引3为止,但不包括索引3,即索引0,1,2三个元素。
如果第一索引是0,还可以省略:
>>>L[:3]
倒序切片
对字符串进行切片
>>> 'ABCDEFG'[:3]
'ABC'
>>> 'ABCDERG'[-3:]
‘EFG’
>>>'ABCDEFG'[::2]
'ACEG'
在很多编程语言中,针对字符串提供了各种截取函数,其实目的就是对字符串切片。python没有针对字符串截取函数,只需要切片一个操作就可以完成,非常简单。
第九章 迭代
python的for循环不仅可以用在list或tupic上,还可以是用在其他任何可迭代对象上。
因此,迭代操作就是对于一个集合,无论这个集合是有序还是无序,我们用for循环总是可以一次取出集合的每一个元素。
注意:集合石指包含一组元素的数据结构,、
我们已经介绍的包括:
1、有序集合:list,tuple,str和unicode;
2、无序集合:set
3、无序集合且有key-value对:dict
索引迭代
python中,迭代永远是取出元素本身,而非元素的索引。
对于有续集和,元素确实是有索引的。有的时候,我们确实想在for循环中拿到索引,怎么办?
方法是使用enumerate()函数:
>>>L = ['Adam','Lisa','Bart','Paul']
>>> for index,nhame in enumerate(L):
print index,'-',name
使用enumerate(),我们可以在for循环中间时绑定索引index和元素name,但是,这不是enumerate()的特殊语法。实际上,enumerate()函数把
['Adam','Lisa','Bart','Paul'] 变成了类似[(0,'Adam'),(1,'Lisa'),(2,'Bart'),(3,'Paul')]
因此迭代的每个元素实际上是一个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,再迭代,就同时获得了索引和元素本身
第十章 列表生成式
生成列表
>>>range(1,11)
[1,2,3,4,5,6,7,8,9,10]
但如果要生成 [1x1,2x2,3x3,4x4,5x5,6x6,7x7,8x8,9x9,10x10]怎么做?
方法一:循环
>>> L = []
>>>for x in range(1,11):
>>>L.append(x*x)
>>>L
[1,4,9,16,25,36,49,64,81,100]
方法二:列表生成式
>>>[x * x for x in range(1,11)]
[1,4,9,16,25,36,49,64,81,100]
这种写法是python特有的列表生成式,把要生成的x*x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。
复杂表达式
for循环不仅可以迭代普通的list,还可以迭代字典:
假设有如下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.iteritems()]
print'<table>'
print '<tr><th>Name</th>'
<th>Score</th><tr>'
print '\n'.join(tds)
print '</table>'
条件过滤
列表生成的for循环后期还可以加上if判断,例如
>>> [x * x for x in range(1,11)]
[1,4,9,16,25,36,49,64,81,100]
如果我们只想要偶数的平方,不改动range()的情况下,可以加上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)
函数式编程
函数:function
函数式:functional ,一种编程范式
Python语言----->函数式
C语言 ------>函数
汇编语言
计算机硬件 ---->指令
函数式编程特点:
1、把计算机视为函数而非指令 ,不贴近于计算机而贴近于计算
2、纯函数式编程:不需要变量,没有副作用,测试简单
3、支持高阶函数,代码简洁
python支持的函数式编程特点:
1、不是纯函数式编程概念:允许有变量
2、支持高阶函数:函数也可以作为变量输入
3、支持闭包:有了闭包就能返回函数
4、有限度地支持匿名函数
高阶函数
变量可以指向函数,并且直接对这个变量进行调用和对这个函数进行调用的结果uoshi一样的
>>>f = abs
>>>f(-29)
29
函数名其实就是指向余函数的变量,函数名和普通的变量没有什么区别,它指向的只是一个函数对象
高阶函数 :能接受函数作为参数的函数
1、变量可以指向函数
2、函数的参数可以接收变量、
3、一个函数可以接受另一个函数作为参数
4、能接收函数作为参数的函数就是高阶函数。
demo:接收abs函数
定义一个函数:接收x,y,f三个参数
其中x,y是数值,f是函数
def add(x,y,f)
return f(x) + f(y)
demo:
>>> def add(x,y,f)
... return f(x) - f(y)
>>>add(-5,9,abs)
14
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 f(x):
return x * x
print map(f,[1,2,3,4,5,6,7.8,9])
输入结果
[1,4,9,16,25.36,49,64,81]
reduce()函数
reduce()函数接收的参数和map()类似,一个函数f,一个list,但行为和map()不同,reduce()传入的函数f必须接受两个参数,reduce()对list每隔元素反复调用函数f,并返回最终结果值。
>>def f(x,y):
return x+y
调用reduce(f,[1,3,5,7,9])时,reduce()函数将做如下计算:
先计算头两个元素:f(1,3),结果为4
再把结果和第三个元素相加,结果为9
...
返回25
上述计算实际上是对list的所有元素求和,虽然python内置了求和函数sum(),但是利用reduce()求和也很简单。
reduce()还可以接收第三个可选参数,作为计算的初始值。
>>>reduce(f,[1,3,5,7,9],100)
125
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,12]
利用filter(),可以完成很多有用的功能,例如,删除None或者空字符串:
def is_not_empty(s):
return s and len(s.strip())>0
filter(is_not,['test'],None,'str',' ','END')
a = ' 123'
a.strip()
结果: '123'
a='\t\t123\r\n'
a.strip()
结果:'123'
自定义排序函数
python内置的sorted()函数客队list进行排序:
>>>sorted([36,5,12,9,21])
但sorted()也是一个高阶函数,它可以接收一个比较函数来实现自定义排序,比较函数的定义是,出入两待比较的元素x,y,如果x应该排在y的前面,返回-1,如果x应该排在y的后面,返回1,如果x==y,返回0
def reversed_cmp(x,y):
if x > y:
return -1
elif x < y:
return 1
return 0
这样,调用sorted()并传入reversd_cmp就可以实现倒序排序
>>>sorted([36,5,12,9,21],reversed_cmp)
返回函数
python的函数不但可以返回int、str、dict等数据类型,还可以返回函数
def f():
print 'call f()...'
#定义函数g
def g():
print 'call g()...'
return g
仔细观察上面的函数定义,我们在函数f内部又定义了一个函数g,由于函数g也是一个对象,函数名g就是指向函数g的变量,所以,最外层函数g可以返回变量g,也就是函数g本身。
>>>x = f()
call f()...
>>>x
<function g at 0x1027bf320>
>>>x()
call g()...
返回函数可以把一些计算延迟执行。例如,如果定义一个普通的求和函数:
def calc_sum(lst):
return sum(lst)
调用calc_sum()函数时,将立刻计算并得到结果:
>>> calc_sum([1, 2, 3, 4])
10
但是,如果返回一个函数,就可以“延迟计算”:
def calc_sum(lst):
def lazy_sum():
return sum(lst)
return lazy_sum
# 调用calc_sum()并没有计算出结果,而是返回函数:
>>> f = calc_sum([1, 2, 3, 4])
>>> f
<function lazy_sum at 0x1037bfaa0>
# 对返回的函数进行调用时,才计算出结果:
>>> f()
10
由于可以返回函数,我们在后续代码中可以决定到底要不要调用这个函数。
闭包
在函数内部定义的函数和外部定义的函数是一样的,只是他们无法被被外部访问:
像calc_sum()函数那样,内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包
闭包的特点是返回的函数还引用了外层函数的局部变量,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。
举例如下:
# 希望一次返回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()函数返回了3个函数时,这3个函数所引用的变量 i 的值已经变成了3。由于f1、f2、f3并没有被调用,所以,此时他们并未计算 i*i,当 f1 被调用时:
>>> f1()
9 # 因为f1现在才计算i*i,但现在i的值已经变为3
因此,返回函数不要引用任何循环变量,或者后续会发生变化的变量。
匿名函数
高阶函数可以接受函数做参数,有些时候不需要显示地定义函数,直接传入匿名函数更方便。在python中,对匿名函数提供了有限的支持。还是以map()为例,计算f(x)=x^2 时,出了定义一个f(x)的函数外,还可以传入匿名函数:
>>>map(lambda x:x*x,range(1,10))
[1,4,9,16,25,36,49,64,81]
关键字 lambda表示匿名函数,就是只能有一个表达式,不写return,返回值就是该表达式的结果。
使用匿名函数,可以不必定义函数名,直接创建一个函数对象,很多时候可以简化代码。
>>>sorted([1,3,9,5,0],lambda x,y:-cmp(x,y))
装饰器
定义了一个函数,想在运行时动态增加功能,又不想改动函数本身代码
方法一:直接修改函数、
方法二:通过高阶函数返回新函数
def f1(x):
return x*2
def new_fn(f):
def fn(x):
print 'call' + f.__name__+'()'
return f(x)
return fn
这个fn(x)函数就叫做装饰器函数
调用装饰器函数:
方法一:
g1 = new_fn(f1)
print g1(5)
方法二:(推荐)
f1 = new_fn(f1)
print f1(5)
f1的原始定义被彻底隐藏掉了
装饰器
python 内置的 @ 语法就是为了简化装饰器的调用
@new_fn def f1(x):
def f1(x): =====>
return x*2 f1 = new_fn(f1)
装饰器的作用:
可以极大地简化代码,避免每隔函数编写重复性代码。
打印日志:@log
检测性能:@performance
数据库事务:@transaction
URL路由:@post(’/regester‘)
编写无参数decorator
python的decorator本身就是一个高阶函数,他接受一个函数作为参数,然后,返回一个新函数
使用decorator用python提供的@语法,这样可以避免手写f=decorate(f)这样的代码
考察一个@log的定义:
def log(f):
def fn(x):
print 'call' + f.__name__+'()...'
return f(x)
return fn
对于阶乘函数,@log工作做得好:
@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)
结果:
Traceback (most recent call last):
File "test.py", line 15, in <module>
print add(1,2)
TypeError: fn() takes exactly 1 argument (2 given)
因为 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装饰器:
完善decorator
@decorator可以动态实现函数功能的增加,但是经过@decorator”改造“后的函数,和原函数相比,除了功能多一点外,有没有其他不同的地方?
在没有decorator的情况下,打印函数名:
def f1(x):
pass
print f1.__name__
输出f1
在有decorator的情况下,再打印函数名:
def log(f):
def wrapper(*args,**kw):
print 'call...'
return f(*args,**kw)
return wrapper
@log f2(x):
pass
print f2.__name__
输出wrapper
可见,由于decorator返回的新函数名已不是'f2',而是@log内部定义的'wraper'。这对那些依赖函数名的代码就会失效。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.wrap(f)
def wrapper(*args,**kw)
print 'call...'
return f(*args,**kw)
return wrapper
最后需要指出,由于我们把原函数签名改成了(*args,**kw),因此,无法获得原函数的参数信息。即便我们采用固定参数来装饰只有一个函数的参数
def log(f):
@functools.wrap(f)
def wrapper(x):
print 'call...'
return f(x)
return wrapper
也可能改变原函数的参数名,因为新函数的参数名始终是'x',原函数定义的参数名不一定是'x'
偏函数
当一个函数有很多参数时,调用者就需要提供多个参数,如果减少参数个数,就可以简化调用者的负担。
比如,int()函数可以把字符串转化为整数,当仅传入字符串时,int()默认按照十进制转换:
>>>int('12345')
12345
但int()还提供而外的base参数,默认值是10.如果传入base参数,就可以做N进制的转换:
>>>int('12345',base=8)
5349
>>>int('12345',16)
74565
假设转换大量的二进制字符串,每次都传入int(x,base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去。
def int2(x,base=2):
return int(x,base)
这样,我们转换二进制就非常方便了:
>>>int2('100000')
64
>>>int2('1010101')
85
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2():
>>>import functools
>>>int2 = functools.partial(int,base=2)
>>>int2('100000')
64
所以,functools.partial可以把一个参数多的函数变成一个参数少的新函数,少的参数需要在创建时指定默认值,这样,心函数调用的难度就降低了。
第三章 模块和包的概念
将所有代码放入一个py文件:无法维护
分开:易于维护,同一个名字的变量在不同的py文件中互不影响
模块a 模块b
#a.py #b.py
x = 5 x = 'str'
def f1(): def f1(iterable):
pass pass
模块的名字就是py文件的文件名
在一个模块中如何引用其他模块?
#test.py 自身模块名
import math #引用math模块
print math.pow(2,10) #调用math模块的函数
模块多了以后,模块名容易冲突,解决方案是把不同的模块放入不同的包中就可以防止模块名冲突。
引用完整的模块名
#test.py
import pq.util
print p1.util.f(2,10)
在文件系统中,包就是文件夹,而模块就是.py文件。包可以有多级
如何区分包和普通目录
包下面有个__init__.py文件,注意每一层都有,这样python才能把文件夹当成一个包来处理。
导入模块
要使用一个模块,我们必须首先导入该模块,python使用import语句导入一个模块。
import math
你可以认为math就是一个指向已导入模块的变量,通过该变量,我们可以访问math模块中所定义的公开函数、变量和类
如果我们只希望用到math中的某几个函数,而不是所有函数,可以用下面的语句:
from math import pow,sin,log
这样可以直接用pow,sin,log这三个函数,但math的其他函数没有导进来。
>>>pow(2,10)
1024.0
如果遇到名字冲突怎么办?比如math模块有一个log函数,logging模块也有一个log函数,如果同时使用,如何解决名字冲突?
如果使用import导入模块名,由于必须通过模块名引用函数名,因此不存在冲突:
import math, logging
print math.log(10) # 调用的是math的log函数
logging.log(10, 'something') # 调用的是logging的log函数
如果使用 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
动态导入模块
如果导入的模块不存在,python解释器就会报importError错误:
>>> import something
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named something
有时候,两个不同的模块提供了相同的功能,比如StringIO和cSTringIO都提供了StingIO这个功能。、
这是因为python是动态语言,解释执行,因此python代码速度慢
如果提高python代码的速度,最简单的方法就是把某些关键函数用C语言重写,这样就能大大提高执行速度。
同样的功能,StringIO是纯用Python代码写的,而cStringIO部分函数是用C写的,因此cStringIO执行速度更快。
利用importError错误,我们经常在python中动态导入模块:
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
上述代码尝试从cStringIO中导入,如果失败了(比如cStringIO没有被安装),再尝试从StringIO导入。这样,如果cStringIO模块存在,则我们将获得更快的运行速度。如果不存在,顶多影响速度,而不会影响正常执行。
使用__future__
python的新版本会引入新的功能,但是,实际上这些功能在上一个老版本中就已经存在了。要”试用“某一个特征,就可以通过导入__future__模块的某些功能来实现。
例如,python2.7的整数除法的运算结果仍是整数
>>>10 / 3
3
但是 python 3.x已经改进了整数除法运算,/得到浮点数,//才是整数
>>> 10 / 3
3.3333333333333335
>>> 10 // 3
3
要在python2.7中引用3.x的除法法则,导入__future__的division
>>>from __future__ import devision
>>>print 10/3
3.333333333335
当新版本的一个特性与旧版本不兼容时,该特性会在旧版本中添加到__future__中,以便旧的代码能在旧的版本中测试新特性。
安装第三方模块
python提供的模块管理工具
--easy_install
--pip(推荐,已内置到python2.7.9)
cmd:
pip install web.py
如果要查询第三方模块的名字 可以到python.org中搜索相关关键字 web.py
python面向对象编程
面向对象编程是一种程序设计范式,把程序看作不同对象的相互调用,对现实世界建立对象模型
基本思想 类和实例
最重要的思想:数据封装
定义类并创建实例
class Person(object):
pass
类名以大写开头,紧接着是object,表示该类是从哪个类中继承下来的。
xiaoming = Person()
xiaohong = Person()
创建实例的属性
如何让每个实例拥有各自不同的属性?由于python是动态语言,对每个实例,都可以直接给他们的属性赋值,例如:
xiaoming = Person()
xiaoming.name = 'Xiao Ming'
xiaoming.gender = 'Male'
xiaoming.birth = '1990-2-3'
给小红加上的属性不一定和小明相同:
xiaohong = Person()
xiaohong.name = 'Xiao Hong'
xiaohong.school = 'No. 1 High School'
xiaohong.grade = 2
实例的属性可以像普通变量一样进行操作:
xiaohong.grade = xiaohong.grade + 1
>>> class Person(object):
pass
>>> xiaoming = Person()
>>> xiaoming.name = 'Xiao Ming'
>>> xiaohong = Person()
>>> xiaohong.grade = 2
>>> print xiaoming.grade
Traceback (most recent call last):
File "<pyshell#371>", line 1, in <module>
print xiaoming.grade
AttributeError: 'Person' object has no attribute 'grade'
>>> print xiaoming.name
Xiao Ming
初始化实例的属性
在定义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以外的参数。
xiaoming = Person('Xiao Ming','Male','1990-2-3')
访问限制
python对属性权限的控制是通过属性名来实现的,如果一个属性是由__双下划线开头,该属性就无法被外部访问。
class Person(object):
def __init__(self,name):
self.name = name
self.title = 'Mr'
self.__job = 'Student'
p = Person('Bob')
print p.name
Bob
print p.__job
Error
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute '__job'
但是一个属性已__xxx__形式定义,那他又可以被外部访问了,以__xxx__定义的属性在python的类中被称为特殊属性,很多预定义的特殊属性可以使用,通常我们不要把普通的变量用属性__xxx__形式定义。
单下划线开头的属性_xxx虽然也可以被外部访问,但是,按照习惯,他们不该被外部访问。
创建类属性
实例属性每个实例各自拥有,相互独立,而类属性有且只有一份。
定义类属性可以在类中定义:
class Person(object):
address = 'Earth'
def __init__(self,name):
self.name = name
因为类属性是直接绑定在类上的,所以,访问类属性不需要创建实例,就可以直接访问。
print Person.address
Earth
对一个实例调用类属性也是可以的,所有实例都可以访问到它所属的类属性:
由于python是动态语言,雷属性也是可以动态添加和修改的
Person.address = 'China'
类属性和实例属性名字冲突怎么办
修改类属性会导致所有实例访问到的类属性全部都受影响,但是,如果在实例变量上修改该类属性会发生什么问题能?
会只修改该实例 的对应类属性,但是其他实例类属性不被修改
可见,当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问。
可见,千万不要在实例上修改类属性,它实际上并没有修改类属性,而是给实例绑定了一个实例属性。
定义实例方法
实例的方法就是类中定义的函数,他的第一个参数永远是self,指向调用该方法的实例本身,其他参数和一个普通函数是完全一样的:
class Person(object):
def __init(self,name):
self.__name = name
def get_name(self):
return self.__name
在实例的内部,可以访问所有实例属性,这样,如果外部需要访问私有属性,可以通过方法调用获得,这种数据封装形式除了能保护内部的数据一致性外,还可以简化外部调用的难度。
方法也是属性
我们在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()才是方法调用
因为方法也是属性,所以,它可以动态地添加到实例上,只是需要用type.MethodType()把一个函数变为一个方法:
import types
def fn_get_grade(self):
if self.score >= 80:
return 'A'
elif 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(gn_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中定义要更直观。
定义类方法
和属性相类似,方法也分实例方法和类方法
在class中定义的放不是实例方法,实例方法第一个参数self是实例本身。
要在class中定义类方法,要这么写:
class Person(object):
count = 0
@classmethod
def how_many(cls):
return cls.count
def __init__(self,name):
Person.count = Person.count + 1
print Person.how_many()
p1.Person('Bob')
print Person.how_many
通过标记一个@classMethod,该方法绑定到Person类上,而非实例。 类方法的第一个参数传入到类本身,通常将参数名命名为cls,上面的cls.count实际上相当于Person.count
因为是在类上调用,而非实例上调用,因此类方法无法获得任何系能力变量,只能获得类的引用。
类的继承
class Person(object):
def __init__(self,name,gender):
self.name = name
self.gender = gender
class Student(Person):
def __init__(self,name,gender,school,score):
super(Student,self).__init__(name,gender):
self.school = school
self.score = score
继承是is关系 如:Student is a Person
如果两个类是has关系,则用组合。如:Student has a Book
class Student(Person):
def __init__(self,bookName):
self.book = Book(bookName)
Book类应该作为Student的一个属性来出现。
Python继承的特点
总是从单个类中继承
不要忘记调用super().__init__
def __init__(self,args):
super(SubClass,self).__init__(args)
pass
一定要用 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允许从霍格父类继承,成为多重继承
多重继承的继承链就不是一棵树了
像这样,D 同时继承自 B 和 C,也就是 D 拥有了 A、B、C 的全部功能。多重继承通过 super()调用__init__()方法时,A 虽然被继承了两次,但__init__()只调用一次:
>>> d = D('d')
init A...
init C...
init B...
init D...
多重继承的目的是从两种继承树中分别选择并继承出子类,以便组合功能使用。
举个例子,Python的网络服务器有TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer,而服务器运行模式有 多进程ForkingMixin 和 多线程ThreadingMixin两种。
要创建多进程模式的 TCPServer:
class MyTCPServer(TCPServer, ForkingMixin)
pass
要创建多线程模式的 UDPServer:
class MyUDPServer(UDPServer, ThreadingMixin):
pass
如果没有多重继承,要实现上述所有可能的组合需要 4x2=8 个子类。
获取对象信息
isinstance()判断是否是某种类型的实例
type(s)
dir(s) 获取变量的所有属性
>>> type(123)
<type 'int'>
>>> s = Student('Bob', 'Male', 88)
>>> type(s)
<class '__main__.Student'>
>>> 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')
>>>s.name
特殊方法
>>>print lst
[1,2,3]
>>>print p
<__main__.Person object at 0x10da9e850>
python 如何把任意变量变成一个str?
因为任何数据类型实例都有一个特殊方法
__str__()
打印list或者Person实例 都是调用的__str__()方法
如果给Person类加上一个__str__()这个特殊方法
就可以按照自己的想法打印出Person的实例来
python特殊方法:
__str__(): 用于print
__len__(): 用于len
__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)
>>> 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 Person(object):
def __init__(self,name,gender):
self.name = name
self.gender = gender
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类实现了按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)
__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
数学运算
python提供对基本数据类型int、float可以做整数和浮点数的四则运算以及乘方等运算。
但是,四则运算不仅局限于int和float,还可以是有理数、矩阵等。
如果要让Rational进行+运算,需要正确实现__add__:
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 __str__(self):
return '%s/%s' % (self.p, self.q)
__repr__ = __str__
现在可以试试有理数加法:
>>> r1 = Rational(1, 3)
>>> r2 = Rational(1, 2)
>>> print r1 + r2
5/6
类型转换
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__()。
@property,可以将python定义的函数“当做”属性访问,从而提供更加友好访问方式
@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) 就会报错。
这种使用 get/set 方法来封装对一个属性的访问在许多面向对象编程的语言中都很常见。
但是写 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装饰后的副产品。
现在,就可以像使用属性一样设置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__也能节省内存。
__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中,函数也是对象,对象和函数的区别并不显著。