威尼斯wns.9778官网 > 计算机教程 > Python之函数进阶威尼斯wns.9778官网

原标题:Python之函数进阶威尼斯wns.9778官网

浏览次数:91 时间:2019-05-18

## 高阶函数
  - 接受函数为参数的函数称为高阶函数

本节内容

上一篇中介绍了Python中函数的定义、函数的调用、函数的参数以及变量的作用域等内容,现在来说下函数的一些高级特性:

  1. 递归函数
  2. 嵌套函数与闭包
  3. 匿名函数
  4. 高阶函数
  5. 内置函数
  6. 总结
def fn(num):       # 接收参数并打印
    print(num)

def run_fn(fn, num): # 接受函数为参数
    fn(num) # 执行

run_fn(fn, 10)      # 10

一、递归函数


函数是可以被调用的,且一个函数内部可以调用其他函数。如果一个函数在内部调用本身,这个函数就是一个递归函数。函数递归调用的过程与循环相似,而且理论上,所有的递归函数都可以写成循环的方式,但是递归函数的优点是定义简单,逻辑清晰。递归和循环都是一个重复的操作过程,这些重复性的操作必然是需要有一定的规律性的。另外,很明显递归函数也需要一个结束条件,否则就会像死循环一样递归下去,直到由于栈溢出而被终止(这个下面介绍)。

可见,要实现一个递归函数需要确定两个要素:

  • 递归规律
  • 结束条件

 

1. 实例:计算正整数n的阶乘 n! = 1 * 2 * 3 * ... * n

  - 在Python中函数名也是变量,函数体就是这个变量的值,是变量就可以重新赋值,取代原有绑定

循环实现

思路有两个:

  • 从1乘到n,需要额外定义一个计数器存放n当前的值
  • 从n乘到1,无需额外定义计数器,直接对n进行减1操作,直到n=0返回1结束
def fact(n):
    if n == 0:
        return 1

    result = 1
    while n >= 1:
        result *= n
        n -= 1
    return result
def fn(num):     # 定义一个函数
    print(num)

fn(10)       # 执行函数,输出10

fn = 10        # 给函数名即变量fn重新赋值,解除fn与函数体的绑定
fn(10)          # fn不指向函数体,无法调用
"""
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable # 报错,类型错误:整型对象无法被调用
"""

递归实现

先来确定递归函数的两个要素:

  • 递归规律:n!=1 * 2 * 3 * ... * n = (n-1)! * n,也就是说fact(n) = fact(n-1) * n,且n逐一减小
  • 结束条件:当n==0时返回1结束
def fact(n):
    if n == 0:
        return 1
    return fact(n-1) * n

怎么样?递归函数的实现方式是不是既简单、又清晰。

我们计算fact(5)的计算过程是这样的:

===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120

同理,要实现求1 2 3 ... n,可以这样写:

def fact(n):
    if n == 1:
        return 1
    return fact(n-1)   n

 

2. 递归函数优缺点

递归函数的优点:
定义简单、逻辑清晰。

递归函数的缺点:
效率并不高且需要注意防止栈溢出。

其他特点:
大家会发现上面实现的递归函数在运算的过程中n是逐渐减小的,也就是说问题规模应该是逐层减小的。

## 常用内建高阶函数

3. 递归特性总结

下面我们来总结写递归的特性:

  • 必须有一个明确的结束条件
  • 每次进入更深一层的递归时,问题规模相比上次递归都应有所减小
  • 递归效率不高,递归层次过多会导致栈溢出。

因为在计算中,函数调用是通过栈(stack,特点是后进先出--LIFO)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈针,每当函数返回,栈就会减少一层栈针。由于栈的大小不是无限的,所有递归调用的次数过多,会导致栈溢出。关于堆栈的介绍可以看下这里:<<内存堆和栈的区别>>

每种编程语言都对递归函数可递归的深度有限制(可以看看这里),有些是跟相应内存空间的分配有关(因为栈是在内存空间中的),如Java。Python中对递归的深度限制默认为1000,可以通过sys.getrecursionlimit()函数来获取该值,超过这个深度会报错:RecursionError: maximum recursion depth exceeded in comparison。当然也可以通过sys.setrecursionlimit(n)来设置新的限制值。

  - map(fn, iterable)
    - 此函数接受两个参数,第一个参数为函数,第二个参数为可迭代对象
    - map()函数执行时,将可迭代对象参数中逐一取出一个值作为参数传入第一个函数参数并执行该参数函数      

二、嵌套函数与闭包


def fn(n):
    return n * n

map(fn, [1, 2, 3])       # 计算列表元素的平方
# <map object at 0x7f5ee58e5b00>

list(map(fn, [1, 2, 3]))
# [1, 4, 9]

list(map(str, [1, 2, 3]))  # 将列表中所有值都转换为字符串
# ['1', '2', '3']

1. 嵌套函数

嵌套函数是指在函数内部定义一个函数,这些函数都遵循各自的作用域和生命周期规则。

来看个例子:

def outer():
    level = 1
    print('outer', level)
    def inner():
        print('inner', level)
    inner()

调用outer函数outer(),输出结果如下:

outer 1
inner 1

再来看个例子:

def outer():
    level = 1
    print('outer', level)
    def inner():
        level = 2
        print('inner', level)
    inner()

调用outer函数outer(),输出结果如下:

outer 1
inner 2

嵌套函数查找变量的顺序是:先查找自己函数体内部是否包含该变量,如果包含则直接应用,如果不包含则查找外层函数体内是否包含该函数,依次向外。

 

2. 闭包

首先要说明一个问题:函数名其实也是一个变量,我们通过def定义一个函数时,实际上就是在定义一个变量,函数名就是变量名称,函数体就是该变量的值。我们知道,变量是可以赋值给其他变量的,因此函数也是可以被当做返回值返回的,并且可以赋值给其他变量。

