函数式编程:一种编程范式
函数式编程特色:python
Python支持的函数式编程:编程
高阶函数:能接收函数作参数的函数vim
咱们讲了高阶函数的概念,编写一个简单的高阶函数:ruby
def add(x, y, f): return f(x) + f(y)
若是传入abs做为参数f的值:bash
add(-5, 9, abs)
根据函数的定义,函数执行的代码其实是:服务器
abs(-5) + abs(9)
因为参数 x, y 和 f 均可以任意传入,若是 f 传入其余函数,就能够获得不一样的返回值。网络
map()是 Python 内置的高阶函数,它接收一个函数 f 和一个 list,并经过把函数 f 依次做用在 list 的每一个元素上,获得一个新的 list 并返回。多线程
例如,对于list [1, 2, 3, 4, 5, 6, 7, 8, 9]闭包
若是但愿把list的每一个元素都做平方,就能够用map()函数:
所以,咱们只须要传入函数f(x)=x*x
,就能够利用map()函数完成这个计算:app
def f(x): return x*x print map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
输出结果:
`[1, 4, 9, 10, 25, 36, 49, 64, 81]
注意:map()函数不改变原有的 list,而是返回一个新的 list。
利用map()函数,能够把一个 list 转换为另外一个 list,只须要传入转换函数。
因为list包含的元素能够是任何类型,所以,map() 不只仅能够处理只包含数值的 list,事实上它能够处理包含任意类型的 list,只要传入的函数f能够处理这种数据类型。
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)
结果将变为125,由于第一轮计算是:
计算初始值和第一个元素:f(100, 1),结果为101。
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'
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]
sorted()也能够对字符串进行排序,字符串默认按照ASCII大小来比较:
>>> sorted(['bob', 'about', 'Zoo', 'Credit']) ['Credit', 'Zoo', 'about', 'bob']
'Zoo'排在'about'以前是由于'Z'的ASCII码比'a'小。
Python的函数不但能够返回int、str、list、dict等数据类型,还能够返回函数!
例如,定义一个函数 f(),咱们让它返回一个函数 g,能够这样写:
def f(): print 'call f()...' # 定义函数g: def g(): print 'call g()...' # 返回函数g: return g
仔细观察上面的函数定义,咱们在函数 f 内部又定义了一个函数 g。因为函数 g 也是一个对象,函数名 g 就是指向函数 g 的变量,因此,最外层函数 f 能够返回变量 g,也就是函数 g 自己
。
调用函数 f,咱们会获得 f 返回的一个函数:
>>> x = f() # 调用f() call f()... >>> x # 变量x是f()返回的函数: <function g at 0x1037bf320> >>> x() # x指向函数,所以能够调用 call g()... # 调用x()就是执行g()函数定义的代码
请注意区分返回函数和返回值:
def myabs(): return abs # 返回函数 def myabs2(x): return abs(x) # 返回函数调用的结果,返回值是一个数值
返回函数能够把一些计算延迟执行。例如,若是定义一个普通的求和函数:
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
因为能够返回函数,咱们在后续代码里就能够决定到底要不要调用该函数。
在函数内部定义的函数和外部定义的函数是同样的,只是他们没法被外部访问:
def g(): print 'g()...' def f(): print 'f()...' return g
将 g 的定义移入函数 f 内部,防止其余代码调用 g:
def f(): print 'f()...' def g(): print 'g()...' return g
可是,考察上一小节定义的 calc_sum 函数:
def calc_sum(lst): def lazy_sum(): return sum(lst) return lazy_sum
注意: 发现无法把 lazy_sum 移到 calc_sum 的外部,由于它引用了 calc_sum 的参数 lst。
像这种内层函数引用了外层函数的变量(参数也算变量),而后返回内层函数的状况,称为闭包(Closure)。
闭包的特色是返回的函数还引用了外层函数的局部变量,因此,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。举例以下:
# 但愿一次返回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。因为f一、f二、f3并无被调用,因此,此时他们并未计算 i*i,当 f1 被调用时:
>>> f1() 9 # 由于f1如今才计算i*i,但如今i的值已经变为3
所以,返回函数不要引用任何循环变量,或者后续会发生变化的变量。
正确写法:
def count(): fs = [] for i in range(1, 4): def f(j): def g(): return j*j return g r=f(i) fs.append(r) return fs f1, f2, f3 = count() print f1(), f2(), f3()
高阶函数能够接收函数作参数,有些时候,咱们不须要显式地定义函数,直接传入匿名函数更方便。
在Python中,对匿名函数提供了有限支持。仍是以map()函数为例,计算 f(x)=x2 时,除了定义一个f(x)的函数外,还能够直接传入匿名函数:
>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]) [1, 4, 9, 16, 25, 36, 49, 64, 81]
经过对比能够看出,匿名函数 lambda x: x * x 实际上就是:
def f(x): return x * x
关键字lambda 表示匿名函数,冒号前面的 x 表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不写return,返回值就是该表达式的结果。
使用匿名函数,能够没必要定义函数名,直接建立一个函数对象,不少时候能够简化代码:
>>> sorted([1, 3, 9, 5, 0], lambda x,y: -cmp(x,y)) [9, 5, 3, 1, 0] 返回函数的时候,也能够返回匿名函数: >>> myabs = lambda x: -x if x < 0 else x >>> myabs(-1) 1 >>> myabs(1) 1
认识装饰器:
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 都能正常工做。
考察上一节的 @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
把上面的定义翻译成高阶函数的调用,就是:
my_func = log('DEBUG')(my_func)
上面的语句看上去仍是比较绕,再展开一下:
log_decorator = log('DEBUG') my_func = log_decorator(my_func)
上面的语句又至关于:
log_decorator = log('DEBUG') @log_decorator def my_func(): pass
因此,带参数的log函数首先返回一个decorator函数,再让这个decorator函数接收my_func并返回新函数:
def log(prefix): def log_decorator(f): def wrapper(*args, **kw): print '[%s] %s()...' % (prefix, f.__name__) return f(*args, **kw) return wrapper return log_decorator @log('DEBUG') def test(): pass print test() #执行结果: [DEBUG] test()... None
对于这种3层嵌套的decorator定义,你能够先把它拆开:
# 标准decorator: def log_decorator(f): def wrapper(*args, **kw): print '[%s] %s()...' % (prefix, f.__name__) return f(*args, **kw) return wrapper return log_decorator # 返回decorator: def log(prefix): return log_decorator(f)
拆开之后会发现,调用会失败,由于在3层嵌套的decorator定义中,最内层的wrapper引用了最外层的参数prefix,因此,把一个闭包拆成普通的函数调用会比较困难。不支持闭包的编程语言要实现一样的功能就须要更多的代码。
@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
def f2(x):
pass
print 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)
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()函数能够把字符串转换为整数,当仅传入字符串时,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('1000000') 64 >>> int2('1010101') 85
functools.partial就是帮助咱们建立一个偏函数的,不须要咱们本身定义int2(),能够直接使用下面的代码建立一个新的函数int2:
>>> import functools >>> int2 = functools.partial(int, base=2) >>> int2('1000000') 64 >>> int2('1010101') 85
因此,functools.partial能够把一个参数多的函数变成一个参数少的新函数,少的参数须要在建立时指定默认值,这样,新函数调用的难度就下降了。
在文件系统中:
要使用一个模块,咱们必须首先导入该模块。Python使用import语句导入一个模块。例如,导入系统自带的模块 math:import math
你能够认为math就是一个指向已导入模块的变量,经过该变量,咱们能够访问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导入模块名,因为必须经过模块名引用函数名,所以不存在冲突:
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 都提供了StringIO这个功能。
这是由于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模块存在,则咱们将得到更快的运行速度,若是cStringIO不存在,则顶多代码运行速度会变慢,但不会影响代码的正常执行。
try 的做用是捕获错误,并在捕获到指定错误时执行 except 语句。
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中,以便旧的代码能在旧版本中测试新特性。
方法一:easy_install
方法二:pip
(推荐,已内置到Python2.7.9)
要想找到合适的第三方模块的名字能够去pypi.python.org搜索
什么是面向对象编程:
面向对象编程的基本思想:
类和实例:
类用于定义抽象类型
实例根据类的定义被建立出来
面向对象编程:数据封装
在Python中,类经过 class 关键字定义。以 Person 为例,定义一个Person类以下:
class Person(object): pass
按照 Python 的编程习惯,类名以大写字母开头,紧接着是(object),表示该类是从哪一个类继承下来的。类的继承将在后面的章节讲解,如今咱们只须要简单地从object类继承。
有了Person类的定义,就能够建立出具体的xiaoming、xiaohong等实例。建立实例使用 类名+(),相似函数调用的形式建立:
xiaoming = Person() xiaohong = Person()
虽然能够经过Person类建立出xiaoming、xiaohong等实例,可是这些实例看上除了地址不一样外,没有什么其余不一样。在现实世界中,区分xiaoming、xiaohong要依靠他们各自的名字、性别、生日等属性。
如何让每一个实例拥有各自不一样的属性?因为Python是动态语言,对每个实例,均可以直接给他们的属性赋值,例如,给xiaoming这个实例加上name、gender和birth属性:
xiaoming = Person()
xiaoming.name = 'Xiao Ming' xiaoming.gender = 'Male' xiaoming.birth = '1990-1-1'
给xiaohong加上的属性不必定要和xiaoming相同:
xiaohong = Person()
xiaohong.name = 'Xiao Hong' xiaohong.school = 'No. 1 High School' xiaohong.grade = 2
实例的属性能够像普通变量同样进行操做:
xiaohong.grade = xiaohong.grade + 1
虽然咱们能够自由地给一个实例绑定各类属性,可是,现实世界中,一种类型的实例应该拥有相同名字的属性。例如,Person类应该在建立的时候就拥有 name、gender 和 birth 属性,怎么办?
在定义 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', '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' p = Person('Bob') print p.name # => Bob print p._title # => Mr 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"定义。**
以单下划线开头的属性"_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): 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。init(self, name)其实也可看作是一个特殊的实例方法。
调用实例方法必须在实例上调用:
p1 = Person('Bob') print p1.get_name() # self不须要显式传入 # => Bob
在实例方法内部,能够访问全部实例属性,这样,若是外部须要访问私有属性,能够经过方法调用得到,这种数据封装的形式除了能保护内部数据一致性外,还能够简化外部调用的难度。
咱们在 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中定义要更直观。
和属性相似,方法也分实例方法和类方法。
在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。
由于是在类上调用,而非实例上调用,所以类方法没法得到任何实例变量,只能得到类的引用。
什么是继承:
继承的好处:
若是已经定义了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...'
像这样,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() 判断它是不是某种类型的实例外,还有没有别的方法获取到更多的信息呢?
例如,已有定义:
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
若是要把一个类的实例变成 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__
对 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 应该排在前面,就返回 -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 可能会报错:
若是一个类表现得像一个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类来表示:
class Rational(object): def __init__(self, p, q): self.p = p self.q = q
p、q 都是整数,表示有理数 p/q。
若是要让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()。
考察 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方法。
因为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也能节省内存。
在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中,函数也是对象,对象和函数的区别并不显著。