Python核心技术与实战学习笔记(六):自定义函数与匿名函数

6.1 函数嵌套

函数嵌套是指函数里面又有函数:

def f1():
    print('hello')
    def f2():
        print('world')
    f2()
f1()

# 输出
hello
world

函数的嵌套,主要有以下两个方面的作用:

  1. 保证内部函数的隐私。内部函数只有被外部函数调用才能访问到,不会暴露在全局作用域。因此,当你的函数内部有一些隐私数据(如数据库用户、密码等),不想暴露在外,就可以使用函数的嵌套,将其封装在内部函数中,直通过外部函数来访问。如:
def connect_DB():
    def get_DB_configuration():   
        ...
        return host, username, password
    conn = connector.connect(get_DB_configuration())
    return conn

get_DB_configuration()为内部函数,无法在connect_DB()函数以外被单独调用,这样提高了程序的安全性

  1. 合理的函数嵌套,能够提高程序的运行效率。如:
def factorial(input):
    # validation check
    if not isinstance(input, int):
        raise Exception('input must be an integer.')
    if input < 0:
        raise Exception('input must be greater or equal to 0' )
    ...

    def inner_factorial(input):
        if input <= 1:
            return 1
        return input * inner_factorial(input-1)
    return inner_factorial(input)

print(factorial(5))

以上是一个计算阶乘的递归函数。在第一次计算之前,我们需要检查输入数值的合理性(需为大于等于0的数字),使用嵌套函数的写法使得递归函数部分为内部函数,而不是整个外部函数,这使得前面的检查条件只执行了一次,提高了效率。

在本人刷LeetCode题的过程中,经常使用嵌套函数完成递归编写,这样一来避免多次执行检查语句,并且可专注于递归部分的功能实现。

6.2 函数变量作用域

局部变量

变量是在函数内部定义的,即为局部变量,只在函数内部有效。一旦函数执行完毕,局部变量就会被回收。

def read_text_from_file(file_path):
    with open(file_path) as file:
        ...

file变量在外部无法访问。

全局变量

变量在整个文件层次上定义的,即为全局变量。

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
    if value < MIN_VALUE or value > MAX_VALUE:
        raise Exception('validation check fails')

MIN_VALUE和MAX_VALUE就是全局变量,可以在文件任何地方访问。

函数内部访问全局变量

在函数内部也是可以访问全局变量的,不过我们不能在函数内部随意修改全局变量的值。如下面的写法就是错误的:

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
    ...
    MIN_VALUE += 1
    ...
validation_check(5)

报错为:

UnboundLocalError: local variable 'MIN_VALUE' referenced before assignment

这是因为定义函数时会将函数内部的变量当做局部变量,即上面的NIN_VALUE被作为一个没有声明的局部变量。为此,我们需在函数内部里面声明该变量为全局变量,即:

MIN_VALUE = 1
MAX_VALUE = 10
def validation_check(value):
    global MIN_VALUE    # 声明函数为全局变量
    ...
    MIN_VALUE += 1
    ...
validation_check(5)

global关键字声明该变量为之前声明的全局变量,这样就可以在函数内部对全局变量做出修改。

要注意的是,在函数内部中若不修改全局变量,则我们也可以不用global关键字声明,直接调用即可,如:

a = 100
def sum(x):
    return x+a

sum(20)
====================
120

所以,我们只有在函数内部需要修改全局变量的时候才会使用关键字global声明该全局变量

内部函数访问外部函数定义的变量

与函数内部访问全局变量类似,若内部函数要访问外部函数定义的变量,也是有访问和修改两种情况。

  • 若只是想调用外部函数变量的值,则直接调用即可(与函数内部直接调用全局变量一样)
  • 若想在内部函数修改外部函数变量中的值,则需在内部函数对该变量进行声明,声明其不是局部变量,为了与全局变量做区分,这里使用的关键字是nonlocal
def outer():
    x = "local"
    def inner():
        nonlocal x # nonlocal 关键字表示这里的 x 就是外部函数 outer 定义的变量 x
        x = 'nonlocal'
        print("inner:", x)
    inner()
    print("outer:", x)
outer()
# 输出
inner: nonlocal
outer: nonlocal

6.3 闭包

闭包和嵌套函数类似,或者说是嵌套函数的其中一种情形:外部函数返回的是其内部函数。返回的函数通常将其赋予给一个变量,其效果就像是给不同特定功能的函数(通过给外部函数传入不同的参数实现)以不同的别名。如下所示:

def nth_power(exponent):
    def exponent_of(base):
        return base ** exponent
    return exponent_of # 返回值是 exponent_of 函数

square = nth_power(2) # 外部函数传入参数2,实现计算平方函数
cube = nth_power(3) # 外部函数传入参数3,实现计算立方函数
square
# 输出
<function __main__.nth_power.<locals>.exponent(base)>

cube
# 输出
<function __main__.nth_power.<locals>.exponent(base)>

print(square(2))  # 计算 2 的平方
print(cube(2)) # 计算 2 的立方
# 输出
4 # 2^2
8 # 2^3

使用闭包的三个好处:

  1. 通过闭包,可使得程序更简洁易读。这是因为,如果不使用闭包,我们将函数写为以下形式:
def nth_power_rewrite(base, exponent):
    return base ** exponent

这看起来比上面要简洁很多,但是当我们需要计算多个数的平方,就会写成如下代码:

# 不使用闭包
res1 = nth_power_rewrite(base1, 2)
res2 = nth_power_rewrite(base2, 2)
res3 = nth_power_rewrite(base3, 2)
...

