函数式编程

介绍

函数式编程特点:

  • 把计算视为函数而不是指令
  • 纯函数式编程:不需要变量,没有副作用,测试简单
  • 支持高阶函数

Python支持的函数式编程:

  • 不是纯函数式编程:允许有变量
  • 支持高阶函数:函数也可以作为变量传入
  • 支持闭包
  • 有限度的支持匿名函数

Python中的高阶函数

  • 变量可以指向函数

    1
    2
    f=abs
    f(-2) # 2
  • 函数名其实就是指向函数的变量

    1
    2
    abs=len
    abs([1,2]) # 2
  • 高阶函数:能接受函数作为参数的函数

    • 变量可以指向函数
    • 函数的参数可以接收变量
    • 一个函数可以接收另一个函数作为参数
      1
      2
      3
      def add(x,y,f):
      return f(x)+f(y)
      add(-1,1,abs)

map函数

接收一个函数f和一个list,并通过函数f依次作用在list每个元素上,得到一个新的list并返回

1
2
3
4
5
6
def f(x):
return x*x;
map(f,[1,2,3]) # 1,4,9
def format_name(s):
return s[0].upper() + s[1:].lower()
print map(format_name, ['adam', 'LiSA', 'barT'])

map函数不改变原有的list,而是返回一个新的list

使用map进行操作相比列表更加高效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time
BIG = 20000000
def f(k):
return 2*k
def benchmark(function, function_name):
start = time.time()
function()
end = time.time()
print("{0} seconds for {1}".format((end - start), function_name))
def list_a():
list_a = []
for i in range(BIG):
list_a.append(f(i))
def list_b():
list_b = [f(i) for i in range(BIG)]
def list_c():
list_c = map(f, range(BIG))
benchmark(list_a, "list a")
benchmark(list_b, "list b")
benchmark(list_c, "list c")

reduce函数

reduce函数接收的参数和map类似,一个函数f和一个lis,传入的函数f必须接收两个参数,reduce对list每个元素反复调用函数f,并返回最终的结果值。

1
2
3
4
5
from functools import reduce  # py3
def f(x,y):
return x+y

reduce(f,[1,2,3],100) # 106

reduce函数在python3的内建函数移除了,放入了functools模块

filter函数

filter函数接收一个函数f和一个list,函数f的作用是对每个元素进行判断,返回True或False,filter根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的list

1
2
3
4
5
import math
def is_sqr(x):
r = int(math.sqrt(x))
return r*r==x
print(list(filter(is_sqr, range(1, 101))))

自定义排序函数

python内置的sorted函数可对list进行排序

1
sorted([36, 5, 12, 9, 21])

sorted()也是一个高阶函数,它可以接收一个比较函数来实现自定义排序,比较函数的定义是,传入两个待比较的元素 x, y,如果 x 应该排在 y 的前面,返回 -1,如果 x 应该排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0。

1
2
3
4
5
6
7
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)

sorted()也可以对字符串进行排序,字符串默认按照ASCII大小来比较:

1
2
sorted(['bob', 'about', 'Zoo', 'Credit'])
# ['Credit', 'Zoo', 'about', 'bob']

闭包

内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包(Closure)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def calc_sum(lst):
def lazy_sum():
return sum(lst)
return lazy_sum

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 使用 lambda 来创建匿名函数。

1
2
3
4
5
6
twice=lambda x:2*x
print(twice(5))
print((lambda x: 2 * x)(5))

some_list = list(range(5))
only_evens = filter(lambda x: x%2 == 0, some_list)

冒号前面的x表示函数参数,只能有一个表达式,不写return,返回值就是该表达式的结果。

1
2
3
def is_not_empty(s):
return s and len(s.strip()) > 0
print filter(lambda s:s and len(s.strip())>0, ['test', None, '', 'str', ' ', 'END'])

lambda表达式同样比list更加高效

1
2
3
4
5
6
7
8
9
10
11
def list_a():
list_a = []
for i in range(BIG):
list_a.append(f(i))
def list_b():
list_b = [f(i) for i in range(BIG)]
def list_c():
list_c = map(f, range(BIG))
def list_d():
# list_d=map(lambda x:f(x),range(BIG))
list_d=map(lambda x:2*x,range(BIG))

