python学习笔记---函数式编程【廖雪峰】

文章介绍了Python中的函数式编程特性,包括高阶函数如map(),reduce(),filter()以及sorted()的使用,强调了函数作为参数和返回值的概念。同时,讲解了闭包、非局部变量以及匿名函数lambda的运用。最后,重点讨论了装饰器的实现和应用场景,展示了如何通过装饰器在不修改原函数代码的情况下添加额外功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数式编程

函数就是面向过程的程序设计的基本单元。

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

高阶函数

结论:函数本身也可以赋值给变量,即:变量可以指向函数。

如果一个变量指向了一个函数,那么可以通过该变量来调用这个函数:

>>> f = abs
>>> f(-10)
10

函数名也是变量

那么函数名是什么呢?函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!


既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数

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

map()

map()函数接收两个参数,一个是函数,一个是Iterablemap传入的函数依次作用到序列的每个元素,并把结果作为新的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]

reduce()

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

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

比方说对一个序列求和,就可以用reduce实现:

>>> from functools import reduce
>>> def add(x, y):
...     return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25

filter()

filter()函数用于过滤序列

map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。----既实现过滤的功能

把一个序列中的空字符串删掉,可以这么写:

def not_empty(s):
    return s and s.strip()

list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))
# 结果: ['A', 'B', 'C']

注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。

sorted()

排序算法

​ 排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个dict呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。

①数字排序

Python内置的sorted()函数就可以对list进行排序:

>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]

此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:

>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

②字符串排序

>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']

默认情况下,对字符串排序,是按照ASCII的大小比较的,由于'Z' < 'a',结果,大写字母Z会排在小写字母a的前面。

现在,我们提出排序应该忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码大加改动,只要我们能用一个key函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。

这样,我们给sorted传入key函数,即可实现忽略大小写的排序:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']

进行反向排序,不必改动key函数,可以传入第三个参数==reverse=True==:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

从上述例子可以看出,高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。

返回函数

函数作为返回值

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回

如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:

>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>

调用函数f时,才真正计算求和的结果:

>>> f()
25

lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为==“闭包(Closure)”==的程序结构拥有极大的威力。

闭包

​ 注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。

返回函数的实际执行时间

另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。

>>> f1()
9
>>> f2()
9
>>> f3()
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()的局部变量,它会报错:

def inc():
    x = 0
    def fn():
        # nonlocal x
        x = x + 1
        return x
    return fn

f = inc()
print(f()) # 1
print(f()) # 2

​ 原因是==x作为局部变量并没有初始化==,直接计算x+1是不行的。但我们其实是想引用inc()函数内部的x,所以需要在fn()函数内部加一个nonlocal x的声明。加上这个声明后,解释器把fn()x看作外层函数的局部变量,它已经被初始化了,可以正确计算x+1

使用闭包时,对外层变量赋值前,需要先使用nonlocal声明该变量不是当前函数的局部变量

匿名函数lambda

计算f(x)=x2

