Python学习笔记 day4

Python学习笔记 day2

  • 1. 函数的定义
    • a.语法格式
    • b.返回多个值
    • c.函数定义小结
  • 2. 函数参数
    • a.位置参数
    • b.关键字参数
    • c. 默认参数
    • d.可变参数
    • e.参数类型检查
    • f.参数传递原理
  • 3. 变量作用域
    • a.变量获取工具
    • b.全局变量和局部变量
    • c.global 和 nonlocal关键字
  • 4.局部函数
  • 5.为函数提供说明文档
  • 6.函数递归
  • 7.函数高级用法
    • a.使用函数变量
    • b.使用函数作为函数形参
    • c.使用函数作为返回值
  • 8.使用 lambda 表达式
  • 参考资料:

1. 函数的定义

函数就是最基本的一种代码抽象的方式。

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。

函数能提高应用的模块性,和代码的重复利用率。Python提供了许多内建函数,比如print()。但也可以自己创建函数,这被叫做用户自定义函数。

a.语法格式

在使用函数之前必须先定义函数,定义函数的语法格式如下:

def 函数名(形参列表):
    //由零条到多条可执行语句组成的函数
    [return [返回值]]

定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。

Python 声明函数必须使用 def 关键字,对函数语法格式的详细说明如下:

函数名:从语法角度来看,函数名只要是一个合法的标识符即可;从程序的可读性角度来看,函数名应该由一个或多个有意义的单词连缀而成,每个单词的字母全部小写,单词与单词之间使用下画线分隔。

形参列表:用于定义该函数可以接收的参数。形参列表由多个形参名组成,多个形参名之间以英文逗号(,)隔开。一旦在定义函数时指定了形参列表,调用该函数时就必须传入到应的参数值,也就是说,谁调用函数谁负责为形参赋值。

在函数体中多条可执行语句之间有严格的执行顺序,排在函数体前面的语句总是先执行,排在函数体后面的语句总是后执行。

函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”。

b.返回多个值

如果程序需要有多个返回值,则既可将多个值包装成列表之后返回,也可直接返回多个值。如果 Python 函数直接返回多个值,Python 会自动将多个返回值封装成元组。

在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值。
如下程序示范了函数直接返回多个值的情形:

def sum_and_avg(list):
    sum = 0
    count = 0
    for e in list:
        # 如果元素e是数值
        if isinstance(e, int) or isinstance(e, float):
            count += 1
            sum += e
    return sum, sum / count
my_list = [20, 15, 2.8, 'a', 35, 5.9, -1.8]
# 获取sum_and_avg函数返回的多个值,多个返回值被封装成元组
tp = sum_and_avg(my_list) #①
print(tp)

上面程序中的第 9 行代码返回了多个值,当 ① 号代码调用该函数时,该函数返回的多个值将会被自动封装成元组,因此程序看到 tp 是一个包含两个元素(由于被调用函数返回了两个值)的元组。

此外,也可使用 Python 提供的序列解包功能,直接使用多个变量接收函数返回的多个值。例如如下代码:

# 使用序列解包来获取多个返回值
s, avg = sum_and_avg(my_list) #②
print(s)
print(avg)

上面程序中 使用两个变量来接收 sum_and_avg() 函数返回的两个值,这就是利用了 Python 提供的序列解包功能。

c.函数定义小结

定义函数时,需要确定函数名和参数个数;

如果有必要,可以先对参数的数据类型做检查;

函数体内部可以用return随时返回函数结果;

函数执行完毕也没有return语句时,自动return None。

函数可以同时返回多个值,但其实就是一个tuple。

函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。

2. 函数参数

在定义 Python 函数时可定义形参(形式参数的意思),这些形参的值要等到调用时才能确定下来,由函数的调用者负责为形参传入参数值。简单来说,就是谁调用函数,谁负责传入参数值。

Python 函数的参数名不是无意义的,Python 允许在调用函数时通过名字来传入参数值。因此,Python 函数的参数名应该具有更好的语义,这样程序可以立刻明确传入函数的每个参数的含义。

a.位置参数

按照形参位置传入的参数被称为位置参数。如果使用位置参数的方式来传入参数值,则必须严格按照走义函数时指定的顺序来传入参数值。

