Python-函数式编程(高阶函数、匿名函数、返回函数、装饰器)

目录

简介

高阶函数

map

filter

匿名函数

返回函数

闭包

装饰器

双装饰器

装饰器模板

思考题

参考


简介

函数式编程是种编程范式(函数式编程程序编程面向对象编程指令式编程等)。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。

函数式编程关心数据的映射,命令式编程关心解决问题的步骤

这里的映射就是数学上「函数」的概念——一种东西和另一种东西之间的对应关系。

这也是为什么「函数式编程」叫做「函数」式编程。(知乎——什么是函数式编程思维

高阶函数

高阶函数是至少满足下列一个条件的函数:

  • 接受一个或多个函数作为输入

  • 输出一个函数

在看下面的内容之前,你需要转变一下思想:

函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。

为了让读者更清晰地理解,在上一篇文章Python-函数基础总结与内置函数中,将print函数赋值给了变量display,类似于函数指针或者说给print函数起了个别名。

>>> display
<built-in function print>

map

map(functioniterable...)

返回一个将 function 应用于 iterable 中每一项并输出其结果的迭代器。 如果传入了额外的 iterable 参数,function 必须接受相同个数的实参并被应用于从所有可迭代对象中并行获取的项。 当有多个可迭代对象时,最短的可迭代对象耗尽则整个迭代就将结束。

仍以上一篇的函数为例:

def power(x, n=2):
    return x ** n

如果需要一个1~10的平方组成的列表,你会怎么做呢?for循环,调用power,添加到列表中?使用map一行就搞定了。如果是立方呢?请看下面:

t = (range(1, 11))
n = (3, 3, 3, 3, 3, 3, 3, 3, 3, 3)
res = map(power, t)
print(type(res))
print(list(res))
res = map(power, t, n)
print(list(res))

结果:

<class 'map'>
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

python3没有reduce?不,它被放到了functools模块中,需要导入。这是为什么呢?博主也很好奇,于是去找了一下,原来不止是reduce,python语言的设计者吉多·范罗苏姆(Guido van Rossum)甚至想将lambda、reduce、filter、map函数都删除。

吉多·范罗苏姆(Guido van Rossum)的部分回信内容

最后他只从标准库中删除了reduce,并放到了functools里面。他不想自己设计的语言引入其他语言中抽象复杂的部分,python更多的是作为一种面向对象的脚本语言,而不是函数式编程语言。

参考:The fate of reduce() in Python 3000

filter

filter(functioniterable)

用 iterable 中 传入函数 function返回真的那些元素,构建一个新的迭代器。iterable 可以是一个序列,一个支持迭代的容器,或一个迭代器。如果 function 是 None ,则会假设它是一个身份函数,即 iterable 中所有返回假的元素会被移除。

一句话:利用函数对序列进行过滤/筛选,如同名字一样。

得到一个列表包含100以内的所有素数:

def prime(num):
    tmp = math.sqrt(num)
    i = 2
    while i <= tmp:
        if num % i == 0:
            return False
        i += 1
    return True
t = (range(2, 100))
res = filter(prime, t)
print(type(res))
print(list(res))

结果:

<class 'filter'>
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

大多数情况下,列表生成式可以完成map和filter的功能。

map 相当于[F(x) for x in S],filter相当于for x in S if P(x)]

t = (range(1, 11))
l = [power(x) for x in t]
l = [power(x,3) for x in t]
t = (range(2, 100))
l = [x for x in t if prime(x)]

有关列表更多内容,可查看:Python-列表总结(操作符、方法、内置函数、相关模块)

匿名函数

一些时候只会在一个地方调用一个很简单的函数,没必要将他们使用def显示的定义出来,这个时候就用到了匿名函数。

Lambda函数可以在需要函数对象的任何地方使用。它们在语法上限于单个表达式。语法如下:

lambda [arg1,arg2,....]:expression

中括号代表可选,也就是说Lambda函数可以有>=0个参数

对于上一篇文章的plus函数:

def plus(a, b):
    return a + b

可以写成:

lambda a,b:a+b

对于power函数,可以写成:

lambda x:x**2

同样,lambda可以作为参数传给其他函数,例如,传给map:

res = map(lambda x: x ** 2, t)

返回函数

说完了函数作为参数,我们就来说说函数作为返回值,也就是返回一个函数。

既然返回一个函数,我们就需要在一个函数中再定义一个函数,有种套娃的感觉。

def plus_not_now(*t):
    def plus_all():
        return sum(t)

    return plus_all


tp = (range(1, 6))
fun = plus_not_now(*tp)
print(type(fun))
print(fun())

我们在plus_not_now中又定义了一个函数plus_all,并将它作为结果返回(注意,不是返回plus_all())。

结果:

<class 'function'>
15

调用函数fun时才真正运行plus_all的内容。

这里有个奇怪的事情,我们在上面的程序后面再定义一个变量fun2,打印一下两个id

fun2 = plus_not_now(*tp)
print(id(fun))
print(id(fun2))

结果:

<class 'function'>
15
2005949437416
2005950885320

