1 预备知识
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。函数能提高应用的模块性,和代码的重复利用率。你
Python提供了许多内建函数,比如print()。也可以自己创建函数,这被叫做用户自定义函数。
1.1 函数的定义与调用
可以定义一个由自己想要功能的函数,以下是简单的规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
- 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号 : 起始,并且缩进。
- return [表达式] 结束函数,选择性地返回一个值给调用方,不带表达式的 return 相当于返回 None。

定义一个函数:给了函数一个名称,指定了函数里包含的参数,和代码块结构。
这个函数的基本结构完成以后,可以通过另一个函数调用执行,也可以直接从 Python 命令提示符执行。
#!/usr/bin/python3def hello() :print("Hello World!")hello()
def max(a, b): #a,b叫做形式参数if a > b:return aelse:return ba = 4b = 5maxnum=max(a,b)#调用函数时的参数a,b叫做实际参数print(maxnum)print(max(a, b))
1.2 参数传递
在 python 中,类型属于对象,对象有不同类型的区分,变量是没有类型的。
可更改(mutable)与不可更改(immutable)对象
在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。
- 不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变 a 的值,相当于新生成了 a。
- 可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。
python 函数的参数传递:
- 不可变类型:类似 C++ 的值传递,如整数、字符串、元组。如 fun(a),传递的只是 a 的值,没有影响 a 对象本身。如果在 fun(a) 内部修改 a 的值,则是新生成一个 a 的对象。
- 可变类型:类似 C++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后 fun 外部的 la 也会受影响
python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。
def change(a):print(id(a)) # 指向的是同一个对象a=10print(id(a)) # 一个新对象a=1print(id(a))change(a)
# 可写函数说明def changeme( mylist ):"修改传入的列表"mylist.append([1,2,3,4])print ("函数内取值: ", mylist)return# 调用changeme函数mylist = [10,20,30]changeme( mylist )print ("函数外取值: ", mylist)
1.3 函数的返回值
return [表达式] 语句用于退出函数,选择性地向调用方返回一个表达式。不带参数值的 return 语句返回 None。之前的例子都没有示范如何返回数值。
def sum( arg1, arg2 ):# 返回2个参数的和."total = arg1 + arg2print ("函数内 : ", total)return total# 调用sum函数total = sum( 10, 20 )print ("函数外 : ", total)
1.4 可以把函数作为对象赋给变量
Python中一切皆对象(object).既然一切皆对象,那么一切都可以让变量引用。
def sum( arg1, arg2 ):# 返回2个参数的和."total = arg1 + arg2return totalf=sumtotal = f( 10, 20 )print("total=",total)
1.4 函数作为参数传递给函数
Python中一切皆对象(object).既然一切皆对象,那么一切都可以作为参数传递!
def add_params(a,b):return a+bdef mult_params(func,a,b,c):return func(a,b)*cr1 = add_params(a,b)# mult_params(func,a,b,c) = (1+2)*3 = 3*3 = 9r2= mult_params(add_params,a,b,c)print(r1)print(r2)
1.5 函数作为返回值
函数也可以作为返回值。
def get_math_func(type) :# 定义一个计算平方的局部函数def square(n) : # ①return n * n# 定义一个计算立方的局部函数def cube(n) : # ②return n * n * n# 定义一个计算阶乘的局部函数def factorial(n) : # ③result = 1for index in range(2 , n + 1):result *= indexreturn result# 返回局部函数if type == "square" :return squareif type == "cube" :return cubeelse:return factorial# 调用get_math_func(),程序返回一个嵌套函数math_func = get_math_func("cube") # 得到cube函数print(math_func(5)) # 输出125math_func = get_math_func("square") # 得到square函数print(math_func(5)) # 输出25math_func = get_math_func("other") # 得到factorial函数print(math_func(5)) # 输出120
2 闭包函数
2.1 函数的嵌套定义
嵌套函数类似于嵌套循环,就是函数内又嵌套着函数。
先看一下函数嵌套调用的例子,分析一下结果
def func2(): #定义一个函数print('我是第二个函数')def func1(): #再定义一个函数print('我是第一个函数')func2()func1()
函数可以进行嵌套定义。
def outer(): #定义外层函数print('我是外层函数')def inner(): #定义内层函数print('我是内层函数')inner() #执行内层函数print('外层函数运行结束')outer()#inner() 尝试添加如下语句,看有什么结果?
2.2 定义闭包函数
在嵌套函数的定义中,把外层函数返回值指向内层函数名,则可以构造出闭包函数。
def outer(): #定义外层函数a=1print('我是外层函数')def inner(): #定义内层函数print('我是内层函数')print('内层函数打印',a)return inner #执行内层函数f=outer() #调用外层函数,并把结果赋值给ff()
:::info 看到内层函数和外层函数都执行了,并且外层函数中的变量a被打印出来。这就是闭包函数,外层函数的变量可以被内层函数调用,这样外层函数变量和内层函数一起构成了类似‘’肚子里的一块区域‘’,这块区域被保护起来,变量只供内层函数‘’享用‘’。类似于封装的效果。内层函数不会立马被执行,当再次调用f时,内层函数才会执行。 :::
在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。 闭包函数需要有三个条件,缺一不可: 1.必须有一个内嵌函数 2.内部函数引用外部函数变量 3.外部函数必须返回内嵌函数
2.3 闭包函数中局部变量的作用域
分析下面的程序,会输出什么?
def outer(): #定义外层函数a=1print('我是外层函数')def inner(): #定义内层函数a=10print('我是内层函数')print('内层函数打印',a)return inner #执行内层函数f=outer() #调用外层函数,并把结果赋值给ff()
:::info
在内层函数中又有a=10,此时并不是改变的外层函数中的a,而是在内层函数中定义的新变量,是两个不同的东西。从结果也能看出来。这就是作用域的问题。
内层函数中调用的变量首先会从内层函数中找,找不到就去外层函数中找,再找不到就到函数外代码中找,再找不到就到内置的模块中找,最后还是找不到,就报错。
:::
下面程序会出错,找找出错的原因
def outer(): #定义外层函数a=1print('我是外层函数')def inner(): #定义内层函数a+=10print('我是内层函数')print('内层函数打印',a)return inner #执行内层函数f=outer() #调用外层函数,并把结果赋值给ff()
:::info
在 python 的函数内,可以直接引用外部变量,但不能改写外部变量。
如果想修改外部函数中的变量,必须加一个nonlocal的声明,修改如下。这和在函数中修改全局变量,加global 有异曲同工之妙。
Nonlocal 与 global 的区别在于:nonlocal 语句会去搜寻本地变量与全局变量之间的变量,其会优先寻找层级关系与闭包作用域最近的外部变量。
:::
def outer(): #定义外层函数a=1print('我是外层函数')def inner(): #定义内层函数nonlocal aa+=10print('我是内层函数')print('内层函数打印',a)return inner #执行内层函数f=outer() #调用外层函数,并把结果赋值给ff()
2.4 Python闭包的closure属性
闭包比普通的函数多了一个 closure 属性,该属性记录着自由变量的地址。当闭包被调用时,系统就会根据该地址找到对应的自由变量,完成整体的函数调用。
def nth_power(exponent):def exponent_of(base):return base ** exponentreturn exponent_ofsquare = nth_power(2)#查看 __closure__ 的值print(square.__closure__)
2.5 闭包函数的意义
:::danger
闭包函数的意义
1、上面讲了一点就是把某些变量和函数代码保护到‘’肚子里‘’,这样可以起到保护的作用。
2、可以构造一个新函数。
:::
def outer(a): #定义函数要一杯不同类型的饮料def inner(b): #定义内层函数,饮料的容量print(f'要一杯{a}')print(f'容量是{b}毫升')return inner #执行内层函数blacktea=outer('红茶') #调用外层函数,并把结果赋值给fgreentea=outer('绿茶')blacktea(300)greentea(500)
:::info
闭包的特点
闭包的最大特点是可以将父函数的变量与内部函数绑定,并返回绑定变量后的函数(也即闭包),此时即便生成闭包的环境(父函数)已经释放,闭包仍然存在。
这个过程很像类(父函数)生成实例(闭包),不同的是父函数只在调用时执行,执行完毕后其环境就会释放,而类则在文件执行时创建,一般程序执行完毕后作用域才释放,因此对一些需要重用的功能且不足以定义为类的行为,使用闭包会比使用类占用更少的资源,且更轻巧灵活。
:::
2.6 闭包应用的例子
例子1:计算一个数的 n 次幂,用闭包可以写成下面的代码,试分析以下代码的运行过程和结果
#闭包函数,其中 exponent 称为自由变量def nth_power(exponent):def exponent_of(base):return base ** exponentreturn exponent_of # 返回值是 exponent_of 函数square = nth_power(2) # 计算一个数的平方cube = nth_power(3) # 计算一个数的立方print(square(2)) # 计算 2 的平方print(cube(2)) # 计算 2 的立方
例子2:生成不同html标签的例子,,试分析以下代码的运行过程和结果
def tag(tag_name):def add_tag(content):return "<{0}>{1}</{0}>".format(tag_name, content)return add_tagcontent = 'Hello'add_tag = tag('a')print add_tag(content)add_tag = tag('b')print add_tag(content)
3 装饰器函数(Decorators)
3.1 装饰器的简介
所谓装饰器,就是对已经存在的函数进行功能的扩充,装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数。
在python中,函数也是一种对象,对于一个对象,我们可以进行很多的操作,例如:
- 进行赋值运算
- 当作参数使用
- 当作返回值使用
- 进行嵌套(比如列表,字典)
这些操作在函数的上都可以使用,同时也是我们制作装饰器的基础
3. 2 简单的装饰器
3.2.1 装饰器的实现过程
以下是一个打印 1 到 1,000,000 中所有素数的程序
# 判断是否是素数的函数def is_prime(x):if x < 2:return Falseif x == 2:return Trueif x % 2 == 0:return Falselimit = int(x ** 0.5) + 2for i in range(3, limit, 2):if x % i == 0:return Falsereturn True# 打印素数的函数def prime_nums():for i in range(1, 1000000):if is_prime(i):print(i)prime_nums()
现在有一个问题,我们想要测试我们打印一到一百万中的所有的素数所花费的时间,需要将上面的代码修改一下。
from time import timedef is_prime(x):if x < 2:return Falseif x == 2:return Trueif x % 2 == 0:return Falselimit = int(x ** 0.5) + 2for i in range(3, limit, 2):if x % i == 0:return Falsereturn True# 修改后的打印素数的花费时间的函数def prime_nums():t1 = time()for i in range(1, 1000000):if is_prime(i):print(i)t2 = time()print(f'程序花费的时间为:{t2 - t1:.2f}秒')prime_nums()
那么可以看到,我们通过修改原来的代码,添加了这个功能,但是原来的函数的功能其实已经变性了,本来只是打印素数的,现在修改为打印素数并且测试时间,当我们不需要测试时间时又需要去改源代码,这十分麻烦并且低效。
既然函数可以当作参数,那何不把需要增加功能的函数当作参数传入到另外用来添加功能的函数中呢?装饰器就是这样做的。
总体上的实现图:
下面是实现这个过程的具体代码,在以上代码的基础上增加了新的代码
from time import time# 用来包装 “打印素数的函数” 的函数def decorator(func):def wrapper():# 原函数上需要增加的功能t1 = time()func()t2 = time()print(f'程序花费时间为:{t2 - t1:.2f}秒')# 返回wrapper这个函数对象return wrapper# 返回“wrapper的返回值”,# wrapper的返回值是wrapper函数对象return wrapper# 判断是否为素数的代码def is_prime(x):if x < 2:return Falseif x == 2:return Trueif x % 2 == 0:return Falselimit = int(x ** 0.5) + 2for i in range(3, limit, 2):if x % i == 0:return Falsereturn True# 打印素数的代码def prime_nums():for i in range(1, 1000000):if is_prime(i):print(i)# 把打印素数的代码当作参数送入# 使用一个变量去接收返回的函数对象new_func = decorator(prime_nums)# 现在这new_func是decorator的返回值# 也就是代表了new_func是wrapper函数对象new_func()print(f'new_func的__name__属性为:{new_func.__name__}')
3.2.2 装饰器的@语法糖
在上面的代码中,我们使用了new_func = decorator(prime_nums)这句代码去接收获得的装饰器,但是python提供了@语法糖,能让我们方便的调用装饰器。
from time import time# 用来包装 “打印素数的函数” 的函数def decorator(func):def wrapper():t1 = time()func()t2 = time()print(f'程序花费时间为:{t2 - t1:.2f}秒')return wrapperreturn wrapper# 判断是否为素数的代码def is_prime(x):if x < 2:return Falseif x == 2:return Trueif x % 2 == 0:return Falselimit = int(x ** 0.5) + 2for i in range(3, limit, 2):if x % i == 0:return Falsereturn True# 打印素数的代码@decoratordef prime_nums():for i in range(1, 1000000):if is_prime(i):print(i)prime_nums()print(f'prime_nums的__name__属性为:{prime_nums.__name__}')
当我们使用@decorator之后,运行prime_nums()时实际上是运行wrapper()