>>> list(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表示函数参数

匿名函数有个限制,就是只能有一个表达式,不用写return返回值就是该表达式的结果

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25

同样,也可以把匿名函数作为返回值返回,比如:

def build(x, y):
    return lambda: x * x + y * y

装饰器decorator

来源:https://www.bilibili.com/video/BV1Vv411x7hj/?p=6&spm_id_from=pageDriver&vd_source=de55588165743d85bbfd39eae0d9c152

代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

现在给你一个函数,在不修改函数源码的前提下,实现在函数执行前和执行后分别输入"before"和"after"。

def func():
    print("我是func函数")
    value = (11, 22, 33, 44)
    return value

result = func()
print(result)

3.1 第一回合

利用闭包的思想实现

def func():
    print("我是func函数")
    value = (11, 22, 33, 44)
    return value

def outer(origin):
    def inner():
        print("before")
        res = origin()
        print("after")
        return  res

    return inner

func = outer(func)

result = func()
print(result)

3.2 第二回合

利用python中支持特殊语法

'''
python中支持特殊语法,在某个函数上方使用:
@函数名
def xxx():
    pass

Python内部会自动执行函数名(xxx),执行完之后,再讲结果赋值给xxx。
xxx=函数名(xxx)

'''

def outer(origin):
    def inner():
        print("before")
        res = origin()
        print("after")
        return  res

    return inner

@outer   # func = outer(func)
def func():
    print("我是func函数")
    value = (11, 22, 33, 44)
    return value



# func = outer(func)   # 等同与  xxx=函数名(xxx)  实现的功能

result = func()
print(result)

3.3 第三回合

请在这3个函数执行前和执行后分别输入"before"和"after"

def func1():
    print("我是func1函数")
    value = (11, 22, 33, 44)
    return value

def func2():
    print("我是func2函数")
    value = (11, 22, 33, 44)
    return value

def func3():
    print("我是func3函数")
    value = (11, 22, 33, 44)
    return value

func1()
func2()
func3()
def outer(origin):
    def inner():
        print("before")
        res = origin()
        print("after")
        return  res

    return inner

@outer
def func1():
    print("我是func1函数")
    value = (11, 22, 33, 44)
    return value

@outer
def func2():
    print("我是func2函数")
    value = (11, 22, 33, 44)
    return value

@outer
def func3():
    print("我是func3函数")
    value = (11, 22, 33, 44)
    return value

func1()
func2()
func3()

优化支持n个参数

利用不定长参数: *args, **kwargs

def outer(origin):
    def inner(*args, **kwargs):
        print("before")
        res = origin(*args, **kwargs)
        print("after")
        return  res

    return inner

@outer
def func1(a1):
    print("我是func1函数")
    value = (11, 22, 33, 44)
    return value

@outer
def func2(a1,a2):
    print("我是func2函数")
    value = (11, 22, 33, 44)
    return value

@outer
def func3(a1):
    print("我是func3函数")
    value = (11, 22, 33, 44)
    return value

func1(11)
func2(11,22)
func3(99)

小总结

●实现原理:基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数。
●实现效果:可以在不改变原函数内部代码和调用方式的前提下实现在函数执行和执行扩展功能

●适用场景:多个函数系统统一在执行前后自定义一些功能。
●装饰器示例

def outer(origin):
    def inner(*args, **kwargs):
        # 执行前
        res = origin(*args, **kwargs)  #调用原来的func函数
        # 执行后
        return  res
    return inner

@outer
def func():
   pass

func()

伪应用场景

在以后编写一个网站时,如果项目共有100个页面,其中99个是需要登录成功之后才有权限访问,就可以基于装饰器来实现。

pip install flask

基于第三方模块Flask(框架)快速写一个网站:

基于装饰器实现的伪代码︰

from flask import Flask

app = Flask(__name__)

def auth(func):
    def inner(*args,**kwargs):
        # 判断用户是否已经登录,已登录继续网下走,未登录则返回登录页面。
        
        res = func(*args,**kwargs)
        return res
    return inner

@auth
def index():
    return "首页"

@auth
def info():
    return "用户中心"

@auth
def order():
    return "订单中心"

def login():
    return "登录页面"


app.add_url_rule("/index/", view_func=index)
app.add_url_rule("/info/", view_func=info)
app.add_url_rule("/order/", view_func=order)
app.add_url_rule("/login/", view_func=login)

app.run()

重要补充:functools

你会发现装饰器实际上就是将原函数更改为其他的函数,然后再此函数中再去调用原函数。

def admin():
    '''这个是XXX的函数'''
    print(112)

admin()
print(admin.__name__) # "admin"
print(admin.__doc__) # 这个是XXX的函数

加上了装饰器之后

def auth(func):
    def inner(*args,**kwargs):
        '''asfdsadfsf'''
        res = func(*args,**kwargs)
        return res
    return inner

@auth
def admin():
    '''这个是XXX的函数'''
    print(112)

admin()
print(admin.__name__) # "inner"
print(admin.__doc__) # asfdsadfsf

即说明:经过装饰器装饰之后,这里的inner函数就待指admin函数


场景:虽然我们知道经过装饰器装饰之后,原函数被其他函数所替代了。但是我们还是想通过访问函数对象的属性时返回的还是他原来自己的值。

利用functools.wraps()

这个时候我们去执行admin()虽然它还是间接的执行的是inner()。但是这个时候通过访问函数对象的属性时返回的是他原来自己的值。

import functools
def auth(func):
    @functools.wraps(func)  #  inner.__name__ = func.__name__  inner.__doc__ = func.__doc__
    def inner(*args,**kwargs):
        '''asfdsadfsf'''
        res = func(*args,**kwargs)
        return res
    return inner

@auth
def admin():
    '''这个是XXX的函数'''
    print(112)

admin()
print(admin.__name__) # "admin"
print(admin.__doc__) # 这个是XXX的函数

其实,一般情况下大家不用functoos也可以实现装饰器的基本功能,但后期在项目开发时,不加functols会出错(内部会读取_name_,且_name_重名的话就报错),所以在此大家就要规范起来自己的写法。

偏函数partial()

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。

函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以**固定住原函数的部分参数**,从而在调用时更简单。偏函数可以简化参数操作

当函数的某个参数是我们可以提前获知的,那我们就可以将它固定住!

举个例子

# 定义一个取余函数,默认和2取余;
def mod(x,y=2):
  # 返回 True 或 False
  return x % y == 0

# 假设我们要计算和3取余,如果不使用partial()函数,那么我们每次调用mod()函数时,都要写y=3
mod(4,y=3)
mod(6,y=3)

# 使用partial()函数
from functools import partial
mod_3 = partial(mod,y=3)
mod_3(4)
mod_3(6)

使用偏函数可以让我们减少填写参数的步骤,也可以使代码更加简洁。

有的人说那我重新定义一个不也可以吗?使用Ctrl C & Ctrl V无视函数体代码量多少,再定义一个。

当然可以,但是这样你的源码就会显得很臃肿,试想一下有两个函数,它俩的不同点就是默认参数值不一样,源码看起来就繁琐,读起来更是!

总之,使用偏函数,可以使你的代码和操作更加简洁!


int2 = functools.partial(参数1,参数2,参数3)

参数1:函数对象,

参数2:*args 可变参数,接收tuple,list

参数3:*kw 关键字参数,接收dict

例如:

int2 = functools.partial(int'10',base=10)

​ 参数1必填,参数2和参数3可省略,那就和原函数没区别了,因为参数1就是原函数,参数2就是可变参数,参数3为关键字参数,int自带关键字参数base,当不传为默认值,传入时必须以base=xxx的形式,对应原始的关键字,参数2传入的话会组装成tuple或list,再通过*args 传入int(*args)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值