decorator装饰器

定义一个函数,想在运行时动态增加功能而不修改代码

1
2
3
4
5
6
7
8
9
10
def f1(x):
return x*2
def new_fn(f):
def fn(x):
print( 'call '+f.__name__+'()')
return f(x)
return fn
# 调用
g=new_fn(f1):
g(5)

Python内置的@语法就是为了简化装饰器的调用

1
2
3
4
5
6
7
8
9
10
def new_fn(f):
def fn(x):
print( 'call '+f.__name__+'()')
return f(x)
return fn
@new_fn
def f1(x):
return x*2
# 调用
f1(5)

装饰器的作用:

  • 简化代码,避免每个函数编写重复性代码
    • 打印日志:@log
    • 检测性能:@pergormance
    • 数据库事务:@transaction
    • URL路由:@post(‘/register’)

无参数decorator
Python的decorator本质上是一个高阶函数,就收一个函数作为参数,然后返回一个新函数。使用decorator用Python提供的@
语法,这样可以避免手动编写f=decorate(f)这样的代码。
对于阶乘函数,以下@log工作的很好

1
2
3
4
5
6
7
8
9
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)

但是,对于参数不是一个的函数,调用将报错

1
2
3
4
5
6
7
8
9
10
@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,保证任意个数的参数总是能正常调用:
比如检测一个程序运行时间

1
2
3
4
5
6
7
8
9
10
11
12
13
def fn_timer(function):
@wraps(function)
def function_timer(*args,**kwargs):
s=time.time()
result=function(*args,**kwargs)
e=time.time()
print ("Total time running %s: %s" % (function.__name__, e - s))
return result
return function_timer
@fn_timer
def list_c():
list_c = map(lambda x:2*x, range(BIG))
list_c()

带参数decorator
对于被装饰的函数,前面提到的log打印的语句是不能变的(除了函数名)。如果希望打印出’[INFO] call xxx()…’,这时,log函数本身就需要传入’INFO’这样的参数,类似这样:

1
2
3
@log('INFO')
def my_func():
pass

上面的定义等价于以下高阶函数的调用:

1
2
3
4
5
6
7
8
9
my_func=log('INFO')(my_func)
# 也就是
log_decorator = log('INFO')
my_func = log_decorator(my_func)
# 有相当于
log_decorator = log('INFO')
@log_decorator
def my_func():
pass

所以,带参数的log函数首先返回一个decorator函数,再让这个decorator函数接收my_func并返回新函数:

1
2
3
4
5
6
7
8
9
10
11
12
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()

完善decorator
在没有decorator的情况下,打印函数名:

1
2
3
4
def f1(x):
pass
print f1.__name__
# 输出 f1

有decorator的情况下,再打印函数名:

1
2
3
4
5
6
7
8
9
10
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的“改造”,就需要把原函数的一些属性复制到新函数中:

1
2
3
4
5
6
7
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可以用来自动化完成这个“复制”的任务:

1
2
3
4
5
6
7
import functools
def log(f):
@functools.wraps(f)
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper

由于我们把原函数签名改成了(*args, **kw),因此,无法获得原函数的原始参数信息。即便我们采用固定参数来装饰只有一个参数的函数:

1
2
3
4
5
6
def log(f):
@functools.wraps(f)
def wrapper(x):
print 'call...'
return f(x)
return wrapper

也可能改变原函数的参数名,因为新函数的参数名始终是 ‘x’,原函数定义的参数名不一定叫 ‘x’。

偏函数

当一个函数有很多参数时,调用者就需要提供多个参数。如果减少参数个数,就可以简化调用者的负担。
比如,int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:

1
2
>>> int('12345')
12345

但int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做 N 进制的转换:

1
2
3
4
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565

假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:

1
2
3
4
def int2(x, base=2):
return int(x, base)
int2('1000000')
# 64

functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:

1
2
3
4
import functools
int2 = functools.partial(int, base=2)
int2('1000000')
# 64

所以,functools.partial可以把一个参数多的函数变成一个参数少的新函数,少的参数需要在创建时指定默认值。