def outer(x):
    def inner(y):
        print(x y)
    return inner

f1 = outer(10)
f2 = outer(20)

f1(100)
f2(100)

上面操作的执行结果是:

110
120

我们知道局部变量的作用域是在定义它的函数体内部,局部变量在函数执行时进行声明,函数执行完毕则会被释放。上面也提到过了,函数也是一个变量,那么嵌套函数内部定义的函数也是一个局部变量,也就是说嵌套函数每调用一次,其内部的函数都会被定义一次。因此,在上面的示例中

f1 = outer(10)
f2 = outer(20)

对于f1和f2而言,两次调用嵌套函数outer并返回的内部函数inner是不同的,且它们取到的x值也是不同的。从表面上来看f1和f2相当于把x分别替换成了10和20:

def f1(y):
    print(10 y)

def f2(y):
    print(20 y)

但实际上不是这样的,f1和f2还是这样的:

def f1(y):
    print(x y)

def f2(y):
    print(x y)

f1和f2被调用时,y的值是通过参数传递进来的(100),而x还是个变量。inner函数会在自己的函数体内部查找该局部变量x,发现没找到,然后去查找它外层的函数局部变量x,找到了。这里好像出现问题了,因为之前说过了局部变量会在函数执行结束后被释放,那么f1和f2被调用时outer函数已经执行完了,理论上x的值应该被释放了才对啊,为什么还能引用x的值?其实,这就是闭包的作用。

  - reduce(fn, iterable),使用前需要从functools导入
    - 此函数接受两个参数,第一个参数为函数,第二个参数为可迭代对象
    - 将第二个参数的元素按照第一个参数函数的语句逐一累积为一个值,并返回结果

闭包的定义

如果在一个内部函数中,引用了外部非全局作用域中的变量,那么这个内部函数就被认为是闭包(closure)。

在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数应用了外部函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在该内部函数被多次调用的过程中,这些私有变量能够保持其持久性。在支持将函数作为对象使用的编程语言中,一般都支持闭包,比如:Python、PHP、Javascript等。

闭包就是根据不同的配置信息得到不同的结果。专业解释是:闭包(closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的应用环境组合而成的实体。

from functools import reduce

def add(a, b):
    return a   b

reduce(add, [1, 2, 3, 4])     # 相当于(((1   2)   3)   4)
# 10    

闭包的工作原理

Ptyhon支持一种特性叫做函数闭包(function closres),它的工作原理是:在非全局(global)作用域(函数)中定义inner函数时,这个inner函数会记录下外层函数的namespaces(外层函数作用域的locals,其中包括外层函数局部作用域中的所有变量),可以称作:定义时状态,inner函数可以通过__closure__(早期版本中为func_closure)这个属性来获得inner函数外层嵌套函数的namespaces。其实我们可以通过打印一个函数的__closesure__属性值是否为None来判断闭包是否发生。

 

闭包与装饰器

其实装饰器就是一种闭包,或者说装饰器是闭包的一种经典应用。区别在于,装饰器的参数(配置信息)是一个函数或类,专门对类或函数进行加工、处理和功能增强。关于装饰器,我们会在后面详细介绍。

  - filter(fn, iterable)
    - 过滤器
    - 将第二个参数的元素按照第一个参数函数的条件逐一检查,若参数函数返回True,则保留该元素,否则则舍去该元素

三、匿名函数


在Python中有两种定义函数的方式:

  • 通过def关键字定义的函数:这是最常用的方式,前面已经介绍过
  • 通过lambda关键字定义的匿名函数:这是本次要说的主角

lambda作为一个关键字,作为引入表达式的语法。与def定义的函数相比较而言,lambda是单一的表达式,而不是语句块。也就是说,我们仅仅能够在lambda中封装有限的业务逻辑(通常只是一个表达式),这样设计的目的在于:让lambda纯粹为了编写简单的函数(通常称为小函数)而设计,def则专注于处理更大的业务。

def even(n):
    if n % 2 == 0:
        return True
    return False

filter(even, [1, 2, 3, 4, 5, 6])
# <filter object at 0x7f57c68cc550>

list(filter(even, [1, 2, 3, 4, 5, 6]))
# [2, 4, 6]

1. 匿名函数的定义

 

语法:
lambda argument1, argument2, ... argumentN :expression using argments

冒号左边是函数的参数,冒号右边是一个整合参数并计算返回值的表达式。

  - sorted():排序

实例:定义一个求两个数之和的函数

def函数

def add(x, y):
    return x   y

lambda函数

lambda x, y: x y
numbers = [-50, -43, 23, 188, 20]
sorted(numbers, key=abs)         # 按照绝对值大小排序
# [20, 23, -43, -50, 188]
sorted(numbers, key=abs,reverse=True)  # 排序后反序
# [188, -50, -43, 23, 20]

威尼斯wns.9778官网,2. 匿名函数的调用方式:

调用方式1:匿名函数也是一个函数对象,可以将匿名函数赋值给一个变量,然后通过在这个变量后加上一对小括号来调用:

add = lambda x, y: x y
sum = add(1, 2)

调用方式2:直接在lambda函数后加上一对小括号调用:

sum = (lambda x, y: x y)(1, 3)

 

3. 匿名函数的特性:

  • 函数体只能包含一个表达式
  • 不能有return语句(表达式的值就是它的返回值)
  • 参数个数不限,可以有0个、1个或多个

本文由威尼斯wns.9778官网发布于计算机教程,转载请注明出处:Python之函数进阶威尼斯wns.9778官网

关键词:

上一篇:Python Django框架笔记(一):安装及创建项目

下一篇:没有了