一、定义
函数是组织好的、可重复使用的、用来实现单一、或相关联功能的代码段。
函数能提高应用的模块性和代码的重复利用率。我们已经知道Python提供了许多内建函数,比如print()。同时我们也可以自己创建函数,这被叫做用户自定义函数。
二、自定义函数
1. 定义规则
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。
- 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
- 函数内容以冒号起始,并且缩进。
>>> def test():
print('测试函数调用')
#无输出结果
但是运行如上代码时并不会有任何输出。这是为什么呢?原因是因为函数只有当调用时才会执行
2. 函数调用
- 通过函数名加英文状态下的小括号对函数进行调用,如 test() 注意加()为调用函数,不加()为引用函数
```python
def test(): print(‘测试函数调用’)
test() 测试函数调用 ```
练习
- 确定以下代码的输出结果是什么?
def test1():
print(5)
def test2():
print(4)
test1()
print(3)
def test3():
print(2)
test2()
print(1)
test3()
#out
2
4
5
3
1
- 通过函数调用的形式打印输出 ‘九九乘法表’ 五次
def t():
print('九九乘法表')
def t_1():
for i in range(5):
t()
t_1()
#out
九九乘法表
九九乘法表
九九乘法表
九九乘法表
九九乘法表
三、函数参数
1. 形参与实参
- 形参就是形式上的参数,没有实际的值,通过别人赋值后才有意义,相当于变量。
- 实参就是实际意义上的参数,是一个实际存在的参数。 ```python def test(b): # 形参,是一个临时变量 print(b)
a = 1 test(a) # 实参
---
<a name="lh5vj"></a>
### 练习
> - [x] 如下代码字段,输出结果会是什么呢?原因是什么?
```python
def test(a):
a = 2
print(a)
a = 1
test(a)
print(a)
#out
2
1
2. 参数类型
- 不可变类型传参在函数体内相当于对变量重新赋值,相当于重新开辟了一块内存来保存值 ```python def test(a): a = 2 print(a) print(id(a)) a = 1 test(a) print(a) print(id(a))
out
2 1654877696 1 1654877664
- 可变类型传参,在函数体内可以改变原有的值,但是不会改可变变参数存储的内存值
```python
def test1(li):
li.extend([1,11])
print('111',li) # [1, 2, 3, 1, 11]
print(id(li))
li = [1,2,3]
test1(li)
print('222',li) # [1, 2, 3, 1, 11]
print(id(li))
#out
111 [1, 2, 3, 1, 11]
35438792
222 [1, 2, 3, 1, 11]
35438792
3. 位置参数
- 位置参数也就是实参与实参顺序一一对应,而不论变量名 ```python def sum_nums(num1, num2): result = num1 + num2 print(“num1”,num1) print(“num2”,num2)
num1 = int(input(“请输入num1:”)) num2 = int(input(“请输入num2:”))
sum_nums(num1, num2) sum_nums(num2, num1)
out
num1+num2=result num2+num1=result
<a name="wODqu"></a>
### 4. 关键字参数(形参)
以形参作为关键字参数,不论参数位置,让传入值准确无误。将如上代码进行简化
```python
def test(b,a):
res = a + b
print(f'{a}+{b}={res}')
a = int(input('请输入a值: '))
b = int(input('请输入b值: '))
test(a=a,b=b)
#out
请输入a值: 10
请输入b值: 20
10+20=30
5. 默认值参数
形参处进行赋值后,当调用时不传该参数就默认使用形参处的值;当调用时传了该参数,则覆盖掉默认参数。
def test2(a,b=2):
a = a+b
print(a,b)
a = 1
test2(a)
test2(a,10)
#out
3 2
11 10
拓展:requests模块
爬虫中使用到的requests.get(url,params=None,kwargs)
*args
kwargs
6. 可变长度参数
可变长度参数,可传可不传,如果传就会有指定的格式返回值。实并不是必须写成args 和kwargs。 只有变量前的 (星号)才是必须的. 你也可以写成*var 和vars. 或者写成args 和**kwargs只是通俗的命名约定。
args 和 **kwargs 主要用于函数定义。 你可以将不定数量的参数传递给一个函数。这里的不定的意思是:预先并不知道, 函数使用者会传递多少个参数给你, 所以在这个场景下使这两个关键字。
- *args 是用来发送一个非键值对的可变数量的参数列表给一个函数。*args接收时会转为元组数据类型
```python
def test1(a,*b):
print(‘第一个传参显示值为:’,a)
for i in b:
tu = (1,2,3,4,5,6) test1(1,2,3,4,5,6) #第一个测试为传入6个数值 print(‘-‘10) #测试分割线 test1(tu) #第二个测试传入一个元组 print(‘-‘10) #测试分割线 test1(tu) #第三个测试传入一个用号标记的元组print('接下来的传参顺序为:',i)
out
第一个传参显示值为: 1 接下来的传参顺序为: 2 接下来的传参顺序为: 3 接下来的传参顺序为: 4 接下来的传参顺序为: 5 接下来的传参顺序为: 6
第一个传参显示值为: (1, 2, 3, 4, 5, 6)
第一个传参显示值为: 1 接下来的传参顺序为: 2 接下来的传参顺序为: 3 接下来的传参顺序为: 4 接下来的传参顺序为: 5 接下来的传参顺序为: 6
- **kwargs 允许你将不定长度的键值对, 作为参数传递给一个函数。 **如果你想要在函数里面处理带名字的参数**, 你应该使用**kwargs。****kwargs接收时会转为字典数据类型**
```python
def test(**x):
for key,value in x.items():
print('{0}={1}'.format(key,value))
test(name='wei')
#out
name=wei
def test(**kwargs):
print(kwargs)
print(type(kwargs))
test()
#out
{}
<class 'dict'>
test(a=1,b=2,c=3,d=4) #这里就相当于一个打包的过程
#out
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
<class 'dict'>
- 测试使用*args 和kwargs两种参数来对函数调用的不同之处**
```python
def test(a,b,c):
print(‘第一个接收参数为:’,a)
print(‘第二个接收参数为:’,b)
print(‘第三个接收参数为:’,c)
先使用*args进行传参
tu=(1,2,3) test(*tu)out
第一个接收参数为: 1 第二个接收参数为: 2 第三个接收参数为: 3
再使用**kwargs进行传参
tu1={‘c’:3,’a’:1,’b’:2} test(**tu1)
out
第一个接收参数为: 1 第二个接收参数为: 2 第三个接收参数为: 3 some_func(fargs, args, *kwargs)
- **标准参数、*args、**kwargs在使用时的顺序**
如果你想在函数里同时使用所有这三种参数, 顺序是这样的:**some_func(fargs, *args, **kwargs)**<br />最常见的用途是在写函数装饰器的时候,此外它也可以用来做**猴子补丁**(monkey patching)。猴子补丁的意思是在程序运行时(runtime)修改某些代码。<br />打个比方,你有个类,里面有个叫get_info的函数会调某API并返回响应的数据。如果我们想测试它,可以把API调用替换成某些测试数据。例如敲入如下代码:
```python
import someclass
def get_info(self,*args):
return 'test data'
someclass.get_info = get_info
7. 解包
def test(a,b,c):
print(a,b,c)
tu = (1,2,3)
test(*tu)
#out
1 2 3 #拆包,将元组内元素解包
a,b,c = (1,2,3)
a,b,c = [1,2,3]
print(a,b,c)
#out
1 2 3
1 2 3
8. 调试
Python debugger(pdb)
利用好调试,能大大提高你捕捉代码Bug的能力。这里着重说一下从脚本内部运行设置断点,使用pdb.set_trace()方法来实现。举个例子:
import pdb
def make_bread():
pdb.set_trace()
return "I don't have time"
print(make_bread())
试下保存上面的脚本后运行之,你会马上进入debugger模式。现在是时候了解下debugger模式下的这些命令了。
命令列表:
- c: 继续执行
- w: 显示当前正在执行的代码行的上下文信息
- a: 打印当前函数的参数列表
- s: 执行当前代码行,并停在第一个能停的地方(单步进入)
- n: 继续执行到当前函数的下一行,或者当前行直接返回(单步跳过)
单步跳过(next)和单步进入(step)的区别在于: 单步进入会进入当前行调用的函数内部并停在里面, 而单步跳过会(几乎)全速执行完当前行调用的函数,并停在当前函数的下一行。
def test(num):
print('我是外函数')
pdb.set_trace()
def test1(num1):
print('我是内函数,用了临时变量')
return num+num1
print('我是外函数的返回值')
return test1
res =test(20)
print(res)
res1 = res(30)
print(res1)
#可以玩一下,在闭包装饰器中的执行步骤
四、函数返回值
1. 返回值介绍
当两个函数之间,想要互相使用到内部变量时,就可以应用到函数的返回值。
练习
- 我们要使用自定义函数实现摄氏度和华氏度相互转化的功能
def c_t():
c_v = float(input('请输入摄氏温度: ')) #2---main方法调用执行函数,执行输入数据转化
print(f'今天的摄氏温度为: {c_v}') #3---顺序执行print语句
return c_v #4---将转化后的c_v值返回出函数外
def f_t(c_v): #7---形参接收传参数据,以c_v变量值形式
f_v = round(c_v/1.8+32,1) #8---顺序执行计算代码赋值给f_v
print(f'今天的华氏温度为: {f_v}') #9---顺序执行print语句
if __name__ == '__main__':
res = c_t() #1实例化调用执行c_t()函数 #5将return出来的c_v赋值给res
f_t(res) #6将c_v值以传参的形式传到f_t()函数内
#out
请输入今天的摄氏温度: 33.5
今天的摄氏温度是: 33.5
今天的华氏温度是: 50.6
比如
- 今天距离20号还有13天
- 20号距离春节5天,今天距离春节多少天? ```python def holiday(): dis_holiday = 13 print(“距离放假还有:{}”.format(dis_holiday)) return dis_holiday
def spring_fes(dis_holiday): dis_spr_fes = dis_holiday + 5 print(“距离春节还有:{}”.format(dis_spr_fes))
dis_holiday = holiday() spring_fes(dis_holiday)
- 注意1:使用return关键字返回内容
- 注意2:将内容返回到函数调用处
- 注意3:函数体中没有return语句时,函数运行结束,则默认返回None,也被称为隐含返回值
<a name="rWone"></a>
### 2. 多个返回值
- 注意1:当执行函数体内代码时,遇到第一个return就将指定值返回到函数调用处,也就是执行到return这行代码,后面的都不执行了。
- 注意2:**多个返回值时,用逗号隔开,但默认为元组。**
```python
def test():
a = 1
b = 2
c = 3
return a,b,c #默认以元组类型返回出去,如果要改成其它类型可在此处进行修改
# d = 4 # 不执行
res = test()
print(res)
#out
(1,2,3)
- 定义一个函数完成用户输入的三个数字的求和以及在另一个函数求该和的平均值(用到函数传参,函数返回值)
a = int(input('请输入数字: '))
b = int(input('请输入数字: '))
c = int(input('请输入数字: '))
def sum_1(a,b,c):
res = sum((a,b,c))
print(f'{a}、{b}、{c}三者之和为:{res}')
return res
def ave_2(a,b,c,d):
d =round(d /3 ,1)
print(f'{a}、{b}、{c}三者均值为: {d}')
d = sum_1(a=a,b=b,c=c)
ave_2(a=a,b=b,c=c,d=d)
五、函数作用域
1. 作用域简介
Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
变量的作用域决定了哪些程序可以访问哪些特定变量名。Python的作用域一共有4种,分别是:
- L(local):局部作用域,即函数中定义的变量
- E(enclosing):嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的
- G(global):全局变量,就是模块级别定义的变量
- B(build-in):内建作用域,系统固定模块里面的变量,比如:int()等
条件判断以及循环体都是没有作用域说法的
2. 作用域优先级
局部作用域(L)>父级函数作用域(E)>全局作用域(G)>系统模块(B)
a = int(3.14) # built-in
b = 11 # global
def outer():
c = 5 # enclosing
def inner():
d = 6 # local
return 1
注意在函数内部无法直接修改全局变量
count = 10
def test():
count = 5
print(count)
return count
test()
print(count)
#out
5
10
3. global关键字
当我们需要在函数内部直接修改全局变量时,我们可以将函数内部的局部变量通过global关键字声明为全局变量。
count = 10
def test():
global count
count = 5
print(count)
test()
print(count)
#out
5
5
六、递归函数
1. 函数介绍
函数Func(args)直接或间接调用函数本身,则该函数称为递归函数。
2. 计算阶乘
分析如下:
- 阶乘本质:n! = 1 2 3 … n
- 用函数func(n)表示, 可以看出:func(n) = n! = 1 2 3 … (n-1) n = (n-1)! n = func(n-1) * n
- 所以 func(n)可以表示为n * func(n-1)
- 运行会发现递归函数自身没有结束条件,所以需要我们自己设置结束条件,终止函数的调用。
a = int(input("请输入计算数值: "))
def fc(a):
if a >1:
return a*fc(a-1)
else:
return 1
res = fc(a)
print(res)
#out
请输入计算数值: 10
3628800
from functools import reduce
i = int(input("阶乘值i= "))
print(reduce(lambda x,y:x*y,range(1,i+1)))
#out
请输入计算数值: 10
3628800
方法2:
l = [“jack”,(“tom”,23),”rose”,(14,55,67)] def test(): for i in l: if isinstance(i,str): print(i) else: l1 = list(i) for j in l1: print(j) if name == ‘main‘: test()
- [x] 制作一个学员信息查询系统,本系统功能包括:
- 查看学员信息
- 添加学员信息
- 删除学员信息
- 退出系统
```python
#实现步骤:
#1.准备一个包含学员信息的列表,每位学员的信息放到一个字典里,若干个字典构成一个列表
#2.将4个功能分别封装成4个函数,以便后期调用
#3.制作界面和功能的选项(调用函数)
#建立可供更新的基础功能列表
student_list=[{'name':'魏公博','age':20,'classid':'高三'},{'name':'郑晓波','age':19,'classid':'高三'},{'name':'王中原','age':17,'classid':'初二'}]
#建立查看学员信息函数
def show_info():
if len(student_list) == 0:
print('-'*14,'没有学员信息','-'*14)
return
print('|{0:<4}|{1:<10}|{2:<4}|{3:<10}|'.format('序号', '姓名', '年龄', '班级'))
print('=' * 14, '信息查询系统', '=' * 14)
for i, j in enumerate(student_list):
print('|{0:<5}|{1:<10}|{2:<5}|{3:<10}|'.format(i + 1, j['name'], j['age'], j['classid']))
#建立添加学员信息函数
def add_info(name,age,classid):
student_dic = {}
student_dic['name'] = name
student_dic['age']= age
student_dic['classid'] =classid
student_list.append(student_dic)
#建立删除学员信息函数
def del_info(x):
x = int(x)
student_list.pop(x-1)
#建立退出学员系统函数
def exit_exe():
pass
while True:
#登陆界面
print(' '*14,'WELCOM',' '*14)
print('='*12,'学员管理系统','='*12)
print('{:1} {:13} {:15}'.format(' ', '1. 查看学员信息', '2. 添加学员信息'))
print('{:1} {:13} {:15}'.format(' ', '3. 删除学员信息', '4. 退出系统'))
print('=' * 40)
choose = input('请输入您的选项: ')
#根据第一次的输入值,调用对应函数,实现分模块功能
if choose == '1':
print('-'*14,'学员信息简介','-'*14)
show_info()
input('按回车键返回主菜单')
elif choose == '2':
print('-'*14,'信息新增系统','-'*14)
name = input('姓名')
age = input('年龄')
classid = input('班级')
add_info(name,age,classid)
show_info()
input('按回车键返回主菜单')
elif choose == '3':
print('-' * 14, '信息删除系统', '-' * 14)
show_info()
x = input('请输入需要删除学员的编号')
del_info(x)
show_info()
input('按回车键返回主菜单')
elif choose == '4':
exit_exe()
print('-' * 14, '欢迎下次使用', '-' * 14)
break
else:
print('Error:无效操作,请重新输入命令')
#建立一个列表
modu_list = [{'name':'魏公博','age':18}]
#定义一个新增函数
def add(name,age):
add_dic = {}
add_dic['name'] = name
add_dic['age'] = age
modu_list.append(add_dic)
#定义一个显示函数
def show():
print('|{0:<3}|{1:<5}|{2:<3}|'.format('序号','姓名','年龄'))
for i,j in enumerate(modu_list):
print('|{0:<3}|{1:<5}|{2:<3}|'.format(i+1,j['name'],j['age']))
while True:
#建立初始化界面
print('Hi!欢迎加入管理系统')
print('1.添加学员信息\n2.退出系统')
choose = input('请选择')
if choose == '1':
print('请输入添加学员信息: ')
name = input('请输入学员名字: ')
age = input('请输入学员年龄: ')
add(name,age)
choose2 = input('是否继续?yes/no')
if choose2.lower() == 'yes':
name = input('请输入学员名字: ')
age = input('请输入学员年龄: ')
add(name, age)
elif choose2.lower() == 'no':
show()
break
else:
print('选择有误')
show()
break
show()
elif choose == '2':
print("欢迎下次使用")
break
else:
print('无效操作,请重新输入')
七、常用内置函数
我们调用命令行,进入ipython交互环境中,然后输入命令help(builtin)即可查看到py中所有的内置文件,如下:
拓展:reduce()函数
reduce()函数在Python3中需导入模块
def reduce(function, sequence, initial=None): # real signature unknown; restored from doc
1. 语法参数
from functools import reduce # 导入模块
reduce(function, sequence[, initial])
参数介绍
function – 有两个参数的函数, 必需参数; sequence – tuple ,list ,dictionary, string等可迭代物,必需参数; initial – 初始值, 可选参数; 返回值:返回计算结果;
运行原理
- 把上一次计算的结果作为下一次的计算的输入
reduce的工作过程是 :在迭代sequence(tuple ,list ,dictionary, string等可迭代物)的过程中,首先把前两个元素传给函数参数,函数加工后,然后把得到的结果和第三个元素作为两个参数传给函数参数, 函数加工后得到的结果又和第四个元素作为两个参数传给函数参数,依次类推。 如果传入了 initial 值, 那么首先传的就不是 sequence 的第一个和第二个元素,而是 initial值和 第一个元素。经过这样的累计计算之后合并序列到一个单一返回值。
2. 普通用法
from functools import reduce # 导入模块
def func1(x,y):
# 把上一次计算的结果作为下一次的计算的输入
print(f"{x} * {y} = {x*y}")
return x*y
if __name__ == "__main__":
list1 = [1,2,3,4,5]
value = reduce(func1,list1) #等价 1*2*3*4*5 = 120
print(value)
print(type(value))
#out
1 * 2 = 2
2 * 3 = 6
6 * 4 = 24
24 * 5 = 120
120
<class 'int'>
3. reduce函数配合匿名函数使用
if __name__ == "__main__":
list1 = [1,2,3,4,5]
value = reduce(lambda x,y : x*y ,list1) #等价 1*2*3*4*5 = 120
print(value)
print(type(value))
#out
120
<class 'int'>
4. reduce函数设置可选参数initial
from functools import reduce # 导入模块
def func1(x,y):
return x*y
if __name__ == "__main__":
list1 = [1,2,3,4,5]
value = reduce(func1,list1,50) #等价 50*1*2*3*4*5 = 6000
print(value)
print(type(value))
#out
6000
<class 'int'>
练习
- 实现阶乘
from functools import reduce
from functools import reduce
i = int(input("阶乘值i= "))
def a(x,y):
return x*y
print(reduce(a,range(1,i+1)))
#out
阶乘值a= 10
3628800
- 取数奇数列表
a =int(input('请输入数字: '))
li = []
for i in range(1,a+1):
if i % 2 == 1:
li.append(i)
print(li)
拓展:filter()函数
功能简介
用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新序列 例如:上题练习中提及的取奇数,可转化为以下代码执行
class filter(object):
"""
filter(function or None, iterable) --> filter object
Return an iterator yielding those items of iterable for which function(item)
is true. If function is None, return the items that are true.
"""
n = int(input('请输入数字: '))
def newLi_add(i):
return i%2==1
print(list(filter(newLi_add,range(1,n+1))))
八、匿名函数
1. 匿名函数介绍
当我们在传入函数时,有些时候,不需要去重复调用该函数,直接传入匿名函数更方便,也无需担心函数名冲突,并且还可以将匿名函数赋值给一个变量,再利用变量来调用该函数。
2. 匿名函数语法
- 匿名函数的表达式为:**lambda x:xx*
- 关键字lambda表示匿名函数
- 冒号前面的x表示函数参数
- x*x是匿名函数的表达式,并且注意匿名函数只能有一个表达式,不用写return,返回值就是该表达式的结果
实际上就是:
def f(x):
return x*x
f = lambda x:x*x
print(f(2))
#out
4
匿名函数作为函数返回值
def t(i,j):
return lambda :i*j #1返回值要在函数的调用处接收
print(t(5,6))
res = t(5,6) #2用res接收返回值,本质是lambda这个匿名函数
print(res()) #3函数的调用要用小括号进行调用,就产生了res()输出结果
#out
<function t.<locals>.<lambda> at 0x0000000001E798C8>
30
匿名函数作实参
from functools import reduce
i = int(input("阶乘值i= "))
print(reduce(lambda x,y:x*y,range(1,i)))
#0ut
阶乘值i= 10
3628800
def test(a,b,func):
res = func(a,b)
return res
nums = test(11,22,lambda x,y:x+y)
print(nums)
练习
- 将li = [4,-2,-5,1,3]以元素的绝对值的方式进行排序为 [1, -2, 3, 4, -5]
li = [4,-2,-5,1,3]
li.sort(key=abs)
print(li)
#out
[1, -2, 3, 4, -5]
- infors = [{‘name’:’qian’,’age’:28},{‘name’:’amy’,’age’:20},{‘name’:’james’,’age’:25}]需求:以 name 的值进行排序
infors = [{'name':'qian','age':28},{'name':'amy','age':20},{'name':'james','age':25}]
print(id(infors))
infors.sort(key=lambda x:x['name'],reverse=True) #key值指的是关键字,reverse指的是升序或降序
print(infors)
print(id(infors))
#OUT
35709128
[{'name': 'qian', 'age': 28}, {'name': 'james', 'age': 25}, {'name': 'amy', 'age': 20}]
35709128
- args,*keargs 参数是什么?
*args是可变位置参数,其中的args为一个元组,传入的参数会被放入一个元组中
**kwargs是可变关键字参数,其中的kwargs为一个字典,传入的参数会以键值对的形式放入字典中
- 求1-100的累加(使用到匿名函数以及reduce方法),延伸到自定义宽度累加
from functools import reduce
while True:
a = input("请输入累和开始值: ")
b = input('请输入累和终止值:')
if a.isdigit() and b.isdigit() and int(b)>int(a):
print(f'{int(a)}累和{int(b)}的结果为:{reduce(lambda x,y:x+y,range(int(a),int(b)+1))}')
break
else:
print('输入有误,请重新输入')
#out
请输入累和开始值: 123
请输入累和终止值:456
123累和456的结果为:96693
- 将以下列表 li = [-11,1,-1,-6,5,8]的负值全部变为正数(注意:可以用到 map 函数)
li = [-11,1,-1,-6,5,8]
#方法1
print(list(map(abs,li)))
#方法2
print([abs(i) for i in li])
#out
[11, 1, 1, 6, 5, 8]
- 以下列表li = [1,5,11,22,13,50,12]筛选大于10的数
li = [1,5,11,22,13,50,12]
#方法1:
def screen(i):
if i > 10:
return i
print(list(filter(screen,li)))
#方法2:
print(list(filter(lambda x:x>10,li)))
#out
[11, 22, 13, 50, 12]
九、高阶函数
我们学习过的map(),filter(),reduce()等都是高阶函数,只要满足以下任一个条件的就是高阶函数。
- 函数名作为参数传入
- 函数名作为返回值
十、闭包
如果在一个函数的内部定义了另一个函数,外部的函数叫它外函数,内部的函数叫它内函数。
1. 闭包条件解读
闭包的定义需要满足以下三个条件:
- 在一个外函数中定义了一个内函数
内函数里运用了外函数的临时变量
一个函数结束的时候,会把自己的临时变量都释放给内存,之后变量都不存在了。一般情况下,确实是这样的。但是闭包是一个特别的情况。外部函数发现,自己的临时变量会在将来的内部函数中用到,自己在结束的时候,返回内函数的同时,会把外函数的临时变量和内函数绑定在一起。所以外函数已经结束了,调用内函数的时候仍然能够使用外函数的临时变量。 在以上实际例子中,两次调用外部函数outer,分别传入的值是5和7。内部函数只定义了一次,我们发现调用的时候,内部函数是能识别外函数的临时变量是不一样的。Python中一切都是对象,虽然函数我们只定义了一次,但是外函数在运行的时候,实际上是按照里面代码执行的,外函数里创建了一个函数,我们每次调用外函数,它都创建一个内函数,虽然代码一样,但是却创建了不同的对象,并且把每次传入的临时变量数值绑定给内函数,再把内函数引用返回。虽然内函数代码是一样的,但其实,我们每次调用外函数,都返回不同的实例对象的引用,他们的功能是一样的,但是它们实际上不是同一个函数对象。 ———————————————— 版权声明:本文为CSDN博主「woshini299cjr」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/u013380694/article/details/90019571
并且外函数的返回值是内函数的引用
引用是什么?在python中一切都是对象,包括整型数据1,1.23,函数,都是对象。 当我们进行a=1的时候,实际上在内存当中有一个地方存了值1,然后用a这个变量名存了1所在内存位置的引用。引用就类似于c语言里的指针,引用也可以理解成地址。a只不过是一个变量名字,a里面存的是1这个数值所在的地址,就是a里面存了数值1的引用。 相同的道理,在python中定义一个函数def demo(): 的时候,内存当中会开辟一些空间,存下这个函数的代码、内部的局部变量等等。这个demo只不过是一个变量名字,它里面存了这个函数所在位置的引用而已。我们还可以进行x = demo, y = demo, 这样的操作就相当于,把demo里存的东西赋值给x和y,这样x 和y 都指向了demo函数所在的引用,在这之后我们可以用x() 或者 y() 来调用我们自己创建的demo() ,调用的实际上根本就是一个函数,x、y和demo三个变量名存了同一个函数的引用。 返回内函数,对于闭包,在外函数outer中 最后return inner,在调用外函数 demo = outer() 的时候,outer返回了inner,inner是一个函数的引用,这个引用被存入了demo中。所以接下来再进行使用demo() 的时候,相当于使用了inner函数。 一个函数,如果函数名后紧跟一对括号,说明现在就要调用这个函数,如果不跟括号,只是一个函数的名字,里面存了函数所在位置的引用。 ———————————————— 版权声明:本文为CSDN博主「woshini299cjr」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/u013380694/article/details/90019571
def test1(a):
print("--1--") #2触发外函数执行,打印输出"--1--"
def test2(b): #2 内部函数 #6内函数被调用执行,同时传入b = 25
print("--2--") #7顺序执行代码,打印输出"--2--"
print(b) #8传入的实参25赋值给b,同时打印输出25
return b + a #1 外部环境的变量引用 #9将传入函数内的25和20相加为45并返回外部代码
print("--3--") #3延续外函数执行顺序,打印输出"--3--"
return test2 #3 内部函数test2返回 #4碰到return函数,将test2返回出去
res = test1(20) # res的本质是test2 #1外函数test1()被调用,同时将20传入test1
res1 = res(25) #res1的本质是test2() #5返回出去的test2被转化成test2()执行函数调用
print(res1) #10内函数返回值45被res1接收,打印输出45
#out
--1--
--3--
--2--
25
45
#闭包函数的实例
#outer是外部函数 a和b都是外函数的临时变量
def outer(a):
b = 10
#inner是内函数
def inner():
#在内函数中 用到了外函数的临时变量
print(a+b)
#外函数的返回值是内函数的引用
return inner
if __name__ == '__main__':
#在这里我们调用外函数传入参数5
#此时外函数两个临时变量 a=5 b=10,并创建了内函数,然后把内函数的引用返回存给了demo
#外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个
内部函数
demo = outer(5)
#我们调用内部函数,看一看内部函数是不是能使用外部函数的临时变量
#demo存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数
demo() # 15
demo2 = outer(7)
demo2() #17
2. 闭包现象
一般情况下,如果一个函数结束,函数的内部所有东西都会释放掉内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
3. 局部变量修改
- 闭包中内函数修改外函数局部变量
在闭包内函数中,可以随意使用外函数绑定的临时变量,但是如果想修改外函数临时变量数值的时候发现出问题了!
在基本的Python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:
- global 声明全局变量
- 全局变量是可变类型数据的时候可以修改
在闭包内函数也是类似的情况。在内函数中想修改闭包变量(外函数绑定给内函数的局部变量)的时候:
- 在Python3中,可以用nonlocal 关键字声明 一个变量, 表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。
- 在Python2中,没有nonlocal这个关键字,可以把闭包变量改成可变类型数据进行修改,比如列表。
- 举例如下:
1 #修改闭包变量的实例
2 # outer是外部函数 a和b都是外函数的临时变量
3 def outer( a ):
4 b = 10 # a和b都是闭包变量
5 c = [a] #这里对应修改闭包变量的方法2
6 # inner是内函数
7 def inner():
8 #内函数中想修改闭包变量
9 # 方法1 nonlocal关键字声明
10 nonlocal b
11 b+=1
12 # 方法二,把闭包变量修改成可变数据类型 比如列表
13 c[0] += 1
14 print(c[0])
15 print(b)
16 # 外函数的返回值是内函数的引用
17 return inner
18
19 if __name__ == '__main__':
20
21 demo = outer(5)
22 demo() # 6 11
从上面代码中能看出来,在内函数中,分别对闭包变量进行了修改,打印出来的结果也确实是修改之后的结果。以上两种方法就是内函数修改闭包变量的方法。
还有一点需要注意:使用闭包的过程中,一旦外函数被调用一次返回了内函数的引用,虽然每次调用内函数,是开启一个函数执行过后消亡,但是闭包变量实际上只有一份,每次开启内函数都在使用同一份闭包变量。
- 举例如下:
1 #coding:utf8
2 def outer(x):
3 def inner(y):
4 nonlocal x
5 x+=y
6 return x
7 return inner
8
9
10 a = outer(10)
11 print(a(1)) //11
12 print(a(3)) //14
两次分别打印出11和14,由此可见,每次调用inner的时候,使用的闭包变量x实际上是同一个。
4. 闭包用途
- 装饰器!装饰器是做什么的?其中一个应用就是,我们工作中写了一个登录功能,我们想统计这个功能执行花了多长时间,我们可以用装饰器装饰这个登录模块,装饰器帮我们完成登录函数执行之前和之后取时间。
- 面向对象!经历了上面的分析,我们发现外函数的临时变量送给了内函数。大家回想一下类对象的情况,对象有好多类似的属性和方法,所以我们创建类,用类创建出来的对象都具有相同的属性方法。闭包也是实现面向对象的方法之一。在python当中虽然我们不这样用,在其他编程语言入比如avaScript中,经常用闭包来实现面向对象编程
- 实现单利模式! 其实这也是装饰器的应用。单利模式毕竟比较高大,需要有一定项目经验才能理解单利模式到底是干啥用的,我们就不探讨了。
———————————————— 版权声明:本文为CSDN博主「woshini299cjr」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/u013380694/article/details/90019571
- 满足哪几个条件形成一个闭包?自己答
#1在外函数中定义了一个内函数
#2外函数的返回值是内函数的引用
#3内函数运用了外函数的临时变量
十一、装饰器
在开发中,为了避免重复造轮子,或者减少重复代码量,从而使用到了这个装饰其功能
由于我们在开发中要遵守封闭开放原则,所以python开发者开始使用装饰器,装饰器也就是说在不改变源代码的情况下为函数添加新的功能。并且使用@符号,@符号为语法糖。
装饰器本身在不改变源代码的情况下,为这个函数添加了新的功能。
1. 装饰器的原型
from functools import wraps
def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logit
def addition_func(x):
print("""Do some math.""")
return x + x
result = addition_func(4)
#out
addition_func was called
Do some math.
import time
def calcu_time(func):
def test_in():
start = time.time()
func()
end = time.time()
print("spend {}".format(end - start))
return test_in
@calcu_time
def test1():
print("---test1---")
time.sleep(2)
test1()
1 import time
2 def showtime(func):
3 def wrapper():
4 start_time = time.time()
5 func()
6 end_time = time.time()
7 print('spend is {}'.format(end_time - start_time))
8
9 return wrapper
10
11 def foo():
12 print('foo..')
13 time.sleep(3)
14
15 foo = showtime(foo)
16 foo()
2. 不带参数的装饰器
(装饰器、被装饰函数都不带参数)
1 import time
2 def showtime(func):
3 def wrapper():
4 start_time = time.time()
5 func()
6 end_time = time.time()
7 print('spend is {}'.format(end_time - start_time))
8
9 return wrapper
10
11 @showtime #foo = showtime(foo)
12 def foo():
13 print('foo..')
14 time.sleep(3)
15
16 @showtime #doo = showtime(doo)
17 def doo():
18 print('doo..')
19 time.sleep(2)
20
21 foo()
22 doo()
3. 带参数的被装饰的函数
1 import time
2 def showtime(func):
3 def wrapper(a, b):
4 start_time = time.time()
5 func(a,b)
6 end_time = time.time()
7 print('spend is {}'.format(end_time - start_time))
8
9 return wrapper
10
11 @showtime #add = showtime(add)
12 def add(a, b):
13 print(a+b)
14 time.sleep(1)
15
16 @showtime #sub = showtime(sub)
17 def sub(a,b):
18 print(a-b)
19 time.sleep(1)
20
21 add(5,4)
22 sub(3,2)
4 带参数的装饰器(装饰函数)
实际是对原有装饰器的一个函数的封装,并返回一个装饰器(一个含有参数的闭包函数)。
当使用@time_logger(3)调用的时候,Python能发现这一层封装,并将参数传递到装饰器的环境去。
1 import time
2 def time_logger(flag = 0):
3 def showtime(func):
4 def wrapper(a, b):
5 start_time = time.time()
6 func(a,b)
7 end_time = time.time()
8 print('spend is {}'.format(end_time - start_time))
9
10 if flag:
11 print('将此操作保留至日志')
12
13 return wrapper
14
15 return showtime
16
17 @time_logger(2) #得到闭包函数showtime,add = showtime(add)
18 def add(a, b):
19 print(a+b)
20 time.sleep(1)
21
22 add(3,4)
5. 类装饰器
- 一般依靠类内部的call方法
1 import time
2 class Foo(object):
3 def __init__(self, func):
4 self._func = func
5
6 def __call__(self):
7 start_time = time.time()
8 self._func()
9 end_time = time.time()
10 print('spend is {}'.format(end_time - start_time))
11
12 @Foo #bar = Foo(bar)
13 def bar():
14 print('bar..')
15 time.sleep(2)
16
17 bar()
6. 使用装饰器的缺点
- 位置错误的代码,不能在装饰器之外添加逻辑功能
- 不能装饰@staticmethod 或者 @classmethod已经装饰过的方法
- 装饰器会对原函数的元信息进行更改,比如函数的docstring,name,参数列表
下面对装饰器第第三个缺点进行解析:
1 import time
2 def showtime(func):
3 def wrapper():
4 start_time = time.time()
5 func()
6 end_time = time.time()
7 print('spend is {}'.format(end_time - start_time))
8
9 return wrapper
10
11 @showtime #foo = showtime(foo)
12 def foo():
13 print('foo..')
14 time.sleep(3)
15
16 def doo():
17 print('doo..')
18 time.sleep(2)
19
20 print(foo.__name__)
21 print(doo.__name__)
#out
wrapper
doo
由此可以看出,装饰器会对原函数的元信息进行更改,可以使用wraps,进行原函数信息的添加
注解wraps本身也是装饰器,他能把函数的元信息拷贝到装饰器函数中使装饰器函数与原函数有一样的元信息
以下是一个wraps的例子
1 import time
2 from functools import wraps
3 def showtime(func):
4
5 @wraps(func)
6 def wrapper():
7 start_time = time.time()
8 func()
9 end_time = time.time()
10 print('spend is {}'.format(end_time - start_time))
11
12 return wrapper
13
14 @showtime #foo = showtime(foo)
15 def foo():
16 print('foo..')
17 time.sleep(3)
18
19 def doo():
20 print('doo..')
21 time.sleep(2)
22
23 print(foo.__name__)
24 print(doo.__name__)
#out
foo
doo
7. 常用的内置装饰器
1. staticmethod:
类似实现了静态方法,注入以后可以直接 : 类名.方法
2. property:
经过property装饰过的函数不再是一个函数,而是一个property,类似实现get,set方法
1 @property
2 def width(self):
3 return self.__width
4
5 @width.setter
6 def width(self, newWidth):
7 self.__width = newWidth
3. classmethod:
与staticmethod很相似,貌似就只有这一点区别: 第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。 ———————————————— 版权声明:本文为CSDN博主「woshini299cjr」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/u013380694/article/details/90019571
- 实现为增加商品函数添加是否登录正确的判断功能:当为登录状态时,则直接打印输出添加商品;否则让用户登录,判断用户输入的用户名以及密码是否正确;如正确则登录,否则提醒用户输入错误。并且不能添加。 ``` flag = True dic = [{‘name’:’admin’,’password’:’123456’},{‘name’:’guest’,’password’:’000000’}] name_list = [j[‘name’] for i, j in enumerate(dic)] password_list = [j[‘password’] for i, j in enumerate(dic)]
初始化商品列表
trade =[{‘t_name’:’娃哈哈’,’t_prc’:’3.5’}] t_n = [j[‘t_name’] for i,j in enumerate(trade)] t_p = [j[‘t_prc’] for i,j in enumerate(trade)]
定义一个用户名与密码验证的函数
def check(fn): def in_check(): global flag while flag == True: name = input(‘请输入用户名’) password = input(‘请输入密码’) if name in name_list and password == password_list[name_list.index(name)]: print(‘登陆验证成功’) flag = False fn() return flag break else: print(‘用户名或密码密码错误’) return in_check
定义用户信息查询函数
@check def dic_info(): if len(dic) == 0: print(‘-‘10,’无新增用户’,’-‘10) return print(‘=’ 10, ‘管理登陆系统’, ‘=’ 10) print(‘{0:<8}{1:<15}{2:<4}’.format(‘序号’,’用户名’,’密码’)) for i,j in enumerate(dic): print(‘{0:<9}{1:<15}{2:<5}’.format(i+1,j[‘name’],j[‘password’]))
定义商品信息显示函数
def t_info(): if len(trade) == 0: print(‘-‘10,’无新增商品’,’-‘10) return print(‘=’ 10, ‘商品信息清单’, ‘=’ 10) print(‘{0:<8}{1:<15}{2:<4}’.format(‘序号’,’商品名’,’单价’)) for i,j in enumerate(trade): print(‘{0:<9}{1:<15}{2:<5}’.format(i+1,j[‘t_name’],j[‘t_prc’]))
定义一个新增用户列表函数
@check def add_user(): name = input(‘新用户名: ‘) password = input(‘新密码: ‘) dic_1 = {} dic_1[‘name’] = name dic_1[‘password’] = password dic.append(dic_1) dic_info()
定义新增商品信息的函数
@check def add_t(): t_name = input(‘新增商品名称: ‘) t_prc = input(‘新增商品价格: ‘) t_1 = {} t_1[‘t_name’]= t_name t_1[‘t_prc’] = t_prc trade.append(t_1) t_info()
定义退出系统函数
def exit(): pass
定义一个校验装饰器
while True: print(‘‘10,’欢迎登陆信息管理系统’,’‘10) while True: name = input(‘请输入用户名’) password = input(‘请输入密码’) if name in name_list and password == password_list[name_list.index(name)]: print(‘登陆验证成功’) break else: print(‘用户名或密码密码错误’) print(‘‘ 10, ‘欢迎登陆信息管理系统’, ‘‘ 10) print(‘{:1}{:15}{:15}’.format(‘ ‘,’1.用户信息查询’,’2.新增商品信息’)) print(‘{:1}{:15}{:15}’.format(‘ ‘,’3.新增用户信息’,’4.退出系统’)) print(‘=’*40) choose = input(‘请输入选项: ‘) if choose ==’1’: dic_info() print(‘按回车键返回主菜单’) input(‘’) elif choose ==’2’: add_t() print(‘新增成功!按回车键返回主菜单’) elif choose == ‘3’: add_user() print(‘新增成功!按回车键返回主菜单’) elif choose == ‘4’: exit() print(‘欢迎下次使用!’) break else: print(‘无效操作,请重新输入选项’)
- [x] 定义一个验证用户名与密码登陆程序,当用户名与密码输入正确方可添加新用户信息,添加成功后输出最新的用户名与密码信息。
```python
li1=[{'name':'admin','password':'000000'}]
Flag = True
def check(func):
def check_in():
while True:
print(' '*11,'请登陆验证')
name =input('name: ')
password = input('password: ')
if name in [j['name'] for i,j in enumerate(li1)] and password == [j['password'] for i,j in enumerate(li1)][[j['name'] for i,j in enumerate(li1)].index(name)]:
print('*'*11,'验证成功','*'*11)
func()
break
else:
print('密码错误,请重新输入')
return check_in
@check
def add():
global Flag
while Flag == True:
print('*'*10,'请录入信息','*'*10)
name = input('name(纯字母组合): ')
password = input('password(纯数字组合): ')
if name.isalpha() and password.isdigit():
li = {}
li['name'] = name
li['password'] = password
li1.append(li)
print('{0:<5}{1:<10}{2:<10}'.format('序号','姓名','密码'))
for i,j in enumerate(li1):
print('{0:<5}{1:<10}{2:<10}'.format(i+1,j['name'],j['password']))
a = input('是否继续添加?Y/N')
if a.upper()=='Y':
Flag = True
elif a.upper() =='N':
Flag = False
break
else:
print('选择有误,请重新输入')
else:
print('输入有误,请重新输入')
print('*'*10,'欢迎登陆系统','*'*10)
add()