0401py学习新知点:
待实践验证:1)函数为入参 2)闭包的使用 3)嵌套函数的应用场景
1. 赋值,得到的是什么,不是copy,只是多了一个变量指向同一个地址
2. 可变和不可变类型数据,在内存的存储过程
3. 深copy和浅copy
背景:1)拷贝一个原列表产生一个新列表
2)想让2个列表完全独立,针对的改的操作的独立而不是读的操作(如果只是读的话就没必要完全独立,也占用内存)
浅copy:拷贝时,不区分可变和不可变数据类型,只拷贝原列表第一层的内存地址完全都拷贝。
如果原列表里的元素都是不可变类型,不会影响新列表,因为给可变类型赋值时,都会重新生成新的值,把原来指向的内存地址解绑掉,再重新指向新的值。
如果原列表元素有可变类型,原和新列表的地址都是同一个,就会粘在一起,没有完全分离。
深copy:拷贝时,区分可变和不可变类型,用不同的方式去拷贝可变和不可变类型,实际是主要对可变类型的区分操作。
针对不可以变类型,还是同一个地址,如果是可变类型(容器),就会新建地址
改操作,永远是产生新值,不会更改原来的地址
举例
import copy
list1=['egon','happy',[66,88]]
print(list1)
print(id(list1),id(list1[0]),id(list1[1]),id(list1[2]),id(list1[2][0]),id(list1[2][1]), sep=' , '):
例子1:纯拷贝
list3=copy.deepcopy(list1)
print(list3)
print(id(list3),id(list3[0]),id(list3[1]),id(list3[2]),id(list3[2][0]),id(list3[2][1]), sep=' , ')
打印:
['egon', 'happy', [66, 88]]
2525749348480 , 2525749388464 , 2525749353776 , 2525749348992 , 2525747237264 , 2525747237968
~~~~~~~~~~~~~~
['egon', 'happy', [66, 88]]
2525749432000 , 2525749388464 , 2525749353776 , 2525749429696 , 2525747237264 , 2525747237968
变 没变 没变 变 没变 没变
例子2:修改了list1,查看list3是否变化
list1[0]='sqq'
list1[2][0]=111
list1[2][1]=222
print(list1)
print(id(list1),id(list1[0]),id(list1[1]),id(list1[2]),id(list1[2][0]),id(list1[2][1]), sep=' , ')
print('~~~~2. ~修改了list1,查看list3是否变化~~~~~~~~~')
print(list3)
print(id(list3),id(list3[0]),id(list3[1]),id(list3[2]),id(list3[2][0]),id(list3[2][1]), sep=' , ')
打印:
['sqq', 'happy', [111, 222]]
2209437812800 , 2209437857392 , 2209437818096 , 2209437813312 , 2209435703088 , 2209435706704
~~~~~修改了list1,查看list3是否变化~~~~~~~~~
['egon', 'happy', [66, 88]]
2209437896512 , 2209437856880 , 2209437818096 , 2209437894208 , 2209435701648 , 2209435702352
结果:修改的列表修改的值id变了,不会影响别的已经深拷贝的列表,因为修改的是不可变类型,如果给不可变类型重新赋值,那就是新开辟的新值的空间后它自己指向了新值,别的列表的指向还是原来的值的地址,没有影响
4. if 表达式为真,除了空,None,False,0外都是真,不需要记录
真:True 假:False
(1)False
(2)None
(3)0
(4)’’,即空字符串
(5)(),即空元组
(6)[],即空列表
(7){},即空字典
除了以上7种等为假,而其余任何值都视为真
5. 变量在内存中浅浅的理解
X=10,把值10放在堆内存中开一个小房子,10就是房子的东西,房间号就算内存地址101010号,栈空间放的是变量名,然后给个内存地址,,然后 通过关联的内存地址去找那个值;
6. 解释器给常用内存自动开辟的内存空间,称为【小整数池】-5~200直接的整数,num1=10,num2=10,10是同一个内存地址。
7. 解释器的垃圾回收机制
什么样的是垃圾?没有直接或间接引用的值称为垃圾。怎么判断用基数法,只要基数不为0就是有效,否则就算垃圾。如果一直轮询检查全部的值是不是垃圾,工程太大,所以用标记分代回收法,分为新生代>青年代>老年代。
8.while循环
8.1 循环的语法和基本应用
语法:
while 条件:
代码1
代码2
代码3
…
8.2 死循环和效率问题
while True:
print(1+1)
打印:
2
2
.....
小结:
重复做一个操作,不能停止就是死循环,所以while循环使用一定要搭配条件控制使用。
纯计算无io的死循环会导致致命的效率问题。
8.3 循环应用
应该解决2个问题,1是哪些是可重复的代码;2哪些判断条件导致做什么事。
举例:需求是输入用户名和密码,都正确登录成功,不正确给3次登录机会
测试1
count = 0
while count < 3:
inp_name = input("请输入用户名:")
inp_pwd = input("请输入密码:")
if inp_name == 'qqq' and inp_pwd == '111':
print("登陆成功")
else:
print("输入的用户名或密码错误!")
count += 1
失败打印:
请输入用户名:1
请输入密码:1
输入的用户名或密码错误!
请输入用户名:1
请输入密码:1
输入的用户名或密码错误!
请输入用户名:2
请输入密码:2
输入的用户名或密码错误!
成功打印
请输入用户名:qqq
请输入密码:111
登陆成功
请输入用户名:
分析:搞点的点是区分了重复代码和条件回应。错误时反而没问题,因为在else里写了次数的累计更新赋值,但是如果第一次就输入正确了,就走到if里的,if里没有次数的累计更新,所以走正确的判断会一直走,反而陷入了死循环了。
新问题,输入对了就不应该重复????
解决方法:见7.4
8.4 退出的2种方式
方式一:将条件改成False
验证1:如果条件变成false后,本次循环体的内容还是会执行完,解释条件变成False后的执行范围
tag=True
while tag:
tag=False
inp_name = input("请输入用户名:")
inp_pwd = input("请输入密码:")
if inp_name == 'qqq' and inp_pwd == '111':
print("登陆成功")
else:
print("输入的用户名或密码错误!")
结果打印:
请输入用户名:1
请输入密码:2
输入的用户名或密码错误!
验证2:结合上述例子,输入对了就不应该重复的问题
tag=True
while tag:
inp_name = input("请输入用户名:")
inp_pwd = input("请输入密码:")
if inp_name == 'qqq' and inp_pwd == '111':
print("登陆成功")
tag = False
else:
print("输入的用户名或密码错误!")
结果打印:
请输入用户名:qqq
请输入密码:111
登陆成功
方式二:break,只要运行到break就立刻终止本层循环
验证1:
tag=True
while tag:
inp_name = input("请输入用户名:")
inp_pwd = input("请输入密码:")
if inp_name == 'qqq' and inp_pwd == '111':
print("登陆成功")
tag = False
break #只要运行到break,立刻终止本层循环
else:
print("输入的用户名或密码错误!")
print('走完判断走的打印。。。。。。。。。。。')
加break,结果打印:
请输入用户名:qqq
请输入密码:111
登陆成功
不加break,结果打印:
请输入用户名:qqq
请输入密码:111
登陆成功
走完判断走的打印。。。。。。。。。。。
验证2:while嵌套+break
count=0
while count < 3: # 第一层循环
inp_name = input("请输入用户名:")
inp_pwd = input("请输入密码:")
if inp_name == 'qqq' and inp_pwd == '111':
print("登陆成功")
while True: # 第二层循环
cmd = input('>>: ')
if cmd == 'aa':
break # 用于结束本层循环,即第二层循环
print('run <%s>' % cmd)
break # 用于结束本层循环,即第一层循环
else:
print("输入的用户名或密码错误!")
count += 1
(把break注释掉执行)不加第二个break,结果打印:
请输入用户名:qqq
请输入密码:111
登陆成功
>>: qq
run <qq>
>>: aa
请输入用户名:qqq
请输入密码:111
登陆成功
>>: aa
请输入用户名:
(把break注释放开)加第二个break,结果打印:
请输入用户名:qqq
请输入密码:111
登陆成功
>>: qq
run <qq>
>>: aa
8.5 while循环嵌套
while循环嵌套+tag的使用
针对嵌套多层的while循环,如果我们的目的很明确就是要在某一层直接退出所有层的循环,其实有一个窍门,就让所有while循环的条件都用同一个变量,该变量的初始值为True,一旦在某一层将该变量的值改成False,则所有层的循环都结束
count = 0
tag = True
while tag:
inp_name = input("请输入用户名:")
inp_pwd = input("请输入密码:")
if inp_name == 'qqq' and inp_pwd == '111':
print("登陆成功")
while tag:
cmd = input('>>: ')
if cmd == 'quit':
tag = False # tag变为False, 所有while循环的条件都变为False
break
print('run <%s>' % cmd)
break # 用于结束本层循环,即第一层循环
else:
print("输入的用户名或密码错误!")
count += 1
结果打印:
请输入用户名:qqq
请输入密码:111
登陆成功
>>: quit
实际工作时不会有这种多层嵌套的使用,不易读
break只会中止当前层的while循环的执行,如果多个while嵌套,每层都要有个中止break处理。
验证2
while True:
while True:
while True:
break
break
break
8.6 while + continue
break代表结束本层循环,而continue则用于结束本次循环,直接进入下一次循环
number=5
while number>1:
number -= 1
if number==3:
continue # 结束掉本次循环,即本次循环continue之后的代码都不会运行了,而是直接进入下一次循环
print(number)
结果打印:
4
2
1
8.7 while + else
在while循环的后面,我们可以跟else语句,当while 循环正常执行完并且中间没有被break 中止的话,就会执行else后面的语句,所以我们可以用else来验证,循环是否正常结束
验证1,没有break,else里正常被执行
number=5
while number>1:
number -= 1
print("Loop",number)
else:
print("循环正常执行完啦")
print("-----out of while loop ------")
结果打印:
Loop 4
Loop 3
Loop 2
Loop 1
循环正常执行完啦
-----out of while loop ------
验证2,有break,else里没有执行
number=5
while number>1:
number -= 1
if number==2:
break
else:
print("Loop",number)
else:
print("循环正常执行完啦")
print("-----out of while loop ------")
结果打印:
Loop 4
Loop 3
-----out of while loop ------
小练习:寻找1到100之间数字7最大的倍数(结果是98)
方法1:
max_num=0
num=1
while num<=100:
if num % 7 == 0:
if max_num<num:
max_num=num
print('目前最大值:{0},num的值:{1}'.format(max_num,num))
num+=1
打印:
目前最大值:7,num的值:7
目前最大值:14,num的值:14
目前最大值:21,num的值:21
目前最大值:28,num的值:28
目前最大值:35,num的值:35
目前最大值:42,num的值:42
目前最大值:49,num的值:49
目前最大值:56,num的值:56
目前最大值:63,num的值:63
目前最大值:70,num的值:70
目前最大值:77,num的值:77
目前最大值:84,num的值:84
目前最大值:91,num的值:91
目前最大值:98,num的值:98
```python
方法2:
number=100
while number>0:
if number%7==0:
print(number)
break
number-=1
打印:
98
小结:方法2的方法太好了,简便高效,虽然2个方法都是能实现功能,但是从轮询的次数来说方法1太浪费了,所以实现功能的同时也要看怎么实现更省力更迅速。。因为找最大值,从1开始肯定是从小的开始,所以方法2反过来轮询,第一个符合判断的就是最大值。------------------ 思路很重要
9. 函数的对象
理解:函数加括号,例如func(),就是调用使用这个函数,只有func时,是对这个对象本身,func也相当于一个变量名,有一个独立的空间存储生成一个内存地址记录。 如果f=func,就是f也有了func的内存地址f()时也能调用func()相同的内容。
9.1 可以将函数当做变量使用,就是赋值
--当你将函数作为参数传递时,你实际上是传递了函数对象的引用,而不是函数的执行结果。
#func=内存地址
def func():
print('这是一个函数···')
#1.可以赋值
ff=func() #赋值不是这样的,这是调用函数,接收返回值
print(ff)
'''
这是一个函数···
None
'''
f=func # 是这样 ,f和func同指向一个内存地址。是赋值
print(f)
print(func)
'''
<function func at 0x0000028BDD0AB4C0>
<function func at 0x0000028BDD0AB4C0>
'''
9.2 可以将函数当做参数
感想:说实话,我能get这个语法点,但现在还get不到这个点的便利性。后期追踪观察吧。
举例1
def func():
print('这是一个函数···')
def foo(x):
print(x) #x = func的内存地址
foo(222)
foo(func) #foo(func的内存地址)
'''
222
<function func at 0x000001744F2AB4C0>
'''
举例2
# 定义一个简单的函数,它接受一个参数并返回该参数的平方
def square(x):
return x ** 2
# 定义一个高阶函数,它接受一个函数作为参数,并调用它来处理给定的值
def apply_function(func, value):
return func(value)
# 使用 apply_function 来应用 square 函数到数字 5 上
result = apply_function(square, 5)
print(result) # 输出:25
result2=square(5)
print(result2)
'''
25
25
'''
9.3 函数可以当做另外一个函数的返回值
def func():
print('这是一个函数···')
def foo2(x):
return x
res=foo2(func)
print(res)
打印:
<function func at 0x0000024E3DD6B4C0>
9.4 可以当做一个容器的元素
9.4.1 列表
def func():
print('这是一个函数···')
l=[func,'hh','次第花开']
print(l)
print(l[0]) #访问第一个元素,函数元素地址
print(l[0]()) #调用函数
打印:
[<function func at 0x00000277DACDB4C0>, 'hh', '次第花开']
<function func at 0x00000277DACDB4C0>
这是一个函数···
None
9.4.2 字典
def func():
print('这是一个函数···')
d= {'k1':func,'k2':'hhhh'}
print(d)
print(d['k1'])
print(d['k1']())
打印:
{'k1': <function func at 0x000001C01D20B4C0>, 'k2': 'hhhh'}
<function func at 0x000001C01D20B4C0>
这是一个函数···
None
9.5 小练习
需求:写个登录、查询、新建、复制,审批功能,然后根据不同的提示,调用不同方法
# 登录、查询、新建、复制,审批功能
def login():
print('登录了***')
def select_for():
print('查询****')
def add_info():
print('新建****')
def copy_info():
print('复制****')
def approve():
print('审批****')
9.5.1 版本1(只是实现功能)
#版本1
while True:
print('''
0:退出
1:登录
2:查询
3:新建
4:复制
5:审批
''')
choose = input('请输入选择编号:')
if choose =='0':
print('退出****')
break
elif choose =='1':
login()
elif choose =='2':
select_for()
elif choose =='3':
add_info()
elif choose =='4':
copy_info()
elif choose =='5':
approve()
else:
print('输入不合法,请重新输入')
打印:
请输入选择编号:1
登录了***
0:退出
1:登录
2:查询
3:新建
4:复制
5:审批
请输入选择编号:0
退出****
9.5.2 版本2(取值和调用不写死,可延展)
#版本2:提示和功能都做成可操作延展性的。
dic = {'1':['登录',login],'2':['查询',select_for],'3':['新建',add_info],'4':['复制',copy_info],'5':['审批',approve]}
while True:
for i in dic:
print(i,dic[i][0])
# print(
# dic['1'],dic['1'][0],
# dic['2'],dic['2'][0],
# dic['3'],dic['3'][0],
# dic['4'],dic['4'][0]
# )
choose = input('请输入选择编号:')
if choose == '0':
print('退出****')
break
if choose in dic:
dic[choose][1]()
else:
print('输入不合法,请重新输入')
打印:
...
3 新建
4 复制
5 审批
请输入选择编号:1
登录了***
1 登录
2 查询
3 新建
4 复制
5 审批
请输入选择编号:0
退出****
10 闭包
大前提:闭包函数=名称空间与作用域+函数嵌套+函数对象
10.1 什么是闭包函数?
判断1)“闭”函数指的该函数就是内嵌函数
判断2)“包”函数指的该函数包含对外层函数作用域下变量的引用(不是对全局作用域)
形象了:f1是个麻袋,里面有几包方便面,又把f2装进麻袋,f2饿了话就那里面的方便面吃了
10.1.1 初识一下
def f1():
x=3333333333 #外层作用域下变量
def f2(): #1.内嵌函数
print(x) #2. 对外层作用域下变量的引用
f2()
f1()
打印:
3333333333
10.1.2 体验闭包的名称空间与作用域+函数嵌套,根据判断2)来确定,只与外层有关
例子1
def f1():
x=3333333333 #外层作用域下变量
def f2(): #1.内嵌函数
print(x) #2. 对外层作用域下变量的引用
f2()
x=111
f1()
打印:
3333333333
例子2:
def f1():
x=3333333333 #外层作用域下变量
def f2(): #1.内嵌函数
print(x) #2. 对外层作用域下变量的引用
f2()
x=111
def foo():
x=222
f1()
foo()
打印:
3333333333
例子3:
def f1():
x=3333333333 #外层作用域下变量
def f2(): #1.内嵌函数
print(x) #2. 对外层作用域下变量的引用
f2()
x=111
def bar():
x=444
f1()
def foo():
x=222
bar()
foo()
打印:
3333333333
总结:为啥结果还是3333333333,因为无论怎么调,调多少次,他们都不是f2的包都改变不了包的本质。 在定义阶段看被谁包了。调用不是关键,以定义的时候为准。
问题:10.1.1里调用只能再f1中调用,为了让F2调用更灵活,在任何作用域都可以调(不是调整作用域关系)所以怎么办?————可以返回f2,这样就可以调了
10.1.2 体验闭包的函数对象
结论1:打破了层级限制
结论2:无论哪里调,都不会影响他只取外层作用域的变量
def f1():
x=3333333333 #外层作用域下变量
def f2(): #1.内嵌函数
print(x) #2. 对外层作用域下变量的引用
print('F2的内存地址= ',f2)
return f2
f=f1()
print('f的内存地址= ',f)
f() #调用 看下
打印:
F2的内存地址= <function f1.<locals>.f2 at 0x000001F000A6B790>
f的内存地址= <function f1.<locals>.f2 at 0x000001F000A6B790>
3333333333
#加深
def foo():
x=555 #和其他位置的x都没有关系
f()
foo()
打印:
3333333333
总结闭包的作用:学了闭包在哪里用呢?实际是掌握了一种为函数传参的方式。
10.1.3 闭包可以多层
举例1:我写的
def f1():
f11=100
def f2():
f22=200
print(f11,' , ',f22)
def f3():
print(f11,' , ',f22,300)
return f3
return f2
a=f1()() #方式1
a()
print('-------')
b=f1() #方式2
bb=b()
bb()
打印:
100 , 200
100 , 200 300
-------
100 , 200
100 , 200 300
举例2:问心一言问的
def outer_function(x):
def middle_function(y):
def inner_function(z):
return x + y + z
return inner_function
return middle_function
# 创建一个三层嵌套闭包
closure = outer_function(1)(2)(3)
# 使用闭包
print(closure) # 输出 6
在这个例子中:
outer_function 是最外层的函数,它接受一个参数 x。
middle_function 是在 outer_function 内部定义的中间层函数,它接受一个参数 y。
inner_function 是在 middle_function 内部定义的最内层函数,它接受一个参数 z。
inner_function 可以访问所有外层函数的变量:x、y 和它自己的 z。
当我们调用 outer_function(1) 时,它返回 middle_function 的一个实例,其中 x 的值被绑定为 1。然后,我们调用这个返回的 middle_function 实例并传入 2,它返回 inner_function 的一个实例,其中 y 的值被绑定为 2。最后,我们调用这个返回的 inner_function 实例并传入 3,它返回 x + y + z 的结果,即 1 + 2 + 3,输出结果为 6。
通过这种方式,你可以创建任意多层嵌套的闭包,每层闭包都可以访问其外部作用域的变量。这种能力使得闭包在 Python 中成为实现诸如装饰器、回调函数和工厂函数等高级功能的重要工具。
11 2种为函数传参的方式
11.1 方式一:直接把函数体需要的参数定义成形参
def f1(x):
print('打印了:',x)
f1(1100)
f1('每天都更好')
打印:
打印了: 1100
打印了: 每天都更好
11.2 方式二 :曲线救国,利用名称空间和作用域的作用包给函数用
f和f2指向同一个内存地址,所以相当于在全局使用到这个f2的功能了,怎么使用可以在其他地方被“加减乘除”了,拿到值或一段逻辑就可以发挥了
def f1():
x=888
def f2():
print(x)
return f2
f=f1() #f和f2指向同一个内存地址,所以相当于在全局使用到这个f2的功能了,怎么使用可以在其他地方被“加减乘除”了,拿到值或一段逻辑就可以发挥了
f()
打印:
888
例子2,加深
def f1():
x=888
def f2():
return x
return f2
f=f1() #额外的点:先定义一个变量接收,不然没有引用就会被回收掉了
ff=f()
print('ff是接收函数返回值:',ff)
print('直接打印函数f调用:',f())
print(type(f()))
def foo():
print(f()+666)
foo()
打印:
ff是接收函数返回值: 888
直接打印函数f调用: 888
<class 'int'>
1554
额外的知识点:
f和y是2个不同的变量引用,没有关系
def f1(x):
def f2():
return x
return f2
f=f1(111) #再额外的点:先定义一个变量接收,不然没有引用就会被回收掉了
ff=f()
print('f的id:',id(f))
print('ff是接收函数返回值:',ff)
y=f1(222)
yy=y()
print('f的id:',id(y))
print('ff是接收函数返回值:',yy)
打印:
f的id: 2270485985168
ff是接收函数返回值: 111
f的id: 2270485985312
ff是接收函数返回值: 222
12 区分直接打印返回值,接收再打印
12.1 有返回值
通过变量接收函数的返回值,或者直接打印函数调用是一样的。
返回值类型是什么类型给函数里的变量有关。
例子1:
def f1():
x=10
y=20
return x+y
f=f1()
print('f是接收函数返回值:',f,type(f))
print('直接打印函数f1调用:',f1(),type(f1()))
打印:
f是接收函数返回值: 30 <class 'int'>
直接打印函数f1调用: 30 <class 'int'>
例子2:
def f1():
x='10'
y="20"
return x+y
f=f1()
print('f是接收函数返回值:',f,type(f))
print('直接打印函数f1调用:',f1(),type(f1()))
打印:
f是接收函数返回值: 1020 <class 'str'>
直接打印函数f1调用: 1020 <class 'str'>
12.2 没有返回值
def f1():
x='10'
y="20"
print(x+y)
f1() #只需要调用函数即可
print('---------')
print(f1())
#这里是既调用了函数,又打印了,所以会先打印1020,又有个none,因为没有返回值
打印:
1020
---------
1020
None
补充:
背景:函数里既有代码体的输出,又有返回值
结论:不管有没有返回值,函数调用的逻辑都是先执行代码体+接收返回值
import time
def index():
time.sleep(3)
print('Welcome to the index page')
return 200
index() #输出1:只调用,执行逻辑执行代码体+返回值,但只打印代码体
res=index()
print(res) #输出2:接收函数返回值,执行逻辑执行代码体+返回值,调用函数体+返回值输出
print(index()) #输出3: 同2,只是方式不同
打印:
Welcome to the index page #输出1:
Welcome to the index page #输出2:
200
Welcome to the index page #输出3:
200
13 名称空间和作用域
名称空间和作用域的“嵌套”关系是在函数定义阶段,即检测语法时确定的。
例子待补充…
14 *args 和 **kwargs在函数中的使用场景和例子
例子待补充…
15 装饰器
15.1 什么是装饰器?
什么是装饰器?
器,就是一个容器,可以是一个函数;
装饰,为其他事物添加额外的点缀;
总结:指定义一个函数,该函数用来为其他函数添加额外功能。
装饰器的原则?
开放封闭原则
开放:对扩展功能是开放的
封闭:不能修改原函数的代码体功能和调用方式(例如:index(x)—>index(x,y)不能的)
总结:装饰器就是在不修改被装饰器对象源代码以及调用方式的前提下,为被装饰对象添加新功能。
15.2 无参装饰器 分析过程…
#原函数
def index(x,y):
time.sleep(3)
print('index函数里了的被执行了x=%s,y=%s'%(x,y))
需求:在下面函数执行时,我想知道执行代码的时间。
第一直觉的方法1:
start_time=time.time()
index(55,44) #函数执行
stop_time=time.time()
print('运行时间 %s' %(stop_time-start_time))
打印:
index函数里了的被执行了x=55,y=44
运行时间 3.016044855117798
结论&思考:哈哈,这是我的第一方法,老实说自己看了下,确实是实现了需求,但是呢?比较low。
如果100个地方需要统计这个时间,就要执行100次重复代码,确实low爆了。写代码的默认规则,就是代码简洁且优雅,代码冗余,完全不符合代码要求。 ——结论:失败。
所以考虑把重复代码封装起来?
方法2:
def wrapper():
start_time=time.time()
index(100,101)
stop_time=time.time()
print('运行时间: %s' %(stop_time-start_time))
wrapper()
打印:
index函数里了的被执行了x=100,y=101
57
结论&思考:确实改进了,把重复的代码做了封装,实现了我们统计运行时长的效果。但是又违背了装饰器的原则,改变了原函数的调用方式,还产生了更多问题:
问题1,函数的参数也写死了,不通用,万一原函数就是功能变了,wrapper就不支持了;
问题2,wrapper里的index还被写死了,只能装饰1个函数;
问题3,本来调index(56)可以实现,现在的wrapper()才能实现,调用变了;
——结论:失败。
所以考虑要吧wrapper封装成和index函数一模一样,不仅实现了原来的代码逻辑,调用方式也不能改变,还要新增我们的统计时间的功能?
方法2下——先优化解决问题1:
问题1 方法1 ——失败
def wrapper(a,b):
start_time=time.time()
index(a,b)
stop_time=time.time()
print('运行时间: %s' %(stop_time-start_time))
wrapper(10,11)
打印:
index函数里了的被执行了x=10,y=11
运行时间: 3.0160603523254395
结论:该方法还是不行,虽然把入参都写成了变量,但是调用时,不支持源代码的通用,
加入源代码减值或加值是不能满足的。
问题1 方法2 ——成功 《将参数写活了》
#原函数---增加了参数z
def index(x,y,z):
time.sleep(3)
print('index函数里了的被执行了x=%s,y=%s,新增z=%s'%(x,y,z))
def wrapper(*args,**kwargs):
start_time=time.time()
index(*args,**kwargs)
stop_time=time.time()
print('运行时间: %s' %(stop_time-start_time))
wrapper(10,11,12)
#wrapper(10,z=11,y=12)
打印:
index函数里了的被执行了x=10,y=11,新增z=12
运行时间: 3.000582218170166
#原函数---减少了参数y
def index(x):
time.sleep(3)
print('index函数里了的被执行了x=%s'%(x))
def wrapper(*args,**kwargs):
start_time=time.time()
index(*args,**kwargs)
stop_time=time.time()
print('运行时间: %s' %(stop_time-start_time))
wrapper(10)
打印:
index函数里了的被执行了x=10
运行时间: 3.005002498626709
方法2下——先优化解决问题2
为了把被装饰对象写活,使得不止装饰index函数,也可以装饰inde2,index3等函数。
原代码
def index(x,y,z):
time.sleep(3)
print('index函数里了的被执行了x=%s,y=%s,新增z=%s'%(x,y,z))
原装饰代码:
def wrapper(*args,**kwargs):
start_time=time.time()
index(*args,**kwargs)
stop_time=time.time()
print('运行时间: %s' %(stop_time-start_time))
wrapper(10,11,12)
分析思路:能不能把index函数写成变量func,然后作为参数传入?写活函数index,一定是要变成变量没问题,但是作为wrapper函数的参数可不可以?
试改一下装饰代码1 --失败
def wrapper(*args,**kwargs):
start_time=time.time()
fun(*args,**kwargs)
stop_time=time.time()
print('运行时间: %s' %(stop_time-start_time))
wrapper(22,23,24,index)
分析实现效果:
从wrapper(*args,**kwargs)参数本身作用角度看,*args,**kwargs可变参数的很强大,能接受任意类型 ,接收wrapper(22,23,24,index) 的参数,没有问题。
从wrapper函数本身的作用目的是什么来看,是实现index一样的效果,基本是调用+实现功能外,额外加其他功能的作用。要遵循index的传参规则,index本来就3个参数,现在就变成了4个,所以业务角度来说wrapper(args,**kwargs)的args,**kwargs是一个菜鸟代收点,把调用时的拿到几个包裹,然后原封不动的给到index(args,**kwargs)的args,**kwargs,然后代码体拿到实参,执行代码。
所以联想到函数传参的2种方式,第一种就是直接变量传参,第二种就是闭包形式传参。
第一种方式行不通,用第二种方式试下呢?
试改一下装饰代码2:
def outter(func):
#func=index
def wrapper(*args,**kwargs):
start_time=time.time()
func(*args,**kwargs)
stop_time=time.time()
print('运行时间: %s' %(stop_time-start_time))
return wrapper #返回wrapper就相当于index功能+额外功能
f=outter(index) #f=wrapper =index
f(22,23,24)
print(f)
打印:
index函数里了的被执行了x=22,y=23,新增z=24
运行时间: 3.002682685852051
<function outter.<locals>.wrapper at 0x0000021DFF8EB8B0>
分析:
a)为什么要return wrapper ?因为wrapper是全局函数,只是为了给wrapper一个他内部需要的参数,缩进成了内嵌函数,为了在全局也能访问wrapper,所以要返回wrapper。
b) 怎么拿到wrapper?调谁?f=outter(index) f=wrapper函数的内存地址;
(outter就是wrapper的袋子,把袋子用绳子绑起来,func就是包里的食物,wrapper就只能拿包里的食物吃,只给wrapper吃,index就是wrapper需要的食物变量,替换wrapper的形参func)
func=index的内存地址;
c)f(22,23,24) ,运行f
试改一下装饰代码3: 《把函数变量写活了》
背景:根据 试改一下装饰代码2里的代码再精简下,接着分析
def index(x,y,z):
time.sleep(3)
print('index函数里了的被执行了x=%s,y=%s,新增z=%s'%(x,y,z))
print('原函数的index地址:',index)
def outter(func):
#func=index
def wrapper(*args,**kwargs):
start_time=time.time()
func(*args,**kwargs)
stop_time=time.time()
print('运行时间: %s' %(stop_time-start_time))
return wrapper
index=outter(index)
index(33,34,35)
print('装饰后的变量index地址:',index)
打印:
原函数的index地址: <function index at 0x000001973B73B8B0>
index函数里了的被执行了x=33,y=34,新增z=35
运行时间: 3.014981985092163
装饰后的变量index地址: <function outter.<locals>.wrapper at 0x000001973B73B9D0>
分析:f实际是个变量名称,f也好,a也好,index也好。如果是index,和原函数名称一样呢?
调用时 实际看不出不是同一个index,调用方式也没改变。实际是不一样的,通过打印结果可以看到原函数的index地址,和装饰封装后的赋值的函数地址是不一样的。index(33,34,35)。
index(x=33,y=34,z=35),index(100,134,135)都可以 。偷梁换柱成功。
装饰器:最终版本 (无参)
背景:如果原函数有返回值就有点小瑕疵,兼容下
def index(x,y,z):
time.sleep(3)
print('index函数里了的被执行了x=%s,y=%s,新增z=%s'%(x,y,z))
return x+y+z
print('----')
def index2(x,y,z):
time.sleep(3)
print('index函数里了的被执行了x=%s,y=%s,新增z=%s'%(x,y,z))
def outter(func):
#func=index
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('运行时间: %s' %(stop_time-start_time))
return res
return wrapper
index=outter(index)
a =index(33,34,35)
print(a)
print('---------')
index=outter(index2)
a =index(33,34,35)
print(a)
打印:
----
index函数里了的被执行了x=33,y=34,新增z=35
运行时间: 3.014542818069458
102
---------
index函数里了的被执行了x=33,y=34,新增z=35
运行时间: 3.00526762008667
None
分析:装饰器里封装了返回值,不管被装饰代码有没有返回值都适用。
15.3 装饰器: 无参 模板
def outter(func):
#func=index
def wrapper(*args,**kwargs):
xxx
res=func(*args,**kwargs)
xxx
return res
return wrapper
注意:配合语法糖即可
15.4 语法糖
问题:每个原函数适用装饰器都要偷梁换柱一下,有没有更好的办法?
#装饰器
def outter(func):
#func=index
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('运行时间: %s' %(stop_time-start_time))
return res
return wrapper
#原函数1和2
@outter # 即index = outter(index)
def index(x,y,z):
time.sleep(3)
print('index函数里了的被执行了x=%s,y=%s,新增z=%s'%(x,y,z))
return x+y+z
def home(name):
time.sleep(3)
print('home函数 %s'%(name))
#偷梁换柱
# index = outter(index)
home = outter(home)
index(11,22,33)
home('sqq')
语法糖语法: 在被装饰对象正上方,单独一行,写@装饰器名字 ,不要加装饰器函数括号
说明1)装饰器要放原函数上面 ,否则 NameError: name ‘outter’ is not defined
说明2)执行到@时就会执行装饰器,想当与执行了outter装饰器函数,用把下面的函数名当参数执行调用函数再赋值给下面函数名相同的变量名 即index = outter(index)
说明3)在执行@outter时,会不会执行下面的index函数?
不会,只会把函数名拿了给到outter 然后执行装饰器里的内容。
问题:写了装饰器后,我又想执行装饰器里的内容,又有时候不用,该怎么做?
可以加多个装饰器吗?
15.5 wraps装饰器补充
说明:偷梁换柱,即将原函数名指向的内存地址偷梁换柱成wrapper函数。所以应该讲wrapper做的跟原函数一样。
#装饰器
def outter(func):
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('运行时间: %s' %(stop_time-start_time))
return res
return wrapper
验证:没有使用装饰器时,打印原函数的name和文档内容
#原函数
def home(name):
'''
这里是原函数home的doc内容---'''
time.sleep(1)
print('home函数 %s'%(name))
print(home.__name__)
print(home.__doc__)
打印:
home
这里是原函数home的doc内容----
验证:有使用装饰器时,打印原函数的name和文档内容
#原函数
@outter
def home(name):
'''
这里是原函数home的doc内容---
'''
time.sleep(1)
print('home函数 %s'%(name))
print(home.__name__)
print(home.__doc__)
打印:
wrapper
None
问题来了,表象上假装装饰的wrapper很与index一样,实际函数自带的内置函数却有问题。
应该要做成与原函数一模一样。
上述问题里应该这样:
index._ _ name _ = 原函数. _ name _ _
index._ _ doc _ _ = 原函数._ _ doc _ _
…
而且这些操作应该在哪里就做?
肯定不应该什么都装饰完了,再次给函数的每个内置函数赋值,不仅内置函数太多,赋值起来也麻烦。
而且这些操作应该在装饰器里就完成了,既然伪装成index原函数,内置函数也不例外。
————目标把原函数的全部内置函数都复制给wrapper。
方法:
from functools import wraps
@wraps(func)
from functools import wraps
#wraps
#装饰器
def outter(func):
@wraps(func) #把获取的原函数的所有方法都装饰到wrapper身上
def wrapper(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('运行时间: %s' %(stop_time-start_time))
# wrapper.__name__ = func.__name__ #逐个变量赋值太麻烦
# wrapper.__doc__ = func.__doc__
return res
return wrapper
@outter
def home(name):
'''
这里是原函数home的doc内容----
'''
time.sleep(1)
print('home函数 %s'%(name))
home('ss')
print(home.__name__)
print(home.__doc__)
打印:
home函数 ss
运行时间: 1.0200204849243164
home
这里是原函数home的doc内容----
最终目的,装饰加了就像没加一样,调用的时候完全不知道区别。
15.5 有参装饰器 分析过程…
需求:用户来源不同渠道(pc,app,小程序)登录,用不同的用户验证功能,
#装饰认证功能
def deco(func):
def wrapper(*args,**kwargs):
name = input('请输入用户名:').strip()
pwd = input('请输入密码:').strip()
if channel == 'applet':
if name == 'qq' and pwd == '11':
print('小程序--用户登录认证 successful。开森。。')
res=func(*args,**kwargs)
return res
else:
print('小程序--用户登录认证 error ,囧... ')
elif channel == 'applet':
if name == 'qq' and pwd == '11':
print('app--用户登录认证 successful。开森。。')
res=func(*args,**kwargs)
return res
else:
print('app--用户登录认证 error ,囧... ')
elif channel == 'pc':
if name == 'qq' and pwd == '11':
print('pc--用户登录认证 successful。开森。。')
res=func(*args,**kwargs)
return res
else:
print('pc--用户登录认证 error ,囧... ')
else:
print('登录错误。请重新登录。。')
return wrapper
执行装饰器1
@auth
def index(x,y):
print('index代码体了,输出x={},y={}'.format(x,y))
@deco
def login(x,y):
print('index代码体了,输出x={},y={}'.format(x,y))
@deco
def apply(x,y):
print('index代码体了,输出x={},y={}'.format(x,y))
index(2,3)
login(2,3)
apply(2,3)
打印:
请输入用户名:qq
请输入密码:11
Traceback (most recent call last):
File "D:\EasyProject\a.py基础\装饰器.py", line 164, in <module>
index(2,3)
File "D:\EasyProject\a.py基础\装饰器.py", line 125, in wrapper
if channel == 'applet':
NameError: name 'channel' is not defined
分析:上面使用语法糖后,执行报错了,因为我们认知功能里加了渠道的判断,没有传,所以报错。正常应该在def auth(func)里加个channel参数,def auth(func,channel):但是加了原函数就不能使用语法糖了。但装饰函数有需要参数,怎么办?
实现了不同渠道的认证功能,但是哪个渠道呢?需要传参,怎么给装饰器自己传参???
方法:闭包的方式传参
执行装饰器2 成功
执行装饰器2
#语法方式2 (更好)
@auth(channel='app') #替换了方式1. 碰到名字+(),先调用
def login(x,y):
print('index代码体了,输出x={},y={}'.format(x,y))
#语法方式1
deco=auth(channel='applet')
@deco
def index(x,y):
print('index代码体了,输出x={},y={}'.format(x,y))
@auth(channel='pc')
def apply(x,y):
print('index代码体了,输出x={},y={}'.format(x,y))
login(2,3)
index(2,3)
apply(2,3)
打印:
请输入用户名:qq
请输入密码:11
app--用户登录认证 successful。开森。。
index代码体了,输出x=2,y=3
请输入用户名:qq
请输入密码:11
小程序--用户登录认证 successful。开森。。
index代码体了,输出x=2,y=3
请输入用户名:qq
请输入密码:11
pc--用户登录认证 successful。开森。。
index代码体了,输出x=2,y=3
总结:
1)碰到名字+(),先调用
2)auth层就是给装饰器传参的。deco的参数是写活函数入参的,wrapper的参数是原封不动模拟原函数的入参的。
3)装饰器最多3层
4)有参语法,函数头顶@+调用3层里的最外层函数deco=auth(channel=‘app’)的auth(channel=‘app’)
15.6 装饰器: 有参 模板
#装饰器
def 有参装饰器(x,y):
def outter(func):
pass
return outer
#调用
@有参装饰器(1,y=2)
def 被装饰对象(x,y):
pass
16 迭代器,生成器
待补充…
17 三元表达式、列表生成式、生成器表达式
17.1 三元表达式
三元表达式是python为我们提供的一种简化代码的解决方案,语法如下:
res = 条件成立时返回的值 if 条件 else 条件不成立时返回的值
例子:
#正常
def max2(x,y):
if x > y:
return x
else:
return y
res = max2(1,2)
print(res)
#三元式
x=1
y =2
res= x if x >y else y
print(res)
打印:
2
2
17.2 列表生成式
例子:
#正常
egg_list=[]
for i in range(10):
egg_list.append('鸡蛋%s' %i)
print(egg_list)
#列表生成式
egg_list2=['鸡蛋%s' %i for i in range(10)]
print(egg_list2)
打印:
['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9']
17.3 生成器表达式
待补充…
18 递归函数
1)为什么要用递归?
——获得了一种新的循环代码的方案。
递归,python给了最大值,1000,也可以自己设置限制;sys.setrecursionlimit()去设定该值,但仍受限于主机操作系统栈大小的限制。超出限制会抛错异常。所以要有条件的使用递归。
2) 递归的应用
递归的2个阶段:
回溯:一层一层调用下去
递推:满足某种结束条件,结束递归调用,然后一层一层返回。
举例1
#age(5)=age(4)+10
#age(4)=age(3)+10
#age(3)=age(2)+10
#age(2)=age(1)+10
#age(1)=18
def age(n):
if n ==1:
return 18
return age(n-1)+10 #如果是5,返回的是一个结果age(4)+10
s=age(5)
print(s)
打印:
58
举例2
l=[1,2,[3,4,[5,6]]]
# for i in l:
# if type(i) is list:
# for ii in i :
# 。。。。判断、重复
#
# else:
# print(i)
def f1(l):
for i in l:
if type(i) is list:
f1(i) #这里着重看下,是不是重复执行,是就缩进到一个函数里
else:
print(i)
f1(l)
打印:
1
2
3
4
5
6
19 类和对象
问题:类和对象,是先有类,还是先有对象?
19.1 面向对象
介绍:
面向过程:
核心是“过程”二字
过程的终极奥义就是将程序流程化
过程是“流水线”,用来分步骤解决问题
面向对象:
核心是“对象”二字
对象是“容器”,用来盛放数据和功能。