b.关键字参数

如果根据参数名来传入参数值,则无须遵守定义形参的顺序,这种方式被称为关键字(keyword)参数。例如如下程序:

# 定义一个函数
def girth(width , height):
    print("width: ", width)
    print("height: ", height)
    return 2 * (width + height)
# 传统调用函数的方式,根据位置传入参数
print(girth(3.5, 4.8))
# 根据关键字参数来传入参数
print(girth(width = 3.5, height = 4.8))
# 使用关键字参数时可交换位置
print(girth(height = 4.8, width = 3.5))
# 部分使用关键字参数,部分使用位置参数
print(girth(3.5, height = 4.8))

上面程序定义了一个简单的 girth() 函数,该函数包含 width、height 两个参数,该函数与前面定义的函数并没有任何区别。

接下来在调用该函数时,既可使用传统的根据位置参数来调用,也可根据关键字参数来调用,在使用关键字参数调用时可交换参数的位置,还可混合使用位置参数和关键字参数。如果在调用函数时混合使用关键字参数和位置参数,则关键字参数必须位于位置参数之后。换句话说,在关键字参数之后的只能是关键字参数。例如如下代码是错误的:

# 位置参数必须放在关键字参数之前,下面代码错误
print(girth(width = 3.5, 4.8))
#运行上面代码,将会提示如下错误:
#SyntaxError: positional argument follows keyword argument

c. 默认参数

在某些情况下,程序需要在定义函数时为一个或多个形参指定默认值,这样在调用函数时就可以省略为该形参传入参数值,而是直接使用该形参的默认值。

为形参指定默认值的语法格式如下:

形参名 = 默认值

从上面的语法格式可以看出,形参的默认值紧跟在形参之后,中间以英文“=”隔开。

默认参数可以简化函数的调用。设置默认参数时,有几点要注意:

一是必选参数在前,默认参数在后,否则Python的解释器会报错;
二是如何设置默认参数。

当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。

使用默认参数有什么好处?最大的好处是能降低调用函数的难度。

 定义默认参数要牢记一点:默认参数必须指向不变对象!

默认参数有个最大的坑,演示如下:

先定义一个函数,传入一个list,添加一个END再返回。多次调用时,会有问题:

def add_end(L=[]):
    L.append('END')
    return L

print(add_end([1, 2, 3]))
# [1, 2, 3, 'END']
print(add_end(['x', 'y', 'z']))
# ['x', 'y', 'z', 'END']
# 当你使用默认参数调用时,一开始结果也是对的:
print(add_end())
# ['END']
# 但是,再次调用add_end()时,结果就不对了:
print(add_end())
# ['END', 'END']
print(add_end())
# ['END', 'END', 'END']

默认参数是[],但是函数似乎每次都“记住了”上次添加了’END’后的list。

原因解释如下:

Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

要修改上面的例子,我们可以用None这个不变对象来实现:

def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L
 为什么要设计str、None这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。
 此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。
 我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。

d.可变参数

Python 允许在形参前面添加一个星号(*),这样就意味着该参数可接收多个参数值,多个参数值被当成元组传入。

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

print(calc(1, 2))
# 如果已经有一个list或者tuple,可以在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:
nums = [1, 2, 3]
print(calc(*nums))

参数收集的本质就是一个元组: Python 会将传给可变参数的多个值收集成一个元组。

Python 允许个数可变的形参可以处于形参列表的任意位置(不要求是形参列表的最后一个参数),但 Python 要求一个函数最多只能带一个支持“普通”参数收集的形参。例如如下程序:

# 定义了支持参数收集的函数
def test(*books ,num) :
    print(books)
    # books被当成元组处理
    for b in books :
        print(b)
    print(num)
# 调用test()函数
test("C语言中文网", "Python教程", num = 20)

正如从上面程序中所看到的,test() 函数的第一个参数就是个数可变的形参,由于该参数可接收个数不等的参数值,因此如果需要给后面的参数传入参数值,则必须使用关键字参数,否则程序会把所传入的多个值都当成是传给 books 参数的。

加了两个星号 ** 的参数会以字典的形式导入。
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

def printinfo( arg1, **vardict ):
   "打印任何传入的参数"
     print (arg1)
   print (vardict)
 