怎么会不一样呢?这个问题就作为本篇文章的思考题吧!

闭包

当一个内嵌函数引用其外部作用域的变量,我们就会得到一个闭包。创建一个闭包必须满足以下几点:

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

前面的返回函数就是闭包

注意,尽量不要引用变化变量

def plus():
    funcs = []
    for i in range(3):
        def add():
            return i + i
        funcs.append(add)
    return funcs


if __name__ == '__main__':
    p1, p2, p3 = plus()
    print(p1(), p2(), p3())

结果为

4 4 4

等你运行p1时,i已经变为了2,不是0,当然,你可以将参数绑定:

def plus():
    funcs = []

    def bind(j):
        def add():
            return j + j

        return add

    for i in range(3):
        funcs.append(bind(i))
    return funcs

装饰器

装饰器:在代码运行前后动态增扩展功能的方式

目前有一些业务已经上线了,现在需要增加验证功能

def add(a, b):
    print(a + b)


def multi(a, b):
    print(a * b)


def devision(a, b):
    print(a / b)

你可能这样写,但如果不让你修改已上线业务代码呢?

def calling(name):
    print(f'{name}正在被调用...')


def add(a, b):
    calling('add')
    print(a + b)


def multi(a, b):
    calling('multi')
    print(a * b)


def devision(a, b):
    calling('devision')
    print(a / b)

你可以这样

def addcalling(func):
    def calling(a, b):
        print(f'{getattr(func, "__name__")}正在被调用...')
        func(a, b)

    return calling


def add(a, b):
    print(a + b)


def multi(a, b):
    print(a * b)


def devision(a, b):
    print(a / b)


if __name__ == '__main__':
    add = addcalling(add)
    add(3, 5)

函数指向了内部函数,multi这些也可以,当然,Python中使用 @装饰器 即可自动让装饰器将下面的函数传入并返回装饰后的函数

def addcalling(func):
    def calling(a, b):
        print(f'{getattr(func, "__name__")}正在被调用...')
        func(a, b)

    return calling

@addcalling
def add(a, b):
    print(a + b)

@addcalling
def multi(a, b):
    print(a * b)

@addcalling
def devision(a, b):
    print(a / b)


if __name__ == '__main__':
    add(3, 5)
    multi(3, 5)
    devision(3, 5)

@addcalling  等价于 func = addcalling(func),所以,即使不运行add(3,5),在解释器解释python代码的过程中碰到@addcalling也会运行addcalling函数

双装饰器

在上面的基础上,再添加一个计算函数运行时间的装饰器

import time


def addcalling(func):
    def calling(a, b):
        print(f'{getattr(func, "__name__")}正在被调用...')
        func(a, b)

    return calling


def addtime(func):
    def runtime(a, b):
        starttime = time.time()
        func(a, b)
        endtime = time.time()
        print(f'运行时间{endtime - starttime}')

    return runtime

@addtime
@addcalling
def add(a, b):
    print(a + b)

@addtime
@addcalling
def multi(a, b):
    print(a * b)

@addtime
@addcalling
def devision(a, b):
    print(a / b)


if __name__ == '__main__':
    add(3, 5)
    multi(3, 5)
    devision(3, 5)

addtime虽然写在前面,但其实是先用addcalling装饰后再用的addtime,类似栈,可以在addtime的时候输出一下函数名

def addtime(func):
    def runtime(a, b):
        starttime = time.time()
        func(a, b)
        print(getattr(func, "__name__"))
        endtime = time.time()
        print(f'运行时间{endtime - starttime}')

结果

add正在被调用...
8
calling
运行时间0.0

装饰器模板

def wrapper_args(*args, **kwargs):
    def wrapper(func):
        # 装饰前需要运行的语句
        def inner(*args, **kwargs):
            # 被装饰函数运行前需要执行的语句
            ret = func(*args, **kwargs)
            # 被装饰函数运行后,得到返回值前需要执行的语句
            return ret

        # 装饰后,返回函数前需要运行的语句
        return inner

    return wrapper

 例如,在Flask等框架中有@app.route(“/”),来添加路由,这就是含参装饰器,上面应该是装饰器的很全的版本了。

或者使用类实现装饰器

class Wrapper(object):
    def __init__(self, func):
        self.__func = func

    def __call__(self, *args, **kwargs):
        # 被装饰函数运行前需要执行的语句
        ret = self.__func(*args, **kwargs)
        # 被装饰函数运行后,得到返回值前需要执行的语句
        return ret

 装饰器的应用:

  • 计算函数运行时间
  • 添加函数运行日志
  • 权限校验
  • 实现类的单例模式

思考题

1.上一篇文章我把print函数赋值给了一个变量display

>>> display=print
>>> id(print)
1777190314296
>>> id(display)
1777190314296

它们的id是一样的,而在上面的返回函数中,fun与fun2的id是不一样的,请思考下为什么不一样?

参考

Python官方文档-函数式编程指引

更多python相关内容:【python总结】python学习框架梳理

本人b站账号:lady_killer9

有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lady_killer9

感谢您的打赏,我会加倍努力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值