Python学习--Day 14
- 视频来源:python视频 900集
100.函数嵌套调用
- 这节课是一节习题课,内容上没什么新知识。
- 有一个要注意区分的地方,range()是包前不包后,而randint()是包前也包后。
101.全局变量和局部变量
- 局部变量:仅限于在函数内部使用。
def func():
s = 'abcd'
s += 'X'
print(s)
print(s) # 本次调用会报错,出了def范围,系统不认识s
- 全局变量:声明再函数外部的变量,所有函数都可以访问
name = 'Amy'
def func():
print(name)
def func1():
print(name)
- 局部变量与全局变量同名,使用函数的时候优先使用局部变量,函数没有权限更改全局变量。
- 如果想改,要使用关键字global,添加的位置在函数开头,不修改全局变量、只是获取全局变量的话是没有必要使用global的。
name = 'Amy'
def func():
global name
name += ' is girl'
print(name)
func()
102.全局变量是列表类型
- 关于标题:不是说全局变量的数据类型都是列表类型,这个标题的含义是全局变量是列表类型的时候将要发生什么,比如不需要使用globl
- 结合上节课的内容,加global的全局变量要求是不可变类型的,而诸如list之类的可变全局变量不需要使用global就可以进行修改。
name = 'Amy'
list1 = [1,2,3,4,5]
def func():
name = 'Tom'
print(name)
def func1():
global name
print(name)
name += ' is human'
list1.appand(8)
print(list1) # 这样是可以修改list的,因为list是可变类型,不用global也可以修改
103.回顾-1
- 函数的回顾:普通参数、可变参数(*args、**kwargs)、关键字参数、返回值、函数之间也可以进行调用。
- 局部变量和全局变量:可变的直接操作,不可变的要声明global才能修改全局。
104.内部函数-2
- 内部函数:在函数里面声明的一个函数
def inner_func():
,声明后如何调用?可以在整体函数内部调用。
注意:内部函数可以访问所有变量,可以修改外部函数的可变类型的局部变量;函数内的内部函数想改变函数内的局部变量要使用nonlocal
关键字。
name = 'huijia'
list2 = [1,2,3]
def func():
p = 100 # 局部变量
list1 = [1, 2, 3, 4, 5] # 局部变量
# 声明内部函数
def inner_func():
nonlocal n
print('huijia')
list2.append(3)
for i in list1:
i += 5
inner_func()
func()
print(list2)
105.内部函数的变量访问-3
- 代码示例如下,内部函数的变量引用,何处加global和nonlocal见下文示例
a = 100
def func():
# global a 不能在这个地方添加
b = 99
def inner_func():
global a # 为了修改a
nonlocal b # 为了修改b
c = 88
c += 1 # 修改c
b += 1
a += 1
# 尝试打印abc
print(a,b,c)
inner_func()
func()
- 总结:可以访问外部函数的变量,内部函数可以修改外部函数的可变类型的变量(如list),内部函数修改全局变量的不可变变量的时候,需要在内部函数声明global 变量名
,外部函数修改外部函数的不可变的变量是,在内部函数中声明nonlocal 变量名
- 使用locals()内置函数进行查看,查看本函数声明的内容有什么:
注意locals()调用后的结果是什么:由key:value的形式组成
a = 100
def func():
b = 99
def inner_func():
print(a,b)
inner_func()
print(locals())
func()
# 输出
# 100 99
# {'inner_func': <function func.<locals>.inner_func at 0x0000028FBE04F158>, 'b': 99}
- 还可以使用globals()查看全局内有什么,带
__
的都是系统自动加载的。
a = 100
print(globals())
def func():
b = 99
def inner_func():
print(a,b)
inner_func()
func()
# 输出:
# {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__':
# <_frozen_importlib_external.SourceFileLoader object at 0x000001E5BC5B4F98>, '__spec__': None, # '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__':
# 'C:/Users/10727/PycharmProjects/untitled/func01.py', '__cached__': None, 'a': 100}
# 100 99
- locals()查看本地变量有哪些,以字典的形式输出。
- globals()查看全局变量有哪些,以字典的形式输出,里面会有一些系统的键值对。
106.闭包-4
- 闭包的概念:是在函数中提出的概念,要符合以下几个条件:①在外部函数中定义了一个内部函数;②外部函数是有返回值的;③返回的值是:内部函数的名称(即没有括号);④内部函数引用了外部函数的变量。格式:
def 外部函数():
...
def 内部函数():
...
return 内部函数
# 注意没有括号
- 想访问函数内的内部变量,怎么办?可以使用return讲对应的变量返回,因此我们也想使用return返回对应的内置函数。如下代码是一个闭包的例子:
def func():
a = 100
def inner_func():
b = 99
print(a,b)
return inner_func #记住return的是inner_func的名称,而不是带括号的调用
x = func() # 现在x就是inner_func,现在的x()就是调用
x()
- 闭包实战:
def func(a,b):
c = 10
def inner_func():
s = a + b + c
print('相加之后的结果是:'s)
return inner_func
func(6,9)()
# x = func(6,9)
# x()
107.闭包保存参数的状态-5
- 闭包保存参数的状态:内部函数在外部函数被调用的时候才分配内存,所以内次的地址都是不一样的,换句话说,外部函数调用两次,内部函数就会声明两次,因此返回了内部函数的地址,在执行完不会被释放,获得了保存状态的能力。【每次调用函数,系统都会为函数提供新的内存。】
内部函数会不会因为多次调用而被覆盖?不会,详见下面代码的测试:
def func(a,b):
c = 10
def inner_func():
s = a+b+c
print(s)
return inner_func
ifunc = func(6,9)
ifunc1 = func(2,8)
ifunc1()
# 问这时候执行下面的代码会不会因为ifunc1受到影响?
ifunc()
# 结果:20 25
108.闭包之计数器-6
- 对比上一节课程中使用的int类型,本节课使用了list,即可变类型。
可变类型是可以进行修改的,每次都会重复调用,但是int不会被修改,每次都是访问一个全新的。
是一个对比,关键在于了解可变类型与不可变类型的区别,并因此导致了内置函数调用的时候产生的不同结果。
def generate_count():
container = [0]
def addone():
container[0] = container[0] + 1
print('当前是第{}次访问'.format(container[0]))
return addone
counter = generate_count()
counter()
counter()
counter()
# 输出:
# 当前是第1次访问
# 当前是第2次访问
# 当前是第3次访问
109.闭包同级访问-7
-
闭包的缺点总结:作用域不直观,因为变量不会被回收所以有一定的内存占用问题
-
闭包的作用:可以使用同级的作用域、读取其他元素的内部变量、延长作用域
-
闭包总结:
①闭包优化了变量,原来需要类对象完成的工作,闭包也可以完成;
②由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存;
③闭包的好处,使代码变得简介,便于阅读代码
④闭包是理解装饰器的基础 -
同级访问示例:
def func():
a = 100
def inner_func1():
b = 90
s = a + b
print(s)
def inner_func2():
inner_func1() # 同级调用
print('---func2')
return inner_func2
f = func()
f()
print(f)
# 190
# ---func2
# <function func.<locals>.inner_func2 at 0x000001615C74F1E0>
- 内部函数再return的情况
def func():
a = 100
def inner_func1():
b = 90
s = a + b
print(s)
def inner_func2():
inner_func1() # 同级调用
print('---func2')
return 'hello' # 再return
return inner_func2
f = func()
ff = f()
print(ff)
# 输出
# 190
# ---func2
# hello
110.装饰器-8
- 引入(注意外部函数传入参数在内部函数的修改方式,要声明
nonlocal
)
def func(number):
a = 100
def inner_func():
nonlocal a
nonlocal number # 对上面外部函数的传入参数进行修改,也要使用nonlocal
number += 1 #对传入的参数进行修改
for i in range(number):
a += 1a
print('修改后的a',a)
return inner_func
f = func(5)
- 地址引用赋值?返回值与test()一样,证明函数也可以被“变量化”
a = 10 # 声明整型变量
b = a # 赋值
def test(): #声明函数
print('执行代码')
t = test #赋值?
t()
# 输出结果
# 执行代码
- 将函数作为参数传输
def test(): #声明函数
print('执行代码')
def func(f):
print(f)
f()
func(test) # 将函数作为参数传递
# 执行结果:
# <function test at 0x00000177886C2EA0>
# 执行代码
- 装饰器:函数作为参数出现,要有闭包的特点
- 定义一个装饰器(decorate,装饰):使用
@
标记,与Java的重写类似。 - 老师的引入:如果想在不改变一个很多其他代码都用的函数的基础上,希望仅仅调用该函数就能够产生不局限于原代码的特定效果,该怎么办?
方式1:(笨重的方法,对多个函数都要装饰的时候是无效的)
def house():
print('毛坯房')
def house1():
house()
print('刷漆')
print('铺地板')
- 方式2:使用装饰器
def decorate(func):
def wrapper():
func()
print('装修')
return wrapper
@decorate
def house():
print('hello')
house()
# 输出函数:
# hello
# 装修
111.装饰器参数(万能装饰器)-9
- 装饰器实战:仅如下代码也会有输出(即并没有调用,也有了输出)
在底层,具体进行过程是下面的:①house()是被装饰函数②将被修饰函数作为参数传给装饰器decorate;③执行decorate函数;④将返回值赋给house,也就是被修饰函数可以见下图,我自己又写了一遍老师讲的流程。
其中decorate的return语句也执行了,返回的wrapper被house接收。
def decorate(func):
a = 100
print('wrapper外层打印测试')
def wrapper():
func()
print('---刷漆')
print('---装修')
print('wrapper加载完成')
return wrapper
@decorate # 使用装饰器
def house():
print('hello')
# wrapper外层打印测试
# wrapper加载完成
- 装饰器实战:
使用装饰器对方法进行重写。
import time
def decorate(func):
def wrapper():
print('正在校验中')
time.sleep(2)
print('校验完毕')
# 调用原函数
func()
return wrapper
@decorate # 装饰f1
def f1():
print('---f1---')
def f2():
print('---f2---')
f1()
f2()
- 新问题:如果被装饰的函数有参数怎么办?
首先明确,在这里的情况下,不能使用f1(5)
进行直接调用,因为这里的f1本质上是wrapper函数,该函数在修改前是没有参数的,因此会报错。
改动上,就在wrapper内置函数上添加参数,并将它传给合适的外部函数。
import time
def decorate(func):
def wrapper(x): # 在wrapper处进行传参操作
print('正在校验中')
time.sleep(2)
print('校验完毕')
# 调用原函数
func(x) # 将x传给对的人
return wrapper
@decorate # 装饰f1
def f1(n): # 此时函数需要参数的传入
print('---f1---',n)
def f2(n):
print('---f2---',n)
f1(3)
f2(3)
# 输出结果
# 正在校验中
# 校验完毕
# ---f1--- 3
# ---f2--- 3
- 万能装饰器,就是用
*args
作为形式参数,将对应的部分填充好,具体实战代码如下:
import time
def decorate(func):
def wrapper(*args):
print('正在校验中')
time.sleep(2)
print('校验完毕')
# 调用原函数
func(*args)
return wrapper
@decorate
def f2(name,age):
print('---f2---{}{}'.format(name,age))
@decorate
def f3(list1):
for i in list1:
print(i)
f2('lili',5)
list1 = [1,2,3]
f3(list1)
- 启发,既然可以使用
*args
,那么也能够使用**kwargs
接收关键字参数,老师说开发时常用的是下面的万能装饰器
def decorate(func):
def wrapper(*args,**kwargs):
print('正在校验中')
time.sleep(2)
print('校验完毕')
# 调用原函数
func(*args,**kwargs)
return wrapper
112.多层装饰器-10
- 多层装饰器:在函数外放两个装饰函数,离谁近先执行谁,因为装饰器默认装饰下面的东西,此处是装饰器1装饰2+函数,因此实际上是等待装饰器2装饰完函数,再用1装饰函数+装饰器2。
def zhuang1(func):
print('---1.1 start')
def wrapper(*args,**kwargs):
func()
print('刷油漆')
print('---1.2 end')
return wrapper
def zhuang2(func):
print('---2.1 start')
def wrapper(*args,**kwargs):
func()
print('装门')
print('---2.2 end')
return wrapper
@zhuang1
@zhuang2
def house():
print('毛坯房')
house()
# 输出:
# ---2.1 start
# ---2.2 end
# ---1.1 start
# ---1.2 end
# 毛坯房
# 装门
# 刷油漆