# 调用printinfo 函数
printinfo(1, a=2,b=3)

输出结果为

1
{'a': 2, 'b': 3}

声明函数时,参数中星号 * 可以单独出现,如果 单独出现星号, * 后的参数必须用关键字传入。

>>> def f(a,b,*,c):
...     return a+b+c
... 
>>> f(1,2,3)   # 报错
Traceback (most recent call last):
  File "", line 1, in 
TypeError: f() takes 2 positional arguments but 3 were given
>>> f(1,2,c=3) # 正常
6
>>>
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。
但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

e.参数类型检查

调用内置函数的时候,如果传入的参数数量不对,或参数类型不能被函数所接受,会报TypeError的错误。
自定义函数时,作为一个完善的函数,也要有参数检查功能。

def my_abs(x):
    if not isinstance(x, (int, float)):    #检查参数类型
        raise TypeError('bad operand type')    #如果参数不符合要求,抛出异常
    if x >= 0:
        return x
    else:
        return -x

f.参数传递原理

Python 的参数值是如何传入函数的呢?这是由 Python 函数的参数传递机制来控制的。Python 中函数的参数传递机制都是“值传递”。所谓值传递,就是将实际参数值的副本(复制品)传入函数,而参数本身不会受到任何影响。

如果需要让函数修改某些数据,则可以通过把这些数据包装成列表、字典等可变对象,然后把列表、字典等可变对象作为参数传入函数,在函数中通过列表、字典的方法修改它们,这样才能改变这些数据。

3. 变量作用域

Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。

变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python的作用域一共有4种,分别是:

L (Local) 局部作用域
E (Enclosing) 闭包函数外的函数中
G (Global) 全局作用域
B (Built-in) 内置作用域(内置函数所在模块的范围)

以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。

Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域。 其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问。

每个函数在执行时,系统都会为该函数分配一块“临时内存空间”,所有的局部变量都被保存在这块临时内存空间内。当函数执行完成后,这块内存空间就被释放了,这些局部变量也就失效了,因此离开函数之后就不能再访问局部变量了。

全局变量意味着它们可以在所有函数内被访问。

不管是在函数的局部范围内还是在全局范围内,都可能存在多个变量,每个变量“持有”该变量的值。从这个角度来看,不管是局部范围还是全局范围,这些变量和它们的值就像一个“看不见”的字典,其中变量名就是字典的 key,变量值就是字典的 value。

a.变量获取工具

Python 提供了如下三个工具函数来获取指定范围内的“变量字典”:
globals():该函数返回全局范围内所有变量组成的“变量字典”。
locals():该函数返回当前局部范围内所有变量组成的“变量字典”。
vars(object):获取在指定对象范围内所有变量组成的“变量字典”。如果不传入object 参数,vars() 和 locals() 的作用完全相同。

globals() 和 locals() 看似完全不同,但它们实际上也是有联系的,关于这两个函数的区别和联系大致有以下两点:
locals() 总是获取当前局部范围内所有变量组成的“变量字典”,因此,如果在全局范围内(在函数之外)调用 locals() 函数,同样会获取全局范围内所有变量组成的“变量字典”;而 globals() 无论在哪里执行,总是获取全局范围内所有变量组成的“变量字典”。
一般来说,使用 locals() 和 globals() 获取的“变量字典”只应该被访问,不应该被修改。但实际上,不管是使用 globals() 还是使用 locals() 获取的全局范围内的“变量字典”,都可以被修改,而这种修改会真正改变全局变量本身:但通过 locals() 获取的局部范围内的“变量字典”,即使对它修改也不会影响局部变量。

下面程序示范了如何使用 locals()、globals() 函数访问局部范围和全局范围内的“变量字典”:

def test ():
    age = 20
    # 直接访问age局部变量
    print(age) # 输出20
    # 访问函数局部范围的“变量数组”
    print(locals()) # {'age': 20}
    # 通过函数局部范围的“变量数组”访问age变量
    print(locals()['age']) # 20
    # 通过locals函数局部范围的“变量数组”改变age变量的值
    locals()['age'] = 12
    # 再次访问age变量的值
    print('xxx', age) # 依然输出20
    # 通过globals函数修改x全局变量
    globals()['x'] = 19