1
2
3
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'])

模块和包

区分包和普通目录:
包下面有个init.py,且每层都必须有

动态导入模块

有的时候,两个不同的模块提供了相同的功能,比如 StringIO 和 cStringIO 都提供了StringIO这个功能。
这是因为Python是动态语言,解释执行,因此Python代码运行速度慢。
如果要提高Python代码的运行速度,最简单的方法是把某些关键函数用 C 语言重写,这样就能大大提高执行速度。
同样的功能,StringIO 是纯Python代码编写的,而 cStringIO 部分函数是 C 写的,因此 cStringIO 运行速度更快。
利用ImportError错误,我们经常在Python中动态导入模块:

1
2
3
4
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO

Python的新版本会引入新的功能,但是,实际上这些功能在上一个老版本中就已经存在了。要“试用”某一新的特性,就可以通过导入future模块的某些功能来实现。
例如,Python 2.7的整数除法运算结果仍是整数,但是,Python 3.x已经改进了整数的除法运算,“/”除将得到浮点数,“//”除才仍是整数,要在Python 2.7中引入3.x的除法规则,导入future的division

1
2
3
4
5
6
7
8
9
10
11
12
# py2
>>> 10 / 3
3
# py3
>>> 10 / 3
3.3333333333333335
>>> 10 // 3
3
# py2
>>> from __future__ import division
>>> print 10 / 3
3.3333333333333335

当新版本的一个特性与旧版本不兼容时,该特性将会在旧版本中添加到future中,以便旧的代码能在旧版本中测试新特性。
在Python 3.x中,字符串统一为unicode,不需要加前缀 u,而以字节存储的str则必须加前缀 b。请利用future的unicode_literals在Python 2.7中编写unicode字符串。

1
2
3
from __future__ import unicode_literals
s = 'am I an unicode?'
print isinstance(s, unicode)

面向对象

在定义 Person 类时,可以为Person类添加一个特殊的__init__()方法,当创建实例时,__init__()方法被自动调用,我们就能在此为每个实例都统一加上以下属性:

1
2
3
4
5
class Person(object):
def __init__(self, name, gender, birth):
self.name = name
self.gender = gender
self.birth = birth

__init__() 方法的第一个参数必须是 self(也可以用别的名字,但建议使用习惯用法),后续参数则可以自由指定,和定义函数没有任何区别。

1
2
3
4
5
6
7
8
9
10
class Person(object):
def __init__(self, name, gender, birth, **kw):
self.name = name
self.gender = gender
self.birth = birth
for k, v in kw.iteritems():
setattr(self, k, v)
xiaoming = Person('Xiao Ming', 'Male', '1990-1-1', job='Student')
print xiaoming.name
print xiaoming.job

访问限制

Python对属性权限的控制是通过属性名来实现的,如果一个属性由双下划线开头(__),该属性就无法被外部访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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'

但是,如果一个属性以"__xxx__"的形式定义,那它又可以被外部访问了,以"__xxx__"定义的属性在Python的类中被称为特殊属性,有很多预定义的特殊属性可以使用,通常我们不要把普通属性用"__xxx__"定义。

创建类属性

类是模板,而实例则是根据类创建的对象,实例属性每个实例各自拥有,互相独立,而类属性有且只有一份。

1
2
3
4
5
# 定义类属性可以直接在 class 中定义
class Person(object):
address = 'Earth'
def __init__(self, name):
self.name = name

因为类属性是直接绑定在类上,所以,访问类属性不需要创建实例,就可以直接访问

1
print(Person.address)

对一个实例调用类的属性也是可以访问的,所有实例都可以访问到它所属的类的属性:

1
2
3
p1 = Person('Bob')
print p1.address
# => Earth

由于Python是动态语言,类属性也是可以动态添加和修改的:

1
2
Person.address = 'China'
print(p1.address)

因为类属性只有一份,所以,当Person类的address改变时,所有实例访问到的类属性都改变了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

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’了:

1
2
3
del p1.address
print(p1.address)
# => Earth

可见,千万不要在实例上修改类属性,它实际上并没有修改类属性,而是给实例绑定了一个实例属性。

