Python高级(三):函数式编程

返回函数

函数作为返回值

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

注意:
1.调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数
2.内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为”闭包“
3.返回的函数并没有立刻执行,而是直到调用了f()才执行

闭包

  1. 使用闭包,就是内层函数引用了外层函数的局部变量。如果只是读外层变量的值,我们会发现返回的闭包函数调用一切正常:
def inc():
    x = 0
    def fn():
        # 仅读取x的值:
        return x + 1
    return fn

f = inc()
print(f()) # 1
print(f()) # 1
  1. 但是,如果对外层变量赋值,由于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

  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(),f2()和f3()结果应该是1,4,9,但实际结果是,全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。

  1. 如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
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

偏函数

将字符串转换为整数的函数int(),将二进制转为整数如下:

def int2(x, base=2):
    return int(x, base)

functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:

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

简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

例子2:

max2 = functools.partial(max,10)
print(max2(5, 6, 7))

相当于

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

结果为10。

匿名函数

在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算f(x)=x2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:

>>> 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: x * x实际上就是:

def f(x):
    return x * x

关键字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

装饰器

  1. 定义:增加函数或类的功能的一个函数。通过装饰器函数,在不修改原函数的前提下,来对函数的功能进行合理的扩充。

举个例子:如何计算函数的执行时间?
如下,你需要计算 add 函数的执行时间。​​​​​​​

def add2(a, b):
    res=a+b
    return res

你可能会这么写​​​​​​​

def add(a, b):
    start_time = time.time()
    res = a + b
    end_time = time.time()
    exec_time = end_time - start_time
    print("add函数,花费的时间是:{}".format(exec_time))
    return res

这个时候,老板又让你计算减法函数(sub)的时间。不用装饰器的话,你又得重复写一段减法的代码。

def sub(a, b):
    start_time = time.time()
    res = a - b
    end_time = time.time()
    exec_time = end_time - start_time
    print("add函数,花费的时间是:{}".format(exec_time))
    return res

这样显得很麻烦,也不灵活,万一计算时间的代码有改动,你得每个函数都要改动。
所以,我们需要引入装饰器

不带参数的装饰器

  1. 定义装饰器
def time_calc(func):
    @functools.wraps(func)
    def wrapper(*args, **kargs):
        # *args 和 **kwargs 表示接受任意数量和类型的参数
        start_time = time.time()
        f = func(*args, **kargs)
        end_time = time.time()
        exec_time = end_time - start_time
        print("花费的时间是:{}".format(exec_time))
        return f
    return wrapper

time_calc是一个装饰器,接受一个函数作为参数,并返回一个函数。借助@语法,把装饰器置于需要增强的函数处
2. 调用装饰器

@time_calc
def add(a, b):
    res = a + b
    return res

调用add()函数,不仅会运行函数本身,还会运行装饰器执行的结果。
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先调用原始函数,再紧接着打印执行时间。

带参数的装饰器

如果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

这个3层嵌套的decorator用法如下:

@log('execute')
def now():
    print('2015-3-25')

执行结果如下:

>>> now()
execute now():
2015-3-25

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

>>> now = log('execute')(now)

我们来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

functools.wraps

以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper'
因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的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:

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

内置装饰器

常见的内置装饰器有三种,@property@staticmethod@classmethod

高阶函数

1. map()函数

  • 用法:map(函数名,列表/元组/集合)
  • 说明:map()将传入的函数依次作用到序列的每个元素,处理完后返回的是生成器类型,需要用list生成数据

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

def f(x):
    return x*x

r=map(f,[1,2,3,4,5])
print(list(r))



[1, 4, 9, 16, 25]

举例,把list所有数字转为字符串

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

2. reduce()函数

  • 用法:reduce(函数名,列表)
  • 说明:reduce把函数结果继续和序列的下一个元素做累计计算

举例说明:将序列[1,2,3,4,5]变成整数12345

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

print(reduce(add,[1,2,3,4,5]))


12345

举例说明:将字符串转化为整数

from functools import reduce
def add(x,y):
    return x*10+y
def char2num(s):
    digits={'1':1,'2':2,'3':3,'4':4,'5':5}
    return digits[s]
print(reduce(add,map(char2num,'12345')))

12345
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))

3. filter()

  • 用法:filter(函数名,列表/元组/集合)
  • 说明:实现筛选功能,把传入的函数依次作用到每个元素,根据返回值保留或丢弃该元素。返回True则保留元素,返回False则丢弃该元素。需要用list生成数据

举例说明

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

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


[1, 5, 9, 15]

4. sorted

  • 用法:可以对列表进行排序
>>>sorted([36, 5, -12, 9, -21])

[-21, -12, 5, 9, 36]
  • 用法:也可以接收一个key函数实现自定义的排序。
  • 说明:key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序

举例说明:

# 按绝对值大小排序
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

举例说明:字符串排序,忽略大小写

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
  • 要进行反向排序,可以传入第三个参数reverse=True
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
posted @ 2022-07-29 22:33  是小鱼呀  阅读(188)  评论(0)    收藏  举报