x = 5
y = 20
print(globals()) # {..., 'x': 5, 'y': 20}
# 在全局访问内使用locals函数,访问的是全局变量的“变量数组”
print(locals()) # {..., 'x': 5, 'y': 20}
# 直接访问x全局变量
print(x) # 5
# 通过全局变量的“变量数组”访问x全局变量
print(globals()['x']) # 5
# 通过全局变量的“变量数组”对x全局变量赋值
globals()['x'] = 39
print(x) # 输出39
# 在全局范围内使用locals函数对x全局变量赋值
locals()['x'] = 99
print(x) # 输出99

b.全局变量和局部变量

定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。

**局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。**如下实例:

total = 0 # 这是一个全局变量
# 可写函数说明
def sum( arg1, arg2 ):
    #返回2个参数的和."
    total = arg1 + arg2 # total在这里是局部变量.
    print ("函数内是局部变量 : ", total)
    return total
 
#调用sum函数
sum( 10, 20 )
print ("函数外是全局变量 : ", total)

以上实例输出结果:

以上实例输出结果:

函数内是局部变量 :  30
函数外是全局变量 :  0

c.global 和 nonlocal关键字

全局变量默认可以在所有函数内被访问,但如果在函数中定义了与全局变量同名的变量,此时就会发生局部变量遮蔽(hide)全局变量的情形。
例如如下程序:

name = 'Charlie'
def test ():
    # 直接访问name全局变量
    print(name) # Charlie
    name = '孙悟空'
test()
print(name)

上面程序中,第 4 行直接访问 name 变量,这是允许的,此时程序将会输出 Charlie。如果在此之后增加如下一行代码:
name = ‘孙悟空’

运行该程序,将会看到如下错误:

UnboundLocalError : local variable ‘name’ referenced before assignment

该错误提示粗体字代码所访问的 name 变量还未定义。这是什么原因呢?这正是由于程序在 test() 函数中增加了“name=‘孙悟空’”一行代码造成的。

Python 语法规定,在函数内部对不存在的变量赋值时,默认就是重新定义新的局部变量。因此这行代码相当于重新定义了 name 局部变量,这样 name 全局变量就被遮蔽了,所以程序会报错。

当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了。

global 用于声明访问全局变量,而 nonlocal 用于声明访问当前函数所在函数内的局部变量。

在python的函数中和全局同名的变量,如果你有修改变量的值就会变成局部变量。如果确定要引用全局变量,并且要对它修改,必须加上global关键字。

以下实例修改全局变量 num:

num = 1
def fun1():
    global num   # 需要使用 global 关键字声明
    print(num)   # 1
    num = 123    #123
    print(num)   #123
fun1()
print(num)

增加了“global name”声明之后,程序会把 name 变量当成全局变量,这意味着 test() 函数后面对 name 赋值的语句只是对全局变量赋值,而不是重新定义局部变量。

**如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了,**如下实例:

def outer():
    num = 10
    def inner():
        nonlocal num   # nonlocal关键字声明
        num = 100
        print(num)    #100
    inner()
    print(num)      #100
outer()

4.局部函数

Python 还支持在函数体内定义函数,这种被放在函数体内定义的函数称为局部函数。

在默认情况下,局部函数对外部是隐藏的,局部函数只能在其封闭(enclosing)函数内有效,其封闭函数也可以返回局部函数,以便程序在其他作用域中使用局部函数。

def get_math_func(type, nn) :
    # 定义一个计算平方的局部函数
    def square(n) :  # ①
        return n * n
    # 定义一个计算立方的局部函数
    def cube(n) :  # ②
        return n * n * n
    # 定义一个计算阶乘的局部函数
    def factorial(n) :   # ③
        result = 1
        for index in range(2, n + 1) :
            result *= index
        return result
    # 调用局部函数
    if type == "square" :
        return square(nn)
    elif type == "cube":
        return cube(nn)
    else:
        return factorial(nn)
print(get_math_func("square", 3)) # 输出9
print(get_math_func("cube", 3)) # 输出27
print(get_math_func("", 3)) # 输出6

上面程序中第一行粗体字代码定义了 get_math_func() 函数,接下来程序的 ①、②、③ 号代码定义了 3 个局部函数,而 get_math_func() 函数则根据参数选择调用不同的局部函数。

