Python语言学习笔记(二)

Python语言学习笔记(二)

函数式编程

函数式编程(Functional Programming),是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数
Python对函数式编程提供部分支持。由于Python允许使用变量,因此Python不是纯函数式编程语言

高阶函数

一个函数接收另一个函数作为参数,这种函数就称之为高阶函数。

def add(x, y, f):
return f(x) + f(y)

 

1.map/reduce
Python内建了map()reduce()函数。
来看map函数的API:

map(function, sequence[, sequence, ...]) -> list

 

map()函数接收两个参数,一个是函数,一个是可迭代序列,map将传入的函数依次作用到序列的每个元素,并把结果作为list返回。(这里Python2.x可能是list,Python3.x可能是Iterator

>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]

 

来看reduce函数的API:

reduce(function, sequence[, initial]) -> value

 

reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数reduce把结果继续和序列的下一个元素做累积计算,其效果就是如下,最后得出一个结果。

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
 
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579

 

配合map(),我们就可以写出把str转换为int的函数:

>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> def char2num(s):
... return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
...
>>> reduce(fn, map(char2num, '13579'))
13579
 
#整理一下
def str2int(s):
def fn(x, y):
return x * 10 + y
def char2num(s):
return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
return reduce(fn, map(char2num, s))
 
#继续简化
def char2num(s):
return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
 
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))

 

2.filter
来看下filter函数的API(python2.x):

filter(function or None, sequence) -> list, tuple, or string

 

filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
把一个序列中的空字符串删掉,可以这么写:

def not_empty(s):
return s and s.strip()
 
list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
# 结果: ['A', 'B', 'C']

 

可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数

3.sorted
先看一下sorted函数的API:

sorted(iterable, cmp=None, key=None, reverse=False) --> new sorted list

 

它可以接收一个key函数来实现自定义的排序

>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
 
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
 
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

 

In general, the key and reverse conversion processes are much faster than specifying an equivalent cmp function. This is because cmpis called multiple times for each list element while key and reverse touch each element only once。
一般用key比较多,用cmp可能比较少。原因如上所述。
sorted()排序的关键在于实现一个映射函数。

返回函数

函数作为返回值

def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
 
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f()
25

 

闭包 Closure
简单的说,就像上面那个例子,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中。
这种内部函数可以使用外部函数变量的行为,就叫闭包(Closure)。这种程序结构拥有极大的威力。
闭包需要满足的条件:

  1. 必须有一个内嵌函数
  2. 内嵌函数必须引用外部函数中的变量
  3. 外部函数的返回值必须是内嵌函数

注意:
1.返回的函数并不会立刻执行,而是直到调用了才执行。所以,返回函数不要引用任何循环变量,或者后续会发生变化的变量。

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
>>> f2()
9
>>> f3()
9
#等到3个函数都返回了,它们所引用的变量i已经变成3了

 

2.闭包的最大特点是可以将父函数的变量与内部函数绑定,并返回绑定变量后的函数(也即闭包),此时即便生成闭包的环境(父函数)已经释放,闭包仍然存在

3.函数内的内部函数可以直接引用外部变量,但不能改写外部变量。否则会报错。
代码例子
python 3 中引入了 nonlocal 语句解决了这个问题
代码例子

参考:
python的闭包和装饰器

装饰器 Decorator

这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。事实上,装饰器就是一种的闭包的应用,只不过其传递的是函数。
比如我们要定义一个能打印日志的decorator:

def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
 
@log
def now():
print('2015-3-25')
 
#相当于执行 now = log(now)
>>> now()
call now():
2015-3-25

 

要点:
1.接受一个函数作为参数,并返回一个函数
2.借助Python的@语法,把decorator置于函数的定义处

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,此处变成3层嵌套。

def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
 
@log('execute')
def now():
print('2015-3-25')
 
#相当于执行 now = log('execute')(now)
>>> now()
execute now():
2015-3-25
 
#但是经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper',有些依赖函数签名的代码执行就会出错。我们需要做的,wrapper.__name__ = func.__name__,但是python有内置的模块 functools.wraps 就是干这个事的。
>>> now.__name__
'wrapper'
 
#所以,一个完整的decorator的写法如下:
import functools
 
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
 
# 或者针对带参数的decorator:
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator

 

参考:
python的闭包和装饰器
装饰器-廖雪峰
python装饰器与面向切面编程

匿名函数

举个例子:
匿名函数lambda x: x * x,实际对应的是:

def f(x):
return x * x

 

关键字lambda表示匿名函数,冒号前面的x表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
Python对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。

偏函数

当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
比如,functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64

 

模块

在Python中,一个.py文件就称之为一个模块(Module)
为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)
注意,每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是文件目录名。

加载模块时,默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中:

>>> import sys
>>> sys.path
['', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python34.zip', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/plat-darwin', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/site-packages']

 

如果我们要添加自己的搜索目录,有两种方法:
一是直接修改sys.path,添加要搜索的目录。
第二种方法是设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。

作用域

__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如__name__就是特殊变量。
类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该被直接引用。

面向对象编程

在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
面向对象最重要的概念就是类(Class)和实例(Instance)。
函数的第一个参数self指向创建的实例本身。

访问限制

如果要防止外部代码修改实例的内部属性,实例的变量名则以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

class Student(object):
 
def __init__(self, name, score):
self.__name = name
self.__score = score

 

Python本身没有任何机制阻止你干坏事,一切全靠自觉。

继承与多态

继承和多态的概念,就不讲了。
鸭子类型
Python作为动态语言,具有动态语言的“鸭子类型”特点。它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
比如下面不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了。也就是,你不一定要传入真正的要求的对象,完全可以传入任何实现了需要方法的对象。

def run_twice(animal):
animal.run()
animal.run()

 

Python的“file-like object“就是一种鸭子类型。只要写个read()方法就行。

获取对象的信息

1.使用type()
2.使用isinstance()
对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。
能用type()判断的基本类型也可以用isinstance()判断。
3.使用dir()
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list。

>>> dir('ABC')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

 

仅仅把属性和方法列出来是不够的,配合getattr()setattr()以及hasattr(),我们可以直接操作一个对象的状态。
要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。如果可以直接写那就直接写。

实例属性和类属性

实例属性就不说了。
类属性,归Student类所有。但类的所有实例都可以访问到。注意不要把实例属性和类属性使用相同的名字,否则会产生屏蔽。

class Student(object):
name = 'Student'
 
>>> print(Student.name) # 打印类的name属性
Student

 

posted @ 2017-07-26 19:53  天涯海角路  阅读(146)  评论(0)    收藏  举报