可以理解成prime_nums本来是指向原函数的,后来被装饰器覆盖了,运行这个函数就是运行的装饰器
类比C语言中的指针,prime_nums就是一个指针,本来指向原函数的,这个prime_nums本身存储了原函数的首地址,后来添加了@decorator之后,这个prime_nums指针就指向了装饰器,prime_nums中的值就变成了装饰器的首地址
3.3 带返回值的装饰器
现在输出素数的函数改成了计算一到一百万的素数的数量的多少,我们count替换print(i)就可以了,每遇到一个素数count+=1, 并且在装饰器的那个函数里面把统计的count返回,实现起来很简单。
from time import time# 用来包装 “打印素数的函数” 的函数def decorator(func):def wrapper():t1 = time()result = func()t2 = time()print(f'程序花费时间为:{t2 - t1:.2f}秒')return resultreturn wrapper# 判断是否为素数的代码def is_prime(x):if x < 2:return Falseif x == 2:return Trueif x % 2 == 0:return Falselimit = int(x ** 0.5) + 2for i in range(3, limit, 2):if x % i == 0:return Falsereturn True# 打印素数的代码@decoratordef prime_nums():count = 0for i in range(1, 1000000):if is_prime(i):count += 1return countprint(f'一到一百万的素数有{prime_nums()}个')
3.4 带参数的装饰器
在以上的代码中,我们直接把需要求素数的范围直接写进了循环中,现在我们需要通过控制台输入的方式输入素数的范围
prime_nums的代码如下:
def prime_nums(limit):count = 0for i in range(1, limit):if is_prime(i):count += 1return count
limit是我们需要输入的范围,修改之后的装饰器代码如下:
def decorator(func):def wrapper(limit):t1 = time()result = func(limit)t2 = time()print(f'程序花费时间为:{t2 - t1:.2f}秒')return resultreturn wrapper
在将函数进行传递的时候,也就是def decorator(func) 这句代码,会把函数的参数也带上,内层的wrapper(limit)就是外界中传进去的参数limit。
from time import time# 用来包装 “打印素数的函数” 的函数def decorator(func):def wrapper(limit):t1 = time()result = func(limit)t2 = time()print(f'程序花费时间为:{t2 - t1:.2f}秒')return resultreturn wrapper# 判断是否为素数的代码def is_prime(x):if x < 2:return Falseif x == 2:return Trueif x % 2 == 0:return Falselimit = int(x ** 0.5) + 2for i in range(3, limit, 2):if x % i == 0:return Falsereturn True# 打印素数的代码@decoratordef prime_nums(limit):count = 0for i in range(1, limit):if is_prime(i):count += 1return countlimit = 10000print(f'1 - {limit}范围内的素数有{prime_nums(limit)}个')