如果封闭函数没有返回局部函数,那么局部函数只能在封闭函数内部调用,如上面程序所示。另外,还会出现一种情况,如果封闭函数将局部函数返回,且程序使用变量保存了封闭函数的返回值,那么这些局部函数的作用域就会被扩大。因此程序完全可以自由地调用它们,就像它们都是全局函数一样。

局部函数内的变量也会遮蔽它所在函数内的局部变量,请看如下代码:

def foo ():
    # 局部变量name
    name = 'Charlie'
    def bar ():
        # 访问bar函数所在的foo函数的name局部变量
        print(name) # Charlie
        name = '孙悟空'
    bar()
foo()

运行上面代码,会导致如下错误:

UnboundLocalError: local variable 'name' referenced before assignment

该错误是由局部变量遮蔽局部变量导致的,在 bar() 函数中定义的 name 局部变量遮蔽了它所在 foo() 函数内的 name 局部变量,因此导致程序报错。

为了声明 bar() 函数中的“name=‘孙悟空’”赋值语句不是定义新的局部变量,只是访问它所在 foo() 函数内的 name 局部变量,可以通过 nonlocal 语句即可声明访问赋值语句只是访问该函数所在函数内的局部变量。将上面程序改为如下形式:

def foo ():
    # 局部变量name
    name = 'Charlie'
    def bar ():
     nonlocal name
        # 访问bar函数所在的foo函数的name局部变量
        print(name) # Charlie
        name = '孙悟空'
    bar()
foo()

增加上面程序中第 5 行之后,接下来 bar() 函数中的“name=‘孙悟空’”就不再是定义新的局部变量,而是访问它所在函数(foo() 函数)内的 name 局部变量。

5.为函数提供说明文档

前面介绍过可以使用 Python 内置的 help() 函数查看其他函数的帮助文档,我们也经常通过 help() 函数查看指定函数的帮助信息,这对于 Python 开发者来说非常重要。

我们还可以为函数编写说明文档,只要把一段字符串放在函数声明之后、函数体之前,这段字符串将被作为函数的部分,这个文档就是函数的说明文档。

程序既可通过 help() 函数查看函数的说明文档( help() 函数查看的就是程序单元的 doc 属性值),也可通过函数的 doc 属性访问函数的说明文档。

以下实例展示了 help 的使用方法:

help('sys')             # 查看 sys 模块的帮助
help('str')             # 查看 str 数据类型的帮助
a = [1,2,3]
help(a)                 # 查看列表 list 帮助信息
help(a.append)      # 显示list的append方法的帮助

通过 doc 属性访问函数的说明文档

import sys
print(sys.__doc__ )

6.函数递归

在一个函数体内调用它自身,被称为函数递归。函数递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。

例如有如下数学题。己知有一个数列:f(0) = 1,f(1) = 4,f(n + 2) = 2*f(n+ 1) +f(n),其中 n 是大于 0 的整数,求 f(10) 的值。这道题可以使用递归来求得。下面程序将定义一个 fn() 函数,用于计算 f(10) 的值。

def fn(n) :
    if n == 0 :
        return 1
    elif n == 1 :
        return 4
    else :
        # 函数中调用它自身,就是函数递归
        return 2 * fn(n - 1) + fn(n - 2)
# 输出fn(10)的结果
print("fn(10)的结果是:", fn(10))

当一个函数不断地调用它自身时,必须在某个时刻函数的返回值是确定的,即不再调用它自身:否则,这种递归就变成了无穷递归,类似于死循环。因此,在定义递归函数时有一条最重要的规定: 递归一定要向已知方向进行。

例如,如果把上面数学题改为如此。己知有一个数列:f(20)=1,f(21)=4,f(n + 2)=2*f(n+1)+f(n),其中 n 是大于 0 的整数,求 f(10) 的值。那么 f(10) 的函数体应该改为如下形式:

def fn(n) :
    if n == 20 :
        return 1
    elif n == 21 :
        return 4
    else :
        # 函数中调用它自身,就是函数递归
        return fn(n + 2) - 2*fn(n + 1)