定义实例方法

实例的方法就是在类中定义的函数,它的第一个参数永远是 self,指向调用该方法的实例本身,其他参数和一个普通函数是完全一样的:

1
2
3
4
5
class Person(object):
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name

get_name(self) 就是一个实例方法,它的第一个参数是self。init(self, name)其实也可看做是一个特殊的实例方法。

1
2
3
p1 = Person('Bob')
print p1.get_name() # self不需要显式传入
# => Bob

在实例方法内部,可以访问所有实例属性,这样,如果外部需要访问私有属性,可以通过方法调用获得,这种数据封装的形式除了能保护内部数据一致性外,还可以简化外部调用的难度。
Python中的方法也是属性,在class中定义的实例方法其实也是属性,实际上是一个函数对象:

1
2
3
4
5
6
7
8
9
10
11
12
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() 把一个函数变为一个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

定义类方法

1
2
3
4
5
6
7
8
9
10
11
class Person(object):
count=0
@classmethod
def how_many(cls):
return cls.count
def __init__(self,name):
self.name=name
Person.count+=1
print(Person.how_many())
p1 = Person('Bob')
print(Person.how_many())

通过标记一个 @classmethod,该方法将绑定到 Person 类上,而非类的实例。类方法的第一个参数将传入类本身,通常将参数名命名为 cls,上面的 cls.count 实际上相当于 Person.count。
因为是在类上调用,而非实例上调用,因此类方法无法获得任何实例变量,只能获得类的引用。

继承

子类和父类是is关系

1
2
class Student(Person):
pass

has关系应该使用组合而非继承

1
2
3
4
class Student(Person):
def __init__(self,bookName):
self.book=Book(bookName)
# Book类作为Student的属性

总是从某个类继承,如果没有合适的类,要从object继承

1
2
class MyClass(object):
pass

不要忘记调用super().__init__方法,用来初始化父类

1
2
3
def __init__(self,args):
super(SubClass,self).__init__(args)
pass

如果已经定义了Person类,需要定义新的Student和Teacher类时,可以直接从Person类继承:

1
2
3
4
5
6
7
8
9
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,也可以用在我们自定义的类,它们本质上都是数据类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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')

>>> isinstance(p, Person)
True # p是Person类型
>>> isinstance(p, Student)
False # p不是Student类型
>>> isinstance(p, Teacher)
False # p不是Teacher类型
>>> isinstance(s, Person)
True # s是Person类型
>>> isinstance(s, Student)
True # s是Student类型
>>> isinstance(s, Teacher)
False # s不是Teacher类型

多态

类具有继承关系,并且子类类型可以向上转型看做父类类型,如果我们从 Person 派生出 Student和Teacher ,并都写了一个 whoAmI() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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,都可以正确打印出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()的方法即可:

1
2
3
class Book(object):
def whoAmI(self):
return 'I am a book'

这是动态语言和静态语言(例如Java)最大的差别之一。动态语言调用实例方法,不检查类型,只要方法存在,参数正确,就可以调用。
Python提供了open()函数来打开一个磁盘文件,并返回 File 对象。File对象有一个read()方法可以读取文件内容:
例如,从文件读取内容并解析为JSON结果:

1
2
3
import json
f = open('/path/to/file.json', 'r')
print(json.load(f))

由于Python的动态特性,json.load()并不一定要从一个File对象读取内容。任何对象,只要有read()方法,就称为File-like Object,都可以传给json.load()。
编写一个File-like Object,把一个字符串 r’[“Tim”, “Bob”, “Alice”]’包装成 File-like Object 并由 json.load() 解析。

1
2
3
4
5
6
import json
class Students(object):
def read(self):
return r'["Tim", "Bob", "Alice"]'
s = Students()
print(json.load(s))

多重继承

除了从一个父类继承外,Python允许从多个父类继承,称为多重继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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__()只调用一次:

1
2
3
4
5
>>> d = D('d')
init A...
init C...
init B...
init D...

多重继承的目的是从两种继承树中分别选择并继承出子类,以便组合功能使用。
比如Python的网络服务器有TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer,而服务器运行模式有 多进程ForkingMixin 和 多线程ThreadingMixin两种。
要创建多进程模式的 TCPServer:

