函数简介(function)

函数也是一个对象 , 对象是内存中专门用来存储数据的一块区域 , 函数可以用来保存一些可执行的代码,并且可以在需要时,对这些语句进行多次的调用 . 函数中保存的代码不会立即执行,需要调用函数代码才会执行 .

1
2
3
4
5
6
7
# 创建函数
def 函数名([形参1,形参2,...形参n]) :
代码块
# 函数名必须要符号标识符的规范(可以包含字母、数字、下划线、但是不能以数字开头)

# 调用函数
函数对象()

定义函数一般都是要实现某种功能的 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 比如有如下三行代码,这三行代码是一个完整的功能
# print('Hello')
# print('你好')
# print('再见')
# 于是可以定义一个函数
def fn():
print("Hello")
print('你好')
print("再见")

print(fn) # <function fn at 0x03D2B618>
print(type(fn)) # <class 'function'>

# fn 是函数对象 fn()是调用函数
# print 是函数对象 print()是调用函数
fn() # 调用函数fn

函数的参数

在定义函数时,可以在函数名后的()中定义数量不等的形参,多个形参之间使用,隔开 .

  • 形参(形式参数),定义形参就相当于在函数内部声明了变量,但是并不赋值
  • 实参(实际参数) , 如果函数定义时,指定了形参,那么在调用函数时也必须传递实参,实参将会赋值给对应的形参,简单来说,有几个形参就得传几个实参
1
2
3
4
5
6
7
# 定义函数时指定形参
def fn2(a , b) :
print(a,"+",b,"=",a + b)

# 调用函数时,来传递实参
fn2(10,20)
fn2(123,456)

定义形参时,可以为形参指定默认值 , 指定了默认值以后,如果用户传递了参数则默认值没有任何作用 , 如果用户没有传递,则默认值就会生效 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def fn(a = 5 , b = 10 , c = 20):
print('a =',a)
print('b =',b)
print('c =',c)

fn(1 , 2 , 3)
# a = 1
# b = 2
# c = 3
fn(1,2)
# a = 1
# b = 2
# c = 20
fn()
# a = 5
# b = 10
# c = 20

实参的传递方式有两种.

  1. 位置参数

    位置参数就是将对应位置的实参赋值给对应位置的形参 , 第一个实参赋值给第一个形参,第二个实参赋值给第二个形参 . . .

    例如: fn(1,2,3)

  2. 关键字参数

    关键字参数,可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数 .

    例如: fn(b=1 , c=2 , a=3) ; print('hello' , end='')end就是关键字参数.

位置参数和关键字参数可以混合使用 , 混合使用关键字和位置参数时,必须将位置参数写到前面 .

Python函数在调用时,解析器不会检查实参的类型 , 实参可以传递任意类型的对象 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def fn(a):
print("a =",a)
b = 123
fn(b) # a = 123
b = True
fn(b) # a = True
b = 'hello'
fn(b) # a = hello
b = None
fn(b) # a = None
b = [1,2,3]
fn(b) # a = [1, 2, 3]

def fn3(a , b):
print(a+b)

# fn3(123,"456") # unsupported operand type(s) for +: 'int' and 'str'

在函数中对形参进行重新赋值,不会影响其他的变量 . 如果形参执行的是一个对象,当我们通过形参去修改对象时 , 会影响到所有指向该对象的变量 .

1
2
3
4
5
6
7
def fn4(a):
a = 20
print('a =',a,id(a))
b = 10
fn4(b) # a = 20 140724655495424
print('b =',b,id(b)) # b = 10 140724655495104
# 可以看到 变量b与a是不同的变量
1
2
3
4
5
6
7
def fn4(a):
a[0] = 20
print('a =',a,id(a))
b = [1,2,3]
fn4(b) # a = [20, 2, 3] 2536012700864
print('b =',b,id(b)) # b = [20, 2, 3] 2536012700864
# 由于函数中修改了对象 , 所以影响到了列表b , 变量a 和b实际上是同一个对象

为了避免函数对对象进行修改, 可以使用切片的方式传入一个副本 , 也可以使用copy()方法来传入浅复制对象.如:fn4(b.copy())fn4(b[:])

为了使函数功能更加灵活 , 可以在定义函数时使用不定长参数 . 即不限制参数的个数. 定义函数时 , 在形参前面加*即表示该形参为不定长参数. 此时传入的参数将会被接收为一个元组 . 此过程称为参数的装包.