# 使用闭包
square = nth_power(2)
res1 = square(base1)
res2 = square(base2)
res3 = square(base3)
...
  1. 同嵌套函数的优点类似,函数开头需要做一些额外工作,当我们需要多次调用该函数时,将这些额外工作放在外部函数,就可以避免多次调用导致的不必要的开销,提高程序的运行效率。
  2. 闭包经常和装饰器一起使用

6.4 匿名函数

匿名函数的关键词为lambda,主要形式如下:

lambda argument1, argument2,... argumentN : expression

例:

square = lambda x: x**2
square(3)
=========================
9

可见匿名函数和常规函数一样,返回的都是一个函数对象(function object)。

匿名函数和常规函数的主要区别主要有以下两点:

  1. lambda是一个表达式(expression),并不是一个语句(statement)

lambda因此可以用在一些常规函数def不能用的地方。

如lambda可以用在列表内部,而常规函数不能

[(lambda x: x*x)(x) for x in range(10)]
# 输出
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

上述代码等价于以下列表生成式:

lst1 = [x**2 for x in range(10)]

再比如lambda可以被用作某些函数的参数,而常规函数def不能:

l = [(1, 20), (3, 0), (9, 10), (2, -1)]
# 对l按列表中元组的第二个元素的大小进行排序
l.sort(key=lambda x:x[1])
print(l)
=====================
[(2, -1), (3, 0), (9, 10), (1, 20)]
# 对字典d按键进行降序排序
d = {2:3,1:21,5:15,-6:2,25:9}
d = sorted(d.items(),reverse=True)    # 注意返回的是列表
print(d)
======================
[(25, 9), (5, 15), (2, 3), (1, 21), (-6, 2)]

# 对字典d按值进行降序排序
q = {2:3,1:21,5:15,-6:2,25:9}
q = sorted(q.items(),key=lambda x:x[1], reverse=True)  
# 元组对应着匿名函数中的x[1],要用q.items();注意sorted返回的是列表
print(q)
=======================
[(1, 21), (5, 15), (25, 9), (2, 3), (-6, 2)]
  1. lambda 的主体是只有一行的简单表达式,并不能扩展为多行的代码

lambda可以使得程序更加简洁易读,如:

def hint:
	print('please input your password:')

可以改写为:

hint = lambda:print('please input your password:')

这样,hint就可以当做参数传入到其他一些类或方法中,如:

from tkinter import Button, mainloop
button = Button(
    text='This is a button',
    hint=lambda: print('being pressed')) # 点击时调用 lambda 函数
button.pack()
mainloop()

函数式编程

函数式编程是指代码中每一块都是不可变的(immutable),都由纯函数(pure function)的形式组成。

纯函数是指函数本身相互独立、互不影响,对于相同的输入,总会有相同的输出,没有任何副作用。

示例:

  • 非纯函数
def multiply_2(l):
    for index in range(0, len(l)):
        l[index] *= 2
    return l

传入的列表中的值被改变了,多次调用则每次的列表都会改变,故该函数不是纯函数

  • 纯函数
    要想上面的代码改为纯函数,则需新建一个新的列表并返回。
def multiply_2_pure(l):
    new_list = []
    for item in l:
        new_list.append(item * 2)
    return new_list

函数编程的优点和缺点

优点:

  • 函数编程的纯函数和不可变特性使得程序更加健壮,易于调试(debug)和测试;

缺点:

  • 限制多,难写

python虽然不像Scala一样是一门函数式编程语言,不过也提供了一些函数式编程的特性。主要在于python提供了这么几个函数:filter(),map()和reduce(),通常结合匿名函数lambda一起使用。

map(function,iterable)

表示对iterable中的每个元素都运用function 进行处理,最后返回一个新的可遍历的集合。(体现了函数式编程的纯函数特性)
如要对列表中的每个元素都乘以2,可用写为:

l = [1, 2, 3, 4, 5]
new_list = map(lambda x: x * 2, l) # [2, 4, 6, 8, 10]

要注意的是,上述代码map()返回的是map类型,需要再进行list(new_list)才能转化为列表形式。

关于map()的效率的比较,有以下代码:

# 对列表的,每个元素进行平方操作
lst = [1,2,3,4,5]

# 第二种创建方式最快,它直接调用C编写的map()函数
squared1 = [x**2 for x in lst]
squared2 = map(lambda x:x**2,lst)  # squared2为map类型的函数,可遍历得到值
squared3 = []
for i in lst:
    squared3.append(i**2)

print(squared1)
================
[1, 4, 9, 16, 25]

type(squared2)
================
map
  • 若只是想得到一个iterable,则使用map()的方法最快
  • 若想得到列表结构的结果,则使用列表解析式最快
filter(function,iterable)

表示对iterable中的每个元素都用function 进行判断,将返回True的元素作为一个新的可遍历集合返回。(体现了函数式编程的纯函数特性)

如要返回列表中的偶数,则可写为:

l = [1, 2, 3, 4, 5]
new_list = filter(lambda x: x % 2 == 0, l) # [2, 4]
reduce(function, iterable)

表示对iterable中的每个元素通过function做一些累积的操作。其中,规定function有两个参数,表示对iterable中的每个元素以及上一次调用后的结果,运用function进行计算,最后返回一个单独的数值。
如计算列表元素的乘积,可用reduce()写为:

l = [1, 2, 3, 4, 5]
product = reduce(lambda x, y: x * y, l) # 1*2*3*4*5 = 120
在数据量较大的情况下,比如机器学习的应用,一般更倾向于使用函数式编程的表示,因为效率更高
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值