1
2
class MyTCPServer(TCPServer, ForkingMixin)
pass

要创建多线程模式的 UDPServer:

1
2
class MyUDPServer(UDPServer, ThreadingMixin):
pass

获取对象信息

对于一个变量,除了用 isinstance() 判断它是否是某种类型的实例外,还有没有别的方法获取到更多的信息呢?
首先可以用 type() 函数获取变量的类型,它返回一个 Type 对象:

1
2
3
4
5
type(123)
<type 'int'>
s = Student('Bob', 'Male', 88)
type(s)
<class '__main__.Student'>

其次,可以用 dir() 函数获取变量的所有属性:

1
2
3
4
5
>>> 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 的一个属性。
dir()返回的属性是字符串列表,如果已知一个属性名称,要获取或者设置对象的属性,就需要用 getattr() 和 setattr( )函数了:

1
2
3
4
>>> getattr(s, 'name')  # 获取name属性
'Bob'

>>> setattr(s, 'name', 'Adam') # 设置新的name属性

定制类

特殊方法

  • 用于print的__str__
  • 用于len的__len__
  • 用于cmp的__cmp__

特点:

  • 特殊方法定义在class中
  • 不需要直接调用
  • Python中的某些函数或操作符会调用对应的特殊方法

__str____repr__

如果要把一个类的实例变成 str,就需要实现特殊方法__str__()

1
2
3
4
5
6
7
8
9
10
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)

__cmp__

对 int、str 等内置数据类型排序时,Python的 sorted() 按照默认的比较函数 cmp 排序,但是,如果对一组 Student 类的实例排序时,就必须提供我们自己的特殊方法 __cmp__()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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类实现了按name进行排序:

1
2
3
>>> L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)]
>>> print sorted(L)
[(Alice: 77), (Bob: 88), (Tim: 99)]

__len__

如果一个类表现得像一个list,要获取有多少个元素,就得用 len() 函数。
要让 len() 函数工作正常,类必须提供一个特殊方法len(),它返回元素的个数。

1
2
3
4
5
6
7
8
class Students(object):
def __init__(self, *args):
self.names = args
def __len__(self):
return len(self.names)
>>> ss = Students('Bob', 'Alice', 'Tim')
>>> print(len(ss))
3

数学运算

Python 提供的基本数据类型 int、float 可以做整数和浮点的四则运算以及乘方等运算。
但是,四则运算不局限于int和float,还可以是有理数、矩阵等。
要表示有理数,可以用一个Rational类来表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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)
# py2
# def __div__(self, r):
# return Rational(self.p * r.q, self.q * r.p)
def __truediv__(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)

类型转换

如果要把 Rational 转为 int,只需要实现特殊方法__int__():

1
2
3
4
5
6
7
8
9
10
11
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

@property

1
2
3
4
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score

当我们想要修改一个 Student 的 scroe 属性时,可以这么写:

1
2
3
s = Student('Bob', 59)
s.score = 60
s.score = 1000 # 直接给属性赋值无法检查分数的有效性

1
2
3
4
5
6
7
8
9
10
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

这种使用 get/set 方法来封装对一个属性的访问在许多面向对象编程的语言中都很常见。
但是写 s.get_score() 和 s.set_score() 没有直接写 s.score 来得直接。
因为Python支持高阶函数,在函数式编程中我们介绍了装饰器函数,可以用装饰器函数把 get/set 方法“装饰”成属性调用:

1
2
3
4
5
6
7
8
9
10
11
12
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装饰后的副产品。

1
2
3
4
5
6
7
8
>>> s = Student('Bob', 59)
>>> s.score = 60
>>> print s.score
60
>>> s.score = 1000
Traceback (most recent call last):
...
ValueError: invalid score

__slots__

由于Python是动态语言,任何实例在运行期都可以动态地添加属性。如果要限制添加的属性,例如,Student类只允许添加 name、gender和score 这3个属性,就可以利用Python的一个特殊的__slots__来实现。顾名思义,__slots__是指一个类允许的属性列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
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__也能节省内存。