1
2
3
4
def fn(*a):
print("a =",a,type(a))
# *a会接受所有的位置实参,并且会将这些实参统一保存到一个元组中(装包)
fn(1,2,'hello',True) # a = (1, 2, 'hello', True) <class 'tuple'>

利用不定长参数 , 可以实现求任意个数字的和.

1
2
3
4
5
6
7
8
9
10
# 定义一个函数,可以求任意个数字的和
def sum(*nums):
# 此时 , nums是一个元组
# 定义一个变量,来保存结果
result = 0
# 遍历元组,并将元组中的数进行累加
for n in nums :
result += n
print(result)
sum(123,456,789,10,20,30,40) # 1468

不定长参数在使用过程中需要注意以下几点:

  • 带星号的形参只能有一个 , 带星号的参数,可以和其他参数配合使用 .

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def fn2(a,b,*c):
    print('a =',a)
    print('b =',b)
    print('c =',c)

    fn2(1,2,3,4,5)
    # a = 1
    # b = 2
    # c = (3, 4, 5)
  • 可变参数不是必须写在最后,但是注意,带*的参数后的所有参数,必须以关键字参数的形式传递

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def fn2(a,*b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)

    #fn2(1,2,3,4,5) # fn2() missing 1 required keyword-only argument: 'c'
    fn2(1,2,3,4,c = 5)
    # a = 1
    # b = (2, 3, 4)
    # c = 5
  • 如果在形参的开头直接写一个*,则要求我们的所有的参数必须以关键字参数的形式传递

    1
    2
    3
    4
    5
    def fn2(*,a,b,c):
    print('a =',a)
    print('b =',b)
    print('c =',c)
    fn2(a=3,b=4,c=5)
  • *形参只能接收位置参数,而不能接收关键字参数

    1
    2
    3
    4
    5
    def fn3(*a) :
    print('a =',a)

    # fn3(a=2) # fn3() got an unexpected keyword argument 'a'
    fn3([1,2],3) # a = ([1, 2], 3)

要想接收不定长的关键字参数, 可以在参数前加** , **形参可以接收其他的关键字参数,它会将这些参数统一保存到一个字典中 , 字典的key就是参数的名字,字典的value就是参数的值 . **形参只能有一个,并且必须写在所有参数的最后 .

1
2
3
4
5
6
7
8
9
def fn3(b,c,**a) :
print('a =',a,type(a))
print('b =',b)
print('c =',c)

fn3(b=1,d=2,c=3,e=10,f=20)
# a = {'d': 2, 'e': 10, 'f': 20} <class 'dict'>
# b = 1
# c = 3

传递实参时,也可以在序列类型的参数前添加星号,这样他会自动将序列中的元素依次作为参数传递 . 此过程称为参数的解包(拆包) , 这里要求序列中元素的个数必须和形参的个数的一致 .

1
2
3
4
5
6
7
8
9
10
11
def fn4(a,b,c):
print('a =',a)
print('b =',b)
print('c =',c)

# 创建一个元组
t = (10,20,30)
fn4(*t) # 把t解包分别传递给a,b,c
# a = 10
# b = 20
# c = 30

类似地 , 可以在传递实参时 , 通过**实现对字典的解包.

1
2
3
4
5
6
7
8
9
10
11
12
13
def fn4(a,b,c):
print('a =',a)
print('b =',b)
print('c =',c)
d = {'a':100,'b':200,'c':300}
fn4(**d)
# a = 100
# b = 200
# c = 300

d = {'a':100,'b':200,'d':300}
# fn4(**d) # fn4() got an unexpected keyword argument 'd'
# 字典中的key必须是形参关键字.

返回值

返回值就是函数执行以后返回的结果 , 可以通过 return 来指定函数的返回值 . return 后边跟什么值,函数就会返回什么值 . return 后边可以跟任意的对象,返回值甚至可以是一个函数 .如果仅仅写一个return 或者 不写return,则相当于return None . 在函数中,return下方的代码都不会执行,return 一旦执行函数自动结束 .

体会 return breakcontinue的区别:

1
2
3
4
5
6
7
8
9
10
def fn4() :
for i in range(5):
if i == 3 :
# break 用来退出当前循环
# continue 用来跳过当次循环
# return 用来结束函数
print(i)
print('循环执行完毕!')

