函数的高级使用

746 阅读10分钟

1、闭包

这是我参与更文挑战的第9天,活动详情查看: 更文挑战

1.1 函数概念理引用

def test1():
    print("--- in test1 func----")

# 调用函数
test1()

# 引用函数
ret = test1

print(id(ret))
print(id(test1))

#通过引用调用函数
ret()

运行结果:

--- in test1 func----
140212571149040
140212571149040
--- in test1 func----

由上图可知,和变量名一样的,函数名只是函数代码空间的引用,当函数名赋值给一个对象的时候 就是引用传递

1.2 闭包

# 定义一个函数
def test(number):

    # 在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包
    def test_in(number_in):
        print("in test_in 函数, number_in is %d" % number_in)
        return number+number_in
    # 其实这里返回的就是闭包的结果
    return test_in


# 给test函数赋值,这个20就是给参数number
ret = test(20)

# 注意这里的100其实给参数number_in
print(ret(100))

#注 意这里的200其实给参数number_in
print(ret(200))

运行结果:

in test_in 函数, number_in is 100
120

in test_in 函数, number_in is 200
220

1.3 看一个闭包的实际例子:

def line_conf(a, b):
    def line(x):
        return a*x + b
    return line

line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
print(line1(5))
print(line2(5))

这个例子中,函数line与变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用。

如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。

注意点:

由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存

1.4 修改外部函数中的变量

def counter(start=0):
    def incr():
        nonlocal start
        start += 1
        return start
    return incr

c1 = counter(5)
print(c1())
print(c1())

c2 = counter(50)
print(c2())
print(c2())

print(c1())
print(c1())

print(c2())
print(c2())

1.5 总结

  • 函数名只是函数代码空间的引用,当函数名赋值给一个对象的时候 就是引用传递
  • 闭包就是一个嵌套定义的函数,在外层运行时才开始内层函数的定义,然后将内部函数的引用传递函数外的对象
  • 内部函数和使用的外部函数提供的变量构成的整体称为闭包

2、装饰器

首先看一段代码

def show():
    print("我们遇到什么苦难也不要怕!!!微笑着面对它!!!")
    
# show 是当前函数对象的名字,因为python中万物皆对象,函数本身也是一个对象
# 将show的函数所在内存地址的索引赋予了func变量,是func变量也可以使用show中保存的功能
func = show  
func()   # 输出show函数中的print语句

通过以上代码,可以确定一点就是 函数名只是一个对象,和普通对象一样,这个对象可以引用其他函数的代码

2.1 装饰器

​ 概念:在不改变原函数功能的基础上进行,功能的扩展,这就是装饰器。

装饰器的本质就是一个函数。是使用另一个函数功能增加旧函数的功能

使用格式:

@拓展功能的函数
要被拓展功能的函数():
    pass
# 拓展功能的函数其实就是装饰器,但是它本质上是一个函数,并且这个函数必须是一个闭包函数,且外层函数需要接收要被拓展功能函数的内存引用

现有一个需求:在不改变上面代码中show功能的基础上使其再输入‘加油,奥利给’。

# say函数是show函数的装饰器(用来给show函数进行功能拓展的)
def say(func): 
    # 对于闭包来说,外层函数需要接收要比装饰函数的内存引用
    def inner():
        print("奥里给")
        func()
    return inner

@say   # 当前装饰器的功能相当于 say(show),注意当前装饰的函数是没有任何参数的
def show():
    print("我们遇到什么苦难也不要怕!!!微笑着面对它!!!")

show()

装饰器的执行过程:

# 将上面的装饰器代码理解成普通的函数调用
def say(func):
    def inner():
        print("奥里给")
        func()
    return inner

def show():
    print("我们遇到什么苦难也不要怕!!!微笑着面对它!!!")

# 将变量名show的引用地址(该地址为show方法功能的内存地址),传递给say函数,并肩该闭包函数返回的inner使用show变量进行保存   
show= say(show)
# 调用show(),即等价调用inner()
# 内部函数inner被引用,所以外部函数的func变量(自由变量)并没有释放
# func里保存的是原show函数对象
        

2.2 被装饰的函数有参数

# 如果装饰的函数有参数,需要在内部函数中写入相同的参数,如果不确定装饰的函数有几个参数,内部函数也可以使用*args 和 **kwargs
def say(func):
    def inner(a, b):
        print(a, b)
        print("奥里给")
        func(a, b)
    return inner

@say
def show(a, b):
    print("我们遇到什么苦难也不要怕!!!微笑着面对它!!!")
    print(a + b)

show(1, 2)

2.3 装饰的函数有返回值

# 如果装饰的函数有返回值需要在内部函数中return 装饰函数的运行结果
def say(func):
    def inner():
        print("-----2-----")
        return func()
    return inner

@say
def show():
    return "-----1-----"


print(show())

2.4 使用多个装饰器装饰同一个函数

# 定义函数:完成包裹数据
def makeBold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

# 定义函数:完成包裹数据
def makeItalic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"
    return wrapped

@makeBold
def test1():
    return "hello world-1"

@makeItalic
def test2():
    return "hello world-2"

@makeBold
@makeItalic
def test3():
    return "hello world-3"

print(test1())
print(test2())
print(test3())
# 执行结果为:
"""
<b>hello world-1</b>
<i>hello world-2</i>
<b><i>hello world-3</i></b>
"""
# 执行是就近原则

3、匿名函数

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

在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

4、高阶函数

4.1 map函数

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

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

            f(x) = x * x

                  │
                  │
  ┌───┬───┬───┬───┼───┬───┬───┬───┐
  │   │   │   │   │   │   │   │   │
  ▼   ▼   ▼   ▼   ▼   ▼   ▼   ▼   ▼

[ 1   2   3   4   5   6   7   8   9 ]

  │   │   │   │   │   │   │   │   │
  │   │   │   │   │   │   │   │   │
  ▼   ▼   ▼   ▼   ▼   ▼   ▼   ▼   ▼

[ 1   4   9  16  25  36  49  64  81 ]

map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,该Iterator是一个特俗的对象,因此通过list()`函数让它把整个序列都计算出来并返回一个list。

你可能会想,不需要map()函数,写一个循环,也可以计算出结果:

L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
    L.append(f(n))
print(L)

的确可以,但是,从上面的循环代码,能一眼看明白“把f(x)作用在list的每一个元素并把结果生成一个新的list”吗?

所以,map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:

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

4.2 filter函数

Python内建的filter()函数用于过滤序列。

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

例如,在一个list中,删掉偶数,只保留奇数,可以这么写:

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

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]

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

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

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

可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。

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

4.3 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)   # abs是取数字的绝对值,当前排序是根据数字的绝对值进行比较的
[5, 9, -12, -21, 36]

反向排序只需要在sorted函数 中添加参数reverse设置为True即可

>>> sorted([1, 3, 2], reverse=True)
[3, 2, 1]

4.4 reduce函数

第一个参数是一个函数,第二个参数是一个可以迭代的类型(Iterable) 第一个参数的函数也必须接受两个参数,reduce会把函数的返回值与序列的下一个元素继续传入函数做计算。

如下例子,求从1累乘到100的值

#reduce 将函数的到的结果继续当做参数传入到函数中去from functools import reducedef add(x,y):    return x*yprint(reduce(add,range(1, 100)))

那么我们可以形象的把这个方法做一个等价描述:

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

结语

文章篇幅较长,给看到这里的小伙伴点个大大的赞!由于作者水平有限,文章中难免会有错误之处,欢迎小伙伴们反馈指正。

如果觉得文章对你有帮助,麻烦 点赞、评论、收藏

你的支持是我最大的动力!!!