本文主要是基于慕课网课程《python进阶》学习,并做一些总结。该课程详细介绍Python强大的函数式编程和面向对象编程,掌握Python高级程序设计的方法。
1. 函数式编程
所谓函数式编程的定义,可参考知乎上的详细回答,戳这里。
Python的函数式编程主要有以下特点:
- 不是纯函数式编程:允许有变量
- 支持高阶函数:函数也可以作为变量传入
- 支持闭包:有了闭包就能返回函数
- 有限度地支持匿名函数
高阶函数的定义
- 变量可以指向函数
- 函数名其实就是指向函数的变量
- 一个函数可以接收另一个函数作为参数
- 能接收函数作为参数的函数就是高阶函数
e.g.
def add(x, y, f):
return f(x) + f(y)
>> add(-5, 9, abs)
map函数
Python内置的高阶函数,它接收一个函数 f 和一个 list
,并通过把函数 f 依次作用在 list
的每个元素上,得到一个新的 list
并返回。
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]
reduce函数
reduce()
函数也是Python内置的一个高阶函数。reduce()
函数接收的参数,一个函数 f,一个list
。函数 f 必须接收两个参数,reduce()
对list
的每个元素反复调用函数f,并返回最终结果值。reduce()
还可以接收第3个可选参数,作为计算的初始值。
def f(x, y):
return x + y
# 先计算头两个元素:f(1, 3),结果为4;
# 再把结果和第3个元素计算:f(4, 5),结果为9;
# 再把结果和第4个元素计算:f(9, 7),结果为16;
# 再把结果和第5个元素计算:f(16, 9),结果为25;
# 由于没有更多的元素了,计算结束,返回结果25。
reduce(f, [1, 3, 5, 7, 9])
>> 25
### filter函数
`filter()`函数是 Python 内置的另一个有用的高阶函数,`filter()`函数接收一个函数 f 和一个`list`,这个函数 f 的作用是对每个元素进行判断,返回 `True`或 `False`,`filter()`根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新`list`。
```python
def is_not_empty(s):
return s and len(s.strip()) > 0
filter(is_not_empty, ['test', None, '', 'str', ' ', 'END'])
>> ['test', 'str', 'END']
sorted函数
sorted()
也是一个高阶函数,它可以接收一个比较函数来实现自定义排序,比较函数的定义是,传入两个待比较的元素 x, y,如果 x 应该排在 y 的前面,返回 -1,如果 x 应该排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0。
def cmp_ignore_case(s1, s2):
if s1.upper() > s2.upper():
return 1
elif s1.upper() < s2.upper():
return -1
else:
return 0
print sorted(['bob', 'about', 'Zoo', 'Credit'], cmp_ignore_case)
>> ['about', 'bob', 'Credit', 'Zoo']
闭包
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()
>> 9 # 因为f1现在才计算i*i,但现在i的值已经变为3
匿名函数
关键字lambda
表示匿名函数,冒号前面表示参数。
>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]
匿名函数有个限制,就是只能有一个表达式,不写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
装饰器
- 无参数decorator
Python中decorator 本质上就是一个高阶函数,它接收一个函数作为参数,然后,返回一个新函数。
使用 decorator 用Python提供的 @ 语法,这样可以避免手动编写f = decorate(f)
这样的代码。
def log(f):
#*args代表一个元组,** kw代表一个dict,变量名是可以随意改变的,主要用于可变参数
def fn(*args, **kw):
print 'call ' + f.__name__ + '()...'
return f(*args, **kw)
return fn
@log
def add(x, y):
return x + y
print add(1, 2)
- 带参数decorator
如果有的函数非常重要,希望打印出'[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
有些时候,我们需要依赖函数名做一些逻辑。但是如上装饰器一番改动之后,函数名已经发生了修改。如果要让调用者看不出一个函数经过了@decorator的
“改造”,就需要把原函数的一些属性复制到新函数中。所以Python内置的functools可以用来自动化完成这个“复制”的任务:
def log(f):
@functools.wraps(f)
def wrapper(x):
print 'call...'
return f(x)
return wrapper
偏函数
偏函数可以理解为函数的可替代函数,但是更加简化。functools.partial
就是帮助我们创建一个偏函数,如下所示,可创建一个base为2的int
函数:
import functools
int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
2. 模块
一个python文件可以理解为一个模块;而一个文件夹可以理解为一个包,但是每一级文件夹下面都需要包含一个特殊的init.py文件。
导入模块
- 使用import关键字导入整个模块。
import math
- 如果希望导入math模块的某几个函数就行,那可以使用如下语法:
from math import pow,sin,log
- 如果同时导入的函数存在同名冲突时,例如上面语句同时导入了
logging
的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
- 支持动态导入:
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
安装第三方模块
使用pip
工具。模块名查询网站:https://pypi.org/
3. 面向对象编程
创建实例/类属性
Python是动态语音,对每个实例,都可以直接给他们的属性赋值:
xiaoming = Person()
xiaoming.name = 'Xiao Ming'
xiaoming.gender = 'Male'
xiaoming.birth = '1990-1-1'
类属性可以直接在class中定义:
class Person(object):
address = 'Earth'
def __init__(self, name):
self.name = name
Person.address = 'Earth'
初始化实例属性
使用特殊的__init__()
方法,第一个参数必须是self
(也可以为别的名字,但建议使用习惯用法)
class Person(object):
def __init__(self, name, gender, birth):
self.name = name
self.gender = gender
self.birth = birth
访问限制
私有属性通过双下划线开头(__)。但是,如果一个属性以__xxx__
的形式定义,那它又可以被外部访问了,以__xxx__
定义的属性在Python的类中被称为特殊属性,有很多预定义的特殊属性可以使用,通常我们不要把普通属性用__xxx__
定义。
定义实例/类方法
实例的方法就是在类中定义的函数,它的第一个参数永远是 self,指向调用该方法的实例本身,其他参数和一个普通函数是完全一样的:
class Person(object):
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
定义类方法,需要使用@classmethod
关键字:
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()
4. 类的继承
类的继承方式
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
一定要用super(Student, self).__init__(name, gender)
去初始化父类,否则,继承自 Person
的Student
将没有 name
和gender
。
多重继承
除了从一个父类继承外,Python允许从多个父类继承,称为多重继承。
class D(B, C):
def __init__(self, a):
super(D, self).__init__(a)
print 'init D...'
获取对象信息
-
isinstance()
判断它是否是某种类型的实例 -
type()
获取变量类型 -
dir()
函数获取变量的所有属性。如果已知一个属性名称,要获取或者设置对象的属性,就需要用 getattr() 和 setattr( )函数了
5. 定制类
_str_()和_repr_()
str()用于显示给用户,而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_()
自定义类的排序方式:
# 按照分数从高到底排序,分数相同的按名字排序。
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 not isinstance(s,Student):
return cmp(self.name,str(s))
if self.score == s.score:
return cmp(self.name,s.name)
else:
return -cmp(self.score,s.score)
L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
print sorted(L)
_len_()
如果类表现得像一个list,可以通过__len__()
自定义类的长度。
要让len()
函数工作正常,类必须提供一个特殊方法__len__()
,它返回元素的个数。
例如,我们写一个 Students
类,把名字传进去:
class Students(object):
def __init__(self, *args):
self.names = args
def __len__(self):
return len(self.names)
类型转换
如果要让自定义类支持有理数运算,转为int
或float
等类型:
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
def __int__(self):
return self.p // self.q
@property
可以将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是动态语言,任何实例在运行期都可以动态地添加属性。
如果要限制添加的属性,就可以利用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中,函数也是对象,对象和函数的区别并不显著。