fn4()

通过下述代码 , 体会函数名称 与 函数调用之间的区别.

1
2
3
4
5
def fn5():
return 10

print(fn5) # fn5是函数对象,打印fn5实际是在打印函数对象 <function fn5 at 0x05771BB8>
print(fn5()) # fn5()是在调用函数,打印fn5()实际上是在打印fn5()函数的返回值 10

文档字符串

help()是Python中的内置函数 , 通过help()函数可以查询python中的函数的用法 .

语法:help(函数对象)

1
help(print) # 获取print()函数的使用说明

执行结果:

1
2
3
4
5
6
7
8
9
10
11
Help on built-in function print in module builtins:

print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.

在定义函数时,可以在函数内部编写文档字符串,文档字符串就是函数的说明 , 当我们编写了文档字符串时,就可以通过help()函数来查看函数的说明 , 文档字符串非常简单,其实直接在函数的第一行写一个字符串就是文档字符串 .

1
2
3
4
5
6
7
8
9
10
11
12
13
def fn(a,b,c):
'''
这是一个文档字符串的示例

函数的作用:。。。。。
函数的参数:
a,作用,类型,默认值。。。。
b,作用,类型,默认值。。。。
c,作用,类型,默认值。。。。
'''
return 10

help(fn)

执行结果 :

1
2
3
4
5
6
7
8
9
10
Help on function fn in module __main__:

fn(a, b, c)
这是一个文档字符串的示例

函数的作用:。。。。。
函数的参数:
a,作用,类型,默认值。。。。
b,作用,类型,默认值。。。。
c,作用,类型,默认值。。。。

可以通过形参:类型的方式来在帮助文档中显示参数类型 , 可以通过->来指定返回值类型 , 具体参考示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 帮助文档中会显示所需的参数类型和返回值类型 , 其中c的参数类型为str,并赋予了默认值'hello'
def fn(a:int,b:bool,c:str='hello') -> int:
'''
这是一个文档字符串的示例

函数的作用:。。。。。
函数的参数:
a,作用,类型,默认值。。。。
b,作用,类型,默认值。。。。
c,作用,类型,默认值。。。。
'''
return 10

help(fn)

执行结果:

1
2
3
4
5
6
7
8
9
10
Help on function fn in module __main__:

fn(a: int, b: bool, c: str = 'hello') -> int
这是一个文档字符串的示例

函数的作用:。。。。。
函数的参数:
a,作用,类型,默认值。。。。
b,作用,类型,默认值。。。。
c,作用,类型,默认值。。。。

作用域与命名空间

作用域(scope)

作用域指的是变量生效的区域 , 在Python中一共有两种作用域 .

  1. 全局作用域
    • 全局作用域在程序执行时创建,在程序执行结束时销毁
    • 所有函数以外的区域都是全局作用域
    • 在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置被访问
  2. 函数作用域
    • 函数作用域在函数调用时创建,在调用结束时销毁
    • 函数每调用一次就会产生一个新的函数作用域
    • 在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问

当我们使用变量时,会优先在当前作用域中寻找该变量,如果有则使用,如果没有则继续去上一级作用域中寻找,如果有则使用,如果依然没有则继续去上一级作用域中寻找,以此类推 .直到找到全局作用域,依然没有找到,则会抛出异常 : NameError: name ‘xxx’ is not defined

1
2
3
4
5
6
7
8
9
10
b = 20 # 全局变量

def fn():
a = 10 # a定义在了函数内部,所以他的作用域就是函数内部,函数外部无法访问
print('函数内部:','a =',a)
print('函数内部:','b =',b)

fn()
# 函数内部: a = 10
# 函数内部: b = 20

如果希望在函数内部修改全局变量,则需要使用global关键字,来声明变量 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = 20

def fn3():
# a = 10 # 在函数中为变量赋值时,默认都是为局部变量赋值
# 如果希望在函数内部修改全局变量,则需要使用global关键字,来声明变量
global a # 声明在函数内部的使用a是全局变量,此时再去修改a时,就是在修改全局的a
a = 10 # 修改全局变量
print('函数内部:','a =',a)

fn3()
print('函数外部:','a =',a)

# 函数内部: a = 10
# 函数外部: a = 10

命名空间(namespace)