从上面的 fn() 函数来看,当程序要计算 fn(10) 的值时,fn(10) 等于 fn(12)-2fn(11),而 fn(11) 等于 fn(13)-2fn(12)……依此类推,直到 fn(19) 等于 fn(21)-2fn(20),此时就可以得到 fn(19) 的值,然后依次反算到 fn(10) 的值。这就是递归的重要规则:对于求 fn(10) 而言,如果 fn(0) 和 fn(1) 是已知的,则应该采用 fn(n)=2fn(n-1)+fn(n-2) 的形式递归,因为小的一端已知;如果 fn(20) 和 fn(21) 是已知的,则应该采用 fn(n)=fn(n+2)-2*fn(n+1) 的形式递归,因为大的一端已知。

递归是非常有用的,例如程序希望遍历某个路径下的所有文件,但这个路径下的文件夹的深度是未知的,那么就可以使用递归来实现这个需求。系统可定义一个函数,该函数接收一个文件路径作为参数,该函数可遍历出当前路径下的所有文件和文件路径,即在该函数的函数体中再次调用函数自身来处理该路径下的所有文件路径。

7.函数高级用法

函数本身也是一个对象,函数既可用于赋值,也可用作其他函数的参数,还可作为其他函数的返回值。

a.使用函数变量

Python 的函数也是一种值:所有函数都是 function 对象,这意味着可以把函数本身赋值给变量,就像把整数、浮点数、列表、元组赋值给变量一样。

当把函数赋值给变量之后,接下来程序也可通过该变量来调用函数。例如如下代码:

# 定义一个计算乘方的函数
def pow(base, exponent) :
    result = 1
    for i in range(1, exponent + 1) :
        result *= base
    return result
# 将pow函数赋值给my_fun,则my_fun可当成pow使用
my_fun = pow
print(my_fun(3 , 4)) # 输出81
# 定义一个计算面积的函数
def area(width, height) :
    return width * height
# 将area函数赋值给my_fun,则my_fun可当成area使用
my_fun = area
print(my_fun(3, 4)) # 输出12

从上面代码可以看出,程序依次将 pow()、area() 函数赋值给 my_fun 变量,接下来即可通过 my_fun 变量分别调用 pow()、area() 函数。

通过对 my_fun 变量赋值不同的函数,可以让 my_fun 在不同的时间指向不同的函数,从而让程序更加灵活。由此可见,使用函数变量的好处是让程序更加灵活。

除此之外,程序还可使用函数作为另一个函数的形参和(或)返回值。

b.使用函数作为函数形参

有时候需要定义一个函数,该函数的大部分计算逻辑都能确定,但某些处理逻辑暂时无法确定,这意昧着某些程序代码需要动态改变,如果希望调用函数时能动态传入这些代码,那么就需要在函数中定义函数形参,这样即可在调用该函数时传入不同的函数作为参数,从而动态改变这段代码。

Python 支持像使用其他参数一样使用函数参数,例如如下程序:

# 定义函数类型的形参,其中fn是一个函数
def map(data, fn) :   
    result = []
    # 遍历data列表中每个元素,并用fn函数对每个元素进行计算
    # 然后将计算结果作为新数组的元素
    for e in data :
        result.append(fn(e))
    return result
# 定义一个计算平方的函数
def square(n) :
    return n * n
# 定义一个计算立方的函数
def cube(n) :
    return n * n * n
# 定义一个计算阶乘的函数
def factorial(n) :
    result = 1
    for index in range(2, n + 1) :
        result *= index
    return result
data = [3 , 4 , 9 , 5, 8]
print("原数据: ", data)
# 下面程序代码3次调用map()函数,每次调用时传入不同的函数
print("计算数组元素的平方")
print(map(data , square))
print("计算数组元素的立方")
print(map(data , cube))
print("计算数组元素的阶乘")
print(map(data , factorial))

上面程序中定义了一个 map() 函数,该函数的第二个参数是一个函数类型的参数,这意味着每次调用函数时可以动态传入一个函数,随着实际传入函数的改变,就可以动态改变 map() 函数中的部分计算代码。

Pthon 3 本身也提供了一个 map() 函数,Python 3 内置的 map() 函数的功能和此处定义的 map() 函数功能类似,但更加强大。

接下来的三行粗体字代码调用了 map() 函数三次,三次调用依次传入了 square、cube、factorial 函数作为参数,这样每次调用 map() 函数时实际的执行代码是有区别的。

