函数式编程
#面向过程的程序设计:
#把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务。(按步骤执行)
#函数就是面向过程的程序设计的基本单元。
#函数式编程:
#允许把函数本身作为参数传入另一个函数,还允许返回一个函数
#相当于使用特定工具完成任务,知道输入输出,不关心内部。
#Python对函数式编程提供部分支持
#高阶函数
#函数本身也可以赋值给变量,即:变量可以指向函数。直接调用abs()函数和调用变量f()完全相同。
#函数名其实就是指向函数的变量! 自定义变量名不能使用内置函数名
#一个函数可以接收另一个函数作为参数,这种函数就称之为高阶函数。函数式编程就是指这种高度抽象的编程范式。
def add(x, y, f):
return f(x) + f(y)
print(add(-5, 6, abs))
1.map
#map函数
#map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
def f(x):
return x * x
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
#由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
2.reduce
#reduce函数
#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
#定义函数:将字符串转换为数字
def char2num(s):
digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
return digits[s]
#组合使用 map 和 reduce
reduce(fn, map(char2num, '13579'))
13579
#整理成一个str2int的函数就是:
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def str2int(s):
def fn(x, y):
return x * 10 + y
def char2num(s):
return DIGITS[s]
return reduce(fn, map(char2num, s))
#还可以用lambda函数进一步简化成:
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2num(s):
return DIGITS[s]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
#练习:利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字
def normalize(name):
return name[0].upper() + name[1:].lower()
#编写一个prod()函数,可以接受一个list并利用reduce()求积
from functools import reduce
def prod(L):
return reduce(lambda x, y: x * y, L)
#练习:利用 map 和 reduce 编写一个 str2float 函数,把字符串 '123.456' 转换成浮点数 123.456
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2num(s):
"将字符数字转换为整数"
return DIGITS[s]
def str2float(s):
# 按小数点分割字符串
parts = s.split('.')
integer_part = parts[0] # 整数部分,如 '123'
decimal_part = parts[1] if len(parts) > 1 else '' # 小数部分,如 '456' 若没有小数则设为空
# 将整数部分转换为整数:使用 reduce 和 map
integer_value = reduce(lambda x, y: x * 10 + y, map(char2num, integer_part))
# 将小数部分转换为小数:判断有小数,先转为整数,再除以10的相应次方
if decimal_part:
decimal_value = reduce(lambda x, y: x * 10 + y, map(char2num, decimal_part))
decimal_value = decimal_value / (10 ** len(decimal_part))
return integer_value + decimal_value
else:
return float(integer_value)
#map用于将一个函数作用于一个序列,以此得到另一个序列;
#reduce用于将一个函数依次作用于上次计算的结果和序列的下一个元素,以此得到最终结果。
3.filter
#filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
def not_empty(s):
return s and s.strip()
#strip() 是 Python 字符串对象的一个方法,用来删除字符串开头和结尾的指定字符:s.strip(chars=None)
#如果不给 chars 参数,默认删除所有空白字符:空格、制表符 \t、换行 \n 等
list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))
#注意filter()函数返回的是一个Iterator,惰性序列,所以要强迫filter()完成计算结果,需用list()函数获得所有结果并返回list。
# 结果: ['A', 'B', 'C']
#回数是指从左向右读和从右向左读都是一样的数,例如12321,909。请利用filter()筛选出回数:
def is_palindrome(n):
n=str(n)
m=0
while m<len(n)-1:
if n[m]!=n[-m-1]:
return False #注意要有返回值
m+=1
return True #首字母大写
output = filter(is_palindrome, range(1, 1000))
print('1~1000:', list(output))
if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]:
print('测试成功!')
else:
print('测试失败!')
#或者更简洁的方法
def is_palindrome(n):
s = str(n)
return s == s[::-1]
#切片语法 [::-1] 表示“从头到尾,步长为 -1”,即把字符串倒过来。
"""
4.sorted
#排序的核心是比较两个元素的大小,不仅有数字,还有字符串,dict等。可以用soeted()函数实现
sorted([36, 5, -12, 9, -21])
#此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序
sorted([36, 5, -12, 9, -21], key=abs)
#key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。
#默认情况下,对字符串排序,是按照ASCII的大小比较的,区分大小写字母。
#如果要忽略大小写排序,按照字母序排序,只要先全部变为大写或小写。
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
#要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
return t[0] #sorted 的 key 函数会依次接收列表里每个元素。此处t[0]表示元组的第一项
L2 = sorted(L, key=by_name)
print(L2)
def by_score(t):
return t[1]
L3 = sorted(L, key=by_score)
print(L3)
#返回函数&闭包
#高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
#即不需要立即计算,当调用函数f时,才真正计算结果
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 #调用函数f时,才真正计算求和的结果
#注意:当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数
"""
#闭包
"""
#我们在函数lazy_sum中又定义了函数sum,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,
#当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种程序结构称为“闭包(Closure)”
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
#每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。但实际结果都是9
#原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。
#返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
#如果一定要引用循环变量怎么办?再创建一个函数,用该函数的参数绑定循环变量当前值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs
#nonlocal
#使用闭包,就是内层函数引用了外层函数的局部变量。
#如果只是读外层变量的值,我们会发现返回的闭包函数调用一切正常
#但是,如果对外层变量赋值,由于Python解释器会把x当作函数fn()的局部变量,它会报错
#需要在fn()函数内部加一个nonlocal x的声明(说明要修改的x就是外面的x)。
def inc():
x = 0
def fn():
nonlocal x
x = x + 1
return x
return fn
f = inc()
print(f()) # 1
print(f()) # 2
#使用闭包时,对外层变量赋值前,需要先使用nonlocal声明该变量不是当前函数的局部变量。
"""
def createCounter():
x = 0 # 1. 在外层定义一个变量,初始值为 0
def counter():
nonlocal x # 2. 声明:我要修改外层那个 x,不要当成局部变量
x = x + 1 # 3. 每次调用自增 1
return x # 4. 返回最新的值
return counter
# 测试:
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
print('测试通过!')
else:
print('测试失败!')
#匿名函数
#关键字lambda表示匿名函数,冒号前面的x表示函数参数。
#匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
#匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数
f = lambda x: x * x
#也可以把匿名函数作为返回值返回
def build(x, y):
return lambda: x * x + y * y
is_odd = lambda x : x % 2 == 1
L = list(filter(is_odd, range(1, 20)))
print(L)
#装饰器
#函数对象有一个__name__属性(注意:是前后各两个下划线),可以拿到函数的名字
#假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
#本质上,decorator就是一个返回函数的高阶函数。
#定义一个能打印日志的decorator,可以定义如下:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
#接受一个函数作为参数,并返回一个函数。
#要借助Python的@语法,把decorator置于函数的定义处:
@log
def now():
print('2024-6-1')
#其实就是利用闭包,在不修改原函数代码的情况下,给原函数增加新功能。
#如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
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('2024-6-1')
#因为函数也是对象,去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper'
#不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
#或者针对带参数的decorator:
import functools
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
#在面向对象(OOP)的设计模式中,decorator被称为装饰模式。
#OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。
#练习
import time, functools
def metric(fn):
@functools.wraps(fn) # 养成好习惯,加上这行
def wrapper(*args, **kw):
start = time.time() # 1. 记录开始时间
result = fn(*args, **kw) # 2. 执行原函数,并拿到结果
end = time.time() # 3. 记录结束时间
# 计算耗时(秒变毫秒要 * 1000)
duration = (end - start) * 1000
print('%s executed in %.2f ms' % (fn.__name__, duration))
return result # 4. 千万别忘了把原函数的结果返回回去!
return wrapper
# 测试
@metric
def fast(x, y):
time.sleep(0.0012)
return x + y;
@metric
def slow(x, y, z):
time.sleep(0.1234)
return x * y * z;
f = fast(11, 22)
s = slow(11, 22, 33)
if f != 33:
print('测试失败!')
elif s != 7986:
print('测试失败!')
import functools
def log(obj):
# 【情况 1】如果传入的是字符串(说明是 @log('text') 这种用法)
if isinstance(obj, str):
text = obj
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s begin call' % text)
result = func(*args, **kw)
print('%s end call' % text)
return result
return wrapper
return decorator
# 【情况 2】如果传入的是函数(说明是 @log 这种直接用法)
else:
func = obj
@functools.wraps(func)
def wrapper(*args, **kw):
print('begin call')
result = func(*args, **kw)
print('end call')
return result
return wrapper
# --- 测试代码 ---
# 测试 1: 不带参数
@log
def f1():
print('Function 1 running...')
f1()
print('----------------')
# 测试 2: 带参数
@log('execute')
def f2():
print('Function 2 running...')
f2()
#偏函数
#Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。
#int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:
int('12345')
#如果传入base参数,就可以做N进制的转换:
#每次都传入int(x, base=2)非常麻烦,于是,想到可以定义一个int2()的函数,默认把base=2传进去
def int2(x, base=2):
return int(x, base)
#functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2
import functools
int2 = functools.partial(int, base=2)
int2('1000000')
#简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
模块
为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里。在Python中,一个.py文件就称之为一个模块(Module)。
为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。
选择一个顶层包名,比如mycompany,按照如下目录存放:
mycompany
├─ __init__.py
├─ abc.py
└─ xyz.py
引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突
abc.py模块的名字就变成了mycompany.abc,类似的,xyz.py的模块名变成了mycompany.xyz。
注意每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。
__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany。
自己创建模块时要注意命名,不能和Python自带的模块名称冲突。
模块是一组Python代码的集合,可以使用其他模块,也可以被其他模块使用。
创建自己的模块时,要注意:
模块名要遵循Python变量命名规范,不要使用中文、特殊字符;
模块名不要和系统模块名冲突,最好先查看系统是否已存在该模块,检查方法是在Python交互环境执行import abc,若成功则说明系统存在此模块。
#使用模块
#!/usr/bin/env python3 第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行
# -*- coding: utf-8 -*- 第2行注释表示.py文件本身使用标准UTF-8编码
' a test module ' #是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释
__author__ = 'Michael Liao' #使用__author__变量把作者写进去
import sys #导入sys模块后,我们就有了变量sys指向该模块,利用sys这个变量,就可以访问sys模块的所有功能。
def test():
args = sys.argv #sys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')
if __name__=='__main__': #
test()
#在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败
#只有当我被直接运行的时候,才执行下面的代码;如果我是被别人导入(Import)当作工具用的,那就别执行。(方便测试又不影响调用)
#导入时,没有打印Hello, word!,因为没有执行test()函数。调用hello.test()时,才能打印出Hello, word!
#作用域
#有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。
#正常的函数和变量名是公开的(public),可以被直接引用
#类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如__author__,__name__,自己的变量一般不要用这种变量名
#类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc
#这也是一种非常有用的代码封装和抽象的方法,即:
#外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。
#安装第三方模块
#pip anaconda
#当我们试图加载一个模块时,Python会在指定的路径下搜索对应的.py文件,如果找不到,就会报错,默认搜索路径存放在sys模块的path变量中
#如果我们要添加自己的搜索目录,有两种方法:
#一是直接修改sys.path,添加要搜索的目录:这种方法是在运行时修改,运行结束后失效
import sys
sys.path.append('/Users/michael/my_py_scripts')
#第二种方法是设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。注意只需要添加我们自己的搜索路径,Python本身的搜索路径不受影响。

被折叠的 条评论
为什么被折叠?



