python笔记-高阶函数

2018-06-26 28次浏览 发表评论

一个函数可以接收另一个函数作为参数,这种函数就称之为高阶函数。 简单示例:

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

print(add(-6, 4, abs))

map/reduce

map()函数接收两个参数,一个是函数,一个是Iterablemap()将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

例如有一个函数:

f(x)=x2

要把这个函数作用在一个list[1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()实现如下:

def f(x):
    return x * x

L = [1, 2, 3, 4, 5, 6, 7, 8, 9]
r = map(f, L)
print(list(r))

输出结果:[1, 4, 9, 16, 25, 36, 49, 64, 81]

使用map()函数把list的所有整数转换为字符串:

print(list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])))

输出结果:['1', '2', '3', '4', '5', '6', '7', '8', '9']

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

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

reduce()把序列[1, 3, 5, 7, 9]变换成整数13579

from functools import reduce
def fn(x, y):
    return x * 10 + y

L = [1, 3, 5, 7, 9]
print(reduce(fn, L))

结果:13579

filter

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

例如删掉一个序列中的偶数,保留奇数:

def is_odd(n):
    return n % 2 == 1

L = [1, 2, 4, 5, 6, 9, 10, 15]
print(list(filter(is_odd, L)))

输出结果:[1, 5, 9, 15]

删除一个列表里面的空字符串:

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

L = filter(not_empty, ['A', '', 'B', None, 'C', '  '])
print(list(L))

输出结果:['A', 'B', 'C']

filter()返回的是原来的元素,filter()的第一个函数只能起到一个判断的作用。

sorted

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

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

输出结果:[-21, -12, 5, 9, 36]

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

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

输出结果:[5, 9, -12, -21, 36]

字符串排序:

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

输出结果:['Credit', 'Zoo', 'about', 'bob']

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

sorted()传入key函数,即可实现忽略大小写的排序:

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

输出结果:['about', 'bob', 'Credit', 'Zoo']

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

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

输出结果:['Zoo', 'Credit', 'bob', 'about']

返回函数

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。返回函数可以把一些计算延迟执行。例如,如果定义一个普通的求和函数:

def out_sum(lst):
    #定义函数 in_sum
    def in_sum():
        return sum(lst)
    #返回函数 in_sum
    return in_sum

下面代码并没有对函数进行执行计算出结果,而是返回函数:

L = [1, 2, 3, 4, 5]
print(out_sum(L))

输出结果:<function out_sum.<locals>.in_sum at 0x00000179D74F0400>

对返回的函数进行调用时,才计算出结果:

L = [1, 2, 3, 4, 5]
f = out_sum(L)

#函数名后加(),才是调用该函数。
print(f())
#结果 15

上面的例子中,函数out_sum中又的定义了函数in_sum,内部函数in_sum引用外部函数的参数和局部变量,当外部函数out_sum返回in_sum时,参数和变量保存在返回函数中,这种程序结构成为:闭包(Closure)

返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

当调用out_sum时候,每次返回的都是一个新的函数:

L = [1, 2, 3, 4, 5]
f1 = out_sum(L)
f2 = out_sum(L)

if f1 == f2:
    print('YES')
else:
    print('NO')

#结果 NO
#函数f1() 和 f2() 结果互不影响。

一个函数可以返回一个计算结果,也可以返回一个函数。返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。

匿名函数

当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。

L = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(list(map(lambda x: x * x, L)))

输出结果:[1, 4, 9, 16, 25, 36, 49, 64, 81]

关键字lambda表示匿名函数,冒号前面的x表示函数参数。

#匿名函数:
lambda x: x * x

#实际就是:
def f(x):
    return x * x

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

匿名函数可以赋值给一个变量,再用变量调用这个函数:

f = lambda x: x * x

print(f)
#结果 <function <lambda> at 0x000001DFDAF91E18>

print(f(5))
结果 25

装饰器

在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。

装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限验证等场景,装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

示例:

import datetime
import time
import functools


def log(func):

    # 避免经过装饰之后的原本函数名变成了 wrapper ,需要在 wrapper 前加入:
    @functools.wraps(func)
    # 接收add传参
    def wrapper(*args, **kw):

        # 参数解构,返回值赋给ret
        ret = func(*args, **kw)

        # 打印当前时间
        print('当前时间:', datetime.datetime.now(), '\r=======')

        # 返回 add 函数执行结果,并加以装饰
        return('函数名:{}()\r参数:{}\r结算结果:{}'.format(func.__name__, args, ret))
    return wrapper

# @log 等于 add = log(add) 也就是 wrapper()
@log
def add(*args):
    j = 0
    for i in args:
        j += i
    return j


t = (1, 2, 3, 4, 5)
print(add(*t))

#验证打印原函数名是否被修改
print('原函数名为:', add.__name__)

定义一个带参数的装饰器:

# -*- coding: utf-8 -*-
import functools
import datetime

def hello():
    return 'hello!'

# 打印时间
def out_time():
    print('当前时间:', datetime.datetime.now(), '\r=======')

# 定义一个带参数的装饰器
def log(f1, f2, f3):

    # 传入add作为参数
    def decorator(func):

        # 避免经过装饰之后的原本函数名变成了 wrapper ,需要在 wrapper 前加入:
        @functools.wraps(func)

        # 接收add的传参
        def wrapper(*args):
            f2()
            ret = func(*args)
            print('{} {}'.format(f1(), f3))
            print('使用函数为:{}()   参数为:{}'.format(func.__name__, args))
            return '计算结果为:{}'.format(ret)

        # 返回函数wrapper
        return wrapper

    # 返回函数decorator
    return decorator


# 执行带参装饰器,@log 等价于 add = log(hello, out_time, '奔跑')(add)
@log(hello, out_time, '奔跑')

#定义一个垒加函数,形参为可变参数
def add(*args):
    j = 0
    for i in args:
        j += i
    return j

L = [1, 2, 3, 4]

#传入实参,解构
print(add(*L))

#验证原函数名是否被修改
print(add.__name__)

偏函数

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)

functools.partial创建一个偏函数:

import functools

int2 = functools.partial(int, base=2)
print(int2('1010'))

#相当于:
print(int('1010', base=2))

简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(默认传入原函数),返回一个新的函数。

也可以在调用新函数时候传入其他参数:

print(int2('1010', base=10))

创建偏函数时,实际上可以接收函数对象、args*kw这3个参数,当传入:

max2 = functools.partial(max, 10)

实际上会把10默认为*args的一部分自动加到左边,也就是:

max2(5, 6, 7)

相当于:

args = (10, 5, 6, 7)
max(*args)

结果为10。

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

评论

随便说点,聊聊天~