编译、运行上面程序,可以看到如下输出结果:

原数据:  [3, 4, 9, 5, 8]
计算数组元素的平方
[9, 16, 81, 25, 64]
计算数组元素的立方
[27, 64, 729, 125, 512]
计算数组元素的阶乘
[6, 24, 362880, 120, 40320]

从上面介绍不难看出,通过使用函数作为参数可以在调用函数时动态传入函数,实际上就可以动态改变被调用函数的部分代码。

在程序最后添加如下一行:

# 获取map的类型
print(type(map))

运行上面代码,将会看到如下输出结果:


c.使用函数作为返回值

Python 还支持使用函数作为其他函数的返回值。例如如下程序:

def get_math_func(type) :
    # 定义一个计算平方的局部函数
    def square(n) :  # ①
        return n * n
    # 定义一个计算立方的局部函数
    def cube(n) :  # ②
        return n * n * n
    # 定义一个计算阶乘的局部函数
    def factorial(n) :   # ③
        result = 1
        for index in range(2 , n + 1):
            result *= index
        return result
    # 返回局部函数
    if type == "square" :
        return square
    if type == "cube" :
        return cube
    else:
        return factorial
# 调用get_math_func(),程序返回一个嵌套函数
math_func = get_math_func("cube") # 得到cube函数
print(math_func(5)) # 输出125
math_func = get_math_func("square") # 得到square函数
print(math_func(5)) # 输出25
math_func = get_math_func("other") # 得到factorial函数
print(math_func(5)) # 输出120

程序中,定义了一个 get_math_func() 函数,该函数将返回另一个函数。接下来在 get_math_func() 函数体内的 ①、②、③ 号粗体字代码分别定义了三个局部函数,最后 get_math_func() 函数会根据所传入的参数,使用这三个局部函数之一作为返回值。

在定义了会返回函数的 get_math_func() 函数之后,接下来程序调用 get_math_func() 函数时即可返回所需的函数,如上面程序中代码所示。

8.使用 lambda 表达式

lambda 表达式的本质就是匿名的、单行函数体的函数。

lambda 表达式的语法格式如下:

lambda [parameter_list] : 表达式

从上面的语法格式可以看出 lambda 表达式的几个要点:
lambda 表达式必须使用 lambda 关键字定义。
在 lambda 关键字之后、冒号左边的是参数列表,可以没有参数,也可以有多个参数。如果有多个参数,则需要用逗号隔开,冒号右边是该 lambda 表达式的返回值。

实际上,lambda 表达式的本质就是匿名的、单行函数体的函数。因此,lambda 表达式可以写成函数的形式。例如,对于如下 lambda 表达式:

lambda x , y:x + y

可改写为如下函数形式:

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

上面定义函数时使用了简化语法:当函数体只有一行代码时,可以直接把函数体的代码放在与函数头同一行。

lambda 只是一个表达式,函数体比 def 简单很多。
lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。

总体来说,函数比 lambda 表达式的适应性更强,lambda 表达式只能创建简单的函数对象(它只适合函数体为单行的情形)。但 lambda 表达式依然有如下两个用途:
对于单行函数,使用 lambda 表达式可以省去定义函数的过程,让代码更加简洁。
对于不需要多次复用的函数,使用 lambda 表达式可以在用完之后立即释放,提高了性能。

使用范例:

def get_math_func(type) :
    result=1
    # 该函数返回的是Lambda表达式
    if type == 'square':
        return lambda n: n * n  # ①
    elif type == 'cube':
        return lambda n: n * n * n  # ②
    else:
        return lambda n: (1 + n) * n / 2 # ③
# 调用get_math_func(),程序返回一个嵌套函数
math_func = get_math_func("cube")
print(math_func(5)) # 输出125
math_func = get_math_func("square")
print(math_func(5)) # 输出25
math_func = get_math_func("other")
print(math_func(5)) # 输出15.0

参考资料:

Python 3 |菜鸟教程 http://www.runoob.com/python3/python3-tutorial.html
廖雪峰 Python教程 https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000
C语言中文网 http://c.biancheng.net/python/function/

你可能感兴趣的:(学习笔记)