命名空间指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中 , 每一个作用域都会有一个它对应的命名空间 , 全局命名空间,用来保存全局变量。函数命名空间用来保存函数中的变量 . 命名空间实际上就是一个字典,是一个专门用来存储变量的字典 .

locals()用来获取当前作用域的命名空间 . 如果在全局作用域中调用locals()则获取全局命名空间,如果在函数作用域中调用locals()则获取函数命名空间 . 返回的是一个字典 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def fn2():
def fn3():
print('fn3中:','a =',a)
fn3()

# fn2()

a = 20

def fn3():
# a = 10 # 在函数中为变量赋值时,默认都是为局部变量赋值
# 如果希望在函数内部修改全局变量,则需要使用global关键字,来声明变量
global a # 声明在函数内部的使用a是全局变量,此时再去修改a时,就是在修改全局的a
a = 10 # 修改全局变量
print('函数内部:','a =',a)

scope = locals() # 当前命名空间
print(scope)
print(type(scope))

执行结果:

1
2
3
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000024A09918310>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'test.py', '__cached__': None, 'fn2': <function fn2 at 0x0000024A099D5160>, 'a': 20, 'fn3': <function fn3 at 0x0000024A099D51F0>, 'scope': {...}}
<class 'dict'>
# 可以看到 , 变量a , 函数fn3,fn2 包括scope自己都在这个字典中.

既然命名空间就是一个字典 , 那么也可以使用操作字典的方式来添加变量 , 但是一般不这么做.

1
2
scope = locals()
scope['c'] = 1000 # 向字典中添加key-value就相当于在全局中创建了一个变量(一般不建议这么做)

类似地 , 使用globals()函数可以在任意位置获得全局命名空间 .

1
2
3
4
5
6
7
8
def fn4():
a = 10
global_scope = globals()
global_scope['a'] = 30
print(a) # 10

fn4()
print(a) # 30

递归

递归简单理解就是自己去引用自己!

从前有座山,山里有座庙,庙里有个老和尚讲故事,讲的是:

从前有座山,山里有座庙,庙里有个老和尚讲故事,讲的是:

从前有座山,山里有座庙,庙里有个老和尚讲故事,讲的是:

从前有座山,山里有座庙,庙里有个老和尚讲故事,讲的是:

. . .

递归式函数,在函数中自己调用自己!

递归是解决问题的一种方式,它和循环很像 , 它的整体思想是,将一个大问题分解为一个个的小问题,直到问题无法分解时,再去解决问题 .

递归式函数的两个要件:

  1. 基线条件

    问题可以被分解为的最小问题,当满足基线条件时,递归就不在执行了

  2. 递归条件

    将问题继续分解的条件

1
2
3
4
5
6
7
8
9
10
11
12
13
def factorial(n):
'''
该函数用来求任意数的阶乘

参数:
n 要求阶乘的数字
'''
# 基线条件 判断n是否为1,如果为1则此时不能再继续递归
if n == 1 :
# 1的阶乘就是1,直接返回1
return 1
# 递归条件
return n * factorial(n-1)

递归和循环类似,基本是可以互相代替的,循环编写起来比较容易,阅读起来稍难 , 递归编写起来难,但是方便阅读 .

高阶函数

高阶函数至少要符合以下两个特点中的一个.

  1. 接收一个或多个函数作为参数
  2. 将函数作为返回值返回

通过把函数作为参数传递的方式 , 可以使函数更加灵活 . 下面是利用高阶函数来实现筛选列表元素的功能 , 体会这么做的好处.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 创建一个列表
l = [1,2,3,4,5,6,7,8,9,10]

# 定义一个函数,用来检查一个任意的数字是否是偶数
def fn2(i) :
return i % 2 == 0 # 是偶数则返回True , 否则返回False
# 这个函数用来检查指定的数字是否大于5
def fn3(i):
return i > 5
# 定义一个函数, 检查指定的元素是不是3的倍数
def fn4(i):
return i % 3 == 0

def fn(func , lst) :

'''
fn()函数可以将指定列表中的所有偶数获取出来,并保存到一个新列表中返回

参数:
lst:要进行筛选的列表
'''
# 创建一个新列表
new_list = []
# 对列表进行筛选
for n in lst :
if func(n) :
new_list.append(n)
# 返回新列表
return new_list


# 此时调用函数fn时 , fn的功能会随着删选函数的改变而改变.
print(fn(fn3,l)) # [6, 7, 8, 9, 10]
print(fn(fn4,l)) # [3, 6, 9]
print(fn(fn2,l)) # [2, 4, 6, 8, 10]

Python中提供了内置函数filter()可以从序列中过滤出符合条件的元素,保存到一个新的序列中 . 该函数就是一个参数为函数的高阶函数 , 其参数有:

  1. 函数,根据该函数来过滤序列(可迭代的结构)
  2. 需要过滤的序列(可迭代的结构)

该函数的返回值是一个filter对象. 可以通过list()函数将其转换为列表.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 创建一个列表
l = [1,2,3,4,5,6,7,8,9,10]

# 定义一个函数,用来检查一个任意的数字是否是偶数
def fn2(i) :
return i % 2 == 0 # 是偶数则返回True , 否则返回False
# 这个函数用来检查指定的数字是否大于5
def fn3(i):
return i > 5
# 定义一个函数, 检查指定的元素是不是3的倍数
def fn4(i):
return i % 3 == 0

print(filter(fn2,l))
# <filter object at 0x00000278FC349040>
print(list(filter(fn2,l))) # [2, 4, 6, 8, 10]
# 可以实现和我们自定义函数的相同效果.

上述代码中fn2是作为参数传递进filter()函数中 , 而fn2实际上只有一个作用,就是作为filter()的参数 , filter()调用完毕以后,fn2就已经没用 , 为了简化代码 , 可以使用匿名函数的方式.

语法: lambda 参数列表 : 返回值

lambda函数表达式专门用来创建一些简单的函数,他是函数创建的又一种方式 , 匿名函数一般都是作为参数使用,其他地方一般不会使用 .

1
2
3
4
5
6
7
8
# 以下两个函数功能等价
def add(a,b):
return a + b
lambda a , b :a + b

# 匿名函数可以作为参数和filter()函数共同使用. 代码变得更加简洁
l = [1,2,3,4,5,6,7,8,9,0]
print(list(filter(lambda i: i%2==0,l ))) # [2, 4, 6, 8, 0]

Python中另一个内置高阶函数map()可以实现 对可迭代对象进行映射操作 , 然后将其添加到一个新的对象中返回.

1
2
3
4
5
6
7
8
l = [1,2,3,4,5,6,7,8,9,10]
r = map(lambda i:i+1,l)
print(r) # <map object at 0x0000021F5ACD9040>
print(type(r)) # <class 'map'>
print(list(r)) # [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

result = map(lambda n:(1 if n % 2 != 0 else n),l)
print(list(result)) # [1, 2, 1, 4, 1, 6, 1, 8, 1, 10]

对于序列的操作中 , 可以使用sort()方法来实现对列表的排序 , sort()方法默认是直接比较列表中的元素的大小 , 在sort()可以接收一个关键字参数 key , key需要一个函数作为参数,当设置了函数作为参数 , 每次都会以列表中的一个元素作为参数来调用函数,并且使用函数的返回值来比较元素的大小 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
l = ['bb','aaaa','c','ddddddddd','fff']
r = l.sort()
print(r) # None 说明该方法没有返回值 , 其排序是对原列表的修改操作

print(l) # ['aaaa', 'bb', 'c', 'ddddddddd', 'fff']
# 说明对字符串默认按Unicode码 使用 < 进行排序

l.sort(key=len) # 指定按len()函数的执行结果使用 < 排序
print(l) # ['c', 'bb', 'fff', 'aaaa', 'ddddddddd']

list = [2,5,'1',3,'6','4']
# list.sort() # TypeError: '<' not supported between instances of 'str' and 'int'
list.sort(key=int) # 将元素类型转换为int后进行排序
print(list) # ['1', 2, 3, '4', 5, '6']

sort()是用来列表排序的方法 , Python中还存在一个函数可以实现排序功能 , sorted() , 这个函数和sort()的用法基本一致,但是sorted()可以对任意的序列进行排序 . 并且使用sorted()排序不会影响原来的对象,而是返回一个新对象 .

1
2
3
4
5
l = [2,5,'1',3,'6','4']

print('排序前:',l) # [2, 5, '1', 3, '6', '4']
print(sorted(l,key=int)) # ['1', 2, 3, '4', 5, '6']
print('排序后:',l) # [2, 5, '1', 3, '6', '4']

闭包

上面介绍的是参数是函数的高阶函数 , 这里介绍返回值是函数的高阶函数 , 这种高阶函数称为闭包. 通过闭包可以创建一些只有当前函数能访问的变量 , 可以将一些私有的数据藏到的闭包中 .

1
2
3
4
5
6
7
8
9
10
def fn():
a = 10
# 函数内部再定义一个函数
def inner():
print('我是fn2' , a)
# 将内部函数 inner作为返回值返回
return inner
r = fn() # 调用fn函数后 , 其返回值是函数inner , 此语句执行后 , r就是inner函数
r() # 调用r函数 , 就相当于调用inner函数
# 我是fn2 10

形成闭包的条件:

  1. 函数嵌套
  2. 将内部函数作为返回值返回
  3. 内部函数必须要使用到外部函数的变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def make_averager():
# 创建一个列表,用来保存数值
nums = []
# 创建一个函数,用来计算平均值
def averager(n) :
# 将n添加到列表中
nums.append(n)
# 求平均值
return sum(nums)/len(nums)
return averager

averager = make_averager()

print(averager(10)) # 10.0
print(averager(20)) # 15.0
nums = [] # 在全局区域对nums的任何更改 不会影响到闭包中的nums , 他们的命名空间不同.
print(averager(30)) # 20.0
print(averager(40)) # 25.0

装饰器

如果有需求希望在函数执行前输出’‘开始执行’’ , 在函数结束后输出"执行完毕" , 显然如果修改每一个函数增加两个print语句以实现这个功能 , 太繁琐且不方便后期维护 , 同时也违反了程序设计的OCP原则. 所谓OCP原则 , 简单来说就是程序应该开放对程序的扩展 , 关闭对程序的修改 . 可以通过装饰器来来实现这一功能.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 实现上述功能的装饰器函数
def begin_end(old):
'''
用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束

参数:
old 要扩展的函数对象
'''
# 创建一个新函数
# 由于不确定被装饰函数的参数个数 , 因此使用两个不定长参数来接受所有的位置参数和所有的关键字参数.
def new_function(*args , **kwargs):
print('开始执行~~~~')
# 调用被扩展的函数
result = old(*args , **kwargs)
print('执行结束~~~~')
# 返回函数的执行结果
return result
# 返回新函数
return new_function
def add(a,b):
return a + b
f = begin_end(add) # f即装饰后的add函数
print(f(2,3))
# 开始执行~~~~
# 执行结束~~~~
# 5

begin_end这种函数我们就称它为装饰器 , 通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展 , 在开发中,我们都是通过装饰器来扩展函数的功能的 .

在定义函数时,可以通过@装饰器,来使用指定的装饰器,来装饰当前的函数 , 可以同时为一个函数指定多个装饰器,这样函数将会按照从内向外的顺序被装饰 .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def fn3(old):
'''
用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束

参数:
old 要扩展的函数对象
'''
# 创建一个新函数
def new_function(*args , **kwargs):
print('fn3装饰~开始执行~~~~')
# 调用被扩展的函数
result = old(*args , **kwargs)
print('fn3装饰~执行结束~~~~')
# 返回函数的执行结果
return result
# 返回新函数
return new_function

def begin_end(old):
'''
用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束

参数:
old 要扩展的函数对象
'''
# 创建一个新函数
def new_function(*args , **kwargs):
print('开始执行~~~~')
# 调用被扩展的函数
result = old(*args , **kwargs)
print('执行结束~~~~')
# 返回函数的执行结果
return result

# 返回新函数
return new_function


@fn3
@begin_end
def say_hello():
print('大家好~~~')

say_hello()

# fn3装饰~开始执行~~~~
# 开始执行~~~~
# 大家好~~~
# 执行结束~~~~
# fn3装饰~执行结束~~~~

本节练习

创建一个函数 power 来为任意数字做幂运算 n ** i

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def power(n , i):
'''
该函数用来求 n ** i
参数:
n : 底数
i : 指数
'''
if i == 0 and n == 0:
print('数据非法')
return
if i == 0 :
return 1
if i == 1:
return n
else :
return n * power(n,i-1)

创建一个函数,用来检查一个任意的字符串是否是回文字符串,如果是返回True,否则返回False

1
2
3
4
5
6
7
def hui_wen(s:str)-> bool:
if len(s)<2:
return True
if s[0] != s[-1]:
return False
else:
return hui_wen(s[1:-1])