Python学习3(函数装饰器)
Posted Zephyr丶J
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python学习3(函数装饰器)相关的知识,希望对你有一定的参考价值。
千峰教育b站的课程
函数
函数定义
参数可有可无
def 函数名([参数...]):
代码
直接打印函数名,会看到函数所在的内存地址,函数名相当于一个变量,指向了这个内存地址
def gen():
print(1)
print(gen) # <function gen at 0x000002B1092DC268>
调用,函数名()
调用函数时参数的顺序
>>> def test(a,b):
... print(a,b)
...
>>> test(1,2) # 位置参数
1 2
>>> test(b=1,a=2) # 关键字参数
2 1
>>>
>>> test(b=1,2) # 关键字参数写在位置参数之前会导致出错
File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
怎么保证传入的参数符合类型要求
isinstance(变量, 类型)
def get_sum(a,b):
if isinstance(a, int) and isinstance(b, int):
print(a + b)
else:
print('type error')
get_sum(1.2,2)
参数默认值
在定义参数的时候可以给参数赋默认值,这样在调用的时候可以不传递这个参数
且默认值参数必须在普通参数的后面(其实也很好理解,因为调用函数传递的时候是按位置传递的)
# def get_sum(a, c = 100,b): # 报错
def get_sum(a,b, c = 100): # c给定了默认值100
if isinstance(a, int) and isinstance(b, int):
print(a + b + c)
else:
print('type error')
get_sum(1,2) #103
关键字参数
调用的时候明确使用参数名赋值,因为默认是按位置顺序赋值的,为了防止因为位置传递发生错误,可以使用关键字参数赋值
例如下面例子中,如果c想用默认值,而d要传递参数,就需要用到关键字参数
def get_sum(a,b, c = 100,d=200):
if isinstance(a, int) and isinstance(b, int):
print(a + b + c + d)
else:
print('type error')
get_sum(1,2, d = 5)
可变参数
*args 接收单独的变量
**kwargs
*args
考虑场景:如果想用一个函数求两个数、三个数…多个数的和
可以使用可变参数,这样将传入的多个参数形成一个元组,传递给函数
也可以什么都不传,相当于一个空的元组
这种自动形成元组的行为成为装包
def get_sum(*args):
print(args)
get_sum(1,2) # (1, 2) 输出的是元组
get_sum(1,2,3,4,5) # (1, 2, 3, 4, 5)
get_sum() # ()
这里是列表
a,*b,c,d = 1,2,3,4,5,6
print(a,b,c,d)
# 1 [2, 3, 4] 5 6
如果有两个可变参数,报错
*a,*b,c,d = 1,2,3,4,5,6
# SyntaxError: two starred expressions in assignment
拆包
a,b,c = (1,2,3)
print(a,b,c) # 1 2 3
# 先拆包后装包
*a,b,c = (1,2,3,5,6,7)
print(a,b,c) # [1, 2, 3, 5] 6 7
在可变参数的函数中传入一个列表,可以看到输出的是只有一个元素的元组
那么怎么才能使得传入的函数的时候是分开的几个数呢?
可以在调用的时候进行拆包,即在调用的时候加*号
def get_sum(*args):
print(args)
get_sum([1,2,3]) # ([1, 2, 3],)
get_sum(*[1,2,3]) # (1, 2, 3)
def get_sum(a, *args):
print(a,args)
get_sum(1,2) # 1 (2,)
get_sum(1,2,3,4,5) # 1 (2, 3, 4, 5)
get_sum([1,2,3]) # [1, 2, 3] ()
get_sum(*[1,2,3]) # 1 (2, 3)
**kwargs
输出是一个字典,而传递给两个位置参数的话会报错,显示需要0个位置参数
那么应该怎么传递呢?
可以传递关键词参数,会转换为字典
kw指的是key word
必须传递关键字参数,会将其转换成key:value的形式,封装到字典里
def show_book(**kwargs):
print(kwargs)
show_book() #
show_book('a','b') # 报错:TypeError: show_book() takes 0 positional arguments but 2 were given
show_book(name ='a') # 'name': 'a'
# 拆包传字典
dict1 = 'name':'a', 'num': 2
show_book(**dict1) # 'name': 'a', 'num': 2
# 用一个*拆包,拆出来是两个key值
print(*dict1) # name num
# 直接输出**dict1会报错
# print(**dict1) # TypeError: 'name' is an invalid keyword argument for print()
两种一起用
def show(*args, **kwargs):
print(args)
print(kwargs)
show(1,2) # (1, 2)/n
dict1 = 'name':'a', 'num': 2
show(1,2,**dict1)
# (1, 2)
# 'name': 'a', 'num': 2
如果很多个值都是不定长参数,那么这种情况下,可以将缺省参数放到 args的后面, 但如果有*kwargs的话,kwargs必须是最后的
def sum_nums_3(a, *args, b=22, c=33, **kwargs):
print(a)
print(b)
print(c)
print(args)
print(kwargs)
sum_nums_3(100, 200, 300, 400, 500, 600, 700, b=1, c=2, mm=800, nn=900)
一般情况下默认值参数会放在*args的后面
# c在前面的话,传参的时候第三个参数会赋值给c
def fun(a, b, c = 10, *args, **kwargs):
print(a,b,c,args,kwargs)
fun(1,2,3,4,5,name='a')
# 1 2 3 (4, 5) 'name': 'a'
# c在可变参数后面,赋值的时候c需要用关键字参数赋值方法
def fun(a, b, *args, c = 10, **kwargs):
print(a,b,c,args,kwargs)
fun(1,2,3,4,5,name='a')
# 1 2 10 (3, 4, 5) 'name': 'a'
kwargs必须在最后面
返回值
python中函数可以返回多个返回值
如果返回多个值,会将多个值封装到一个元组里返回
def returning():
return 1,2
res = returning()
a,b = returning() # 1,2
print(res) # (1, 2)
全局变量和局部变量
当函数内出现局部变量和全局变量相同名字时,函数内部中的 变量名 = 数据 此时理解为定义了一个局部变量,而不是修改全局变量的值
(这里和之前学其他语言好像不太一样,在java中的一个方法中可以直接对全局变量进行更改,而在python中不能对函数外的变量直接进行修改,只能做查看的操作;为什么呢,刚刚去写了一段java代码感受了一下,可以做一个不专业的直观的解释,因为java中对变量的声明是需要加变量类型的,所以如果在函数内声明了和全部变量一样的变量,那么在函数内使用的就是这个局部变量,简单来说就是修改变量和声明变量代码是不一样的;而在python中声明变量不需要加变量类型,同样如果在函数中声明了一个和全局变量一样的变量,那么这只能认为是一个声明变量的操作,而不能作为一个修改全局变量的操作,否则就会出现问题,所以在python中函数不能直接对全局变量进行修改)
那么怎么才能在函数中修改全局变量呢?
需要在函数中声明我用的就是全局变量
a = 10
def check():
global a # 如果不加这句话会报错,因为不能直接对全局变量进行修改
a -= 10
print(a)
check() # 0
如果在函数中出现global 全局变量的名字 那么这个函数中即使出现和全局变量名相同的变量名 = 数据 也理解为对全局变量进行修改,而不是定义局部变量
如果在一个函数中需要对多个全局变量进行修改,那么可以一次性全部声明,也可以分开声明
# 可以使用一次global对多个全局变量进行声明
global a, b
# 还可以用多次global声明都是可以的
# global a
# global b
查看所有的全局变量和局部变量
Python提供了两个内置函数globals()和locals()可以用来查看所有的全局变量和局部变量。
def test():
a = 100
b = 40
print(locals()) # 'a': 100, 'b': 40
test()
x = 'good'
y = True
print(globals()) # '__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x101710630>, '__spec__': None, '__annotations__': , '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/jiangwei/Desktop/Test/test.py', '__cached__': None, 'test': <function test at 0x101695268>, 'x': 'good', 'y': True
可变数据类型和不可变数据类型
所谓可变类型与不可变类型是指:数据能够直接进行修改,如果能直接修改那么就是可变,否则是不可变
可变类型(修改数据,内存地址不会发生变化)有: 列表、字典、集合
不可变类型(修改数据,内存地址必定发生变化)有:int、float、bool、字符串、元组
(python中有小整数对象池,字符串缓存池)
只有不可变数据类型才需要加global
函数中的注释
在函数的第一行用一个字符串作为函数文档
>>> def test(a,b):
... "用来完成对2个数求和" # 函数第一行写一个字符串作为函数文档
... print("%d"%(a+b))
...
>>>
>>> test(11,22) # 函数可以正常调用
33
>>>
>>> help(test) # 使用 help 查看test函数的文档说明
Help on function test in module __main__:
test(a, b)
用来完成对2个数求和
在函数内加一对三引号,会自动出现param…类似的,与java中的/** */相似
def get_info(name: str, age: int):
"""
接收用户的名字和年龄,拼接一个字符串并返回
:param name: 接收一个名字
:param age: 接收用户的年龄,必须是 0-200 间的一个整数
:return: 返回拼接好的字符串
"""
return "我的名字叫 %s,今年是 %d 岁" % (name, age)
get_info("吴彦祖", 19)
get_info(520, 19) # 注意,形参上标注的类型只是提高代码的可读性,并不会限制实参的类型
help(get_info)
引用
sys.getrefcount() 获取引用个数(本身调用这个方法也有一个引用指向目标地址),例如下面的例子 3 + 1 = 4
import sys
list1 = [1,2,3,4]
list2 = list1
list3 = list1
print(sys.getrefcount(list1)) # 4
# del list3
# print(sys.getrefcount(list1)) # 3
del list1
print(sys.getrefcount(list2)) # 3
嵌套函数
函数只是一段可执行代码,编译后就“固化”了,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数了。函数还可以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题。
函数嵌套
函数也是一个变量,所以函数内部也可以定义函数,相当于声明了一个变量
如果要修改外部函数的变量,需要在内部函数中声明对外部变量的使用,即加关键字nonlocal
def outer():
a = 10
print('outer----hello')
def inner(): # inner这个函数是在outer函数内部定义的
b = 20
nonlocal a # nonlocal表示引用内部函数外侧的局部变量a
a += b
print('inner----hello')
inner() # inner函数只在outer函数内部可见
print(inner) # <function outer.<locals>.inner at 0x00000134E031AEA0>
# 用来查看局部变量
print(locals()) # 'a': 10, 'inner': <function outer.<locals>.inner at 0x00000223594EAEA0>
outer()
# inner() 这里会报错,在outer函数外部无法访问到inner函数
闭包
符合以下三个条件是闭包:
是一个嵌套函数
内部函数引用了外部函数的变量
返回值是内部函数
闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数块+引用环境)。
下面这段代码前两个调用中,因为内部函数返回的是函数名,我们知道,相当于一个变量,打印出来是它的地址
而在后两个调用中,因为outer(x)返回的是一个函数名,用函数名调用内部函数,所以返回的是内部函数的返回值,相当于res = outer(x), t = res()
因为无法直接在外部调用内部的inner()函数,所以这里在外部函数中将inner()当做返回值返回,就可以在外部使用这个内部函数
def outer(n):
num = n
def inner():
return num+1
return inner
print(outer(3)) # <function outer.<locals>.inner at 0x0000020E2905AEA0>
print(outer(5)) # <function outer.<locals>.inner at 0x0000020E2905AEA0>
print(outer(3)()) # 4
print(outer(5)()) # 6
在这段程序中,函数 inner 是函数 outer 的内嵌函数,并且 inner 函数是outer函数的返回值。我们注意到一个问题:内嵌函数 inner 中引用到外层函数中的局部变量num,Python解释器会这么处理这个问题呢? 先让我们来看看这段代码的运行结果,当我们调用分别由不同的参数调用 outer 函数得到的函数时,得到的结果是隔离的(相互不影响),也就是说每次调用outer函数后都将生成并保存一个新的局部变量num,这里outer函数返回的就是闭包。 如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).
闭包一般在装饰器中使用
装饰器
首先明白,函数名也是一个变量
如下面代码中,将函数名b赋值给a,a变量指向了b函数的地址,因此运行结果是b
def a():
print('a')
def b():
print('b')
a = b
a() # b
为了在不修改原来已有函数的基础上,为函数添加新的功能(遵循开放封闭原则),出现了装饰器
为了给房子刷墙,写了一个匿名函数,直接执行发现,貌似没有调用任何函数,但是出现了12的结果,说明@decorator 执行了装饰器函数,装饰器函数的参数是house,即将house函数作为参数传入装饰器中,因此会打印那两行横线,然后return 返回了wapper,house接收这个返回值,house = wrapper
此时,house函数经过了装饰,已经变成了新的函数wrapper,这时候执行house会输出wrapper函数的执行结果
def decorator(func):
print('--------------1')
print(func)
def wrapper():
print('---------')
func()
print('刷墙')
print('--------------2')
return wrapper
# house = decorator(house)
@decorator
def house():
print('毛坯房')
# 输出
# --------------1
# <function house at 0x000002112911AEA0> (这句话可以看出在执行之前,会进行装饰,将house函数传入装饰器中)
# --------------2
# 装饰后的house变成了wrapper
print(house) # <function decorator.<locals>.wrapper at 0x000002112911C1E0>
house()
# 输出
# ---------
# 毛坯房
# 刷墙
装饰器的功能
- 引入日志
- 函数执行时间统计
- 执行函数前预备处理
- 执行函数后清理功能
- 权限校验等场景
- 缓存
带有参数的装饰器
如果原函数有参数,则装饰器内部函数也需要有参数
def check_time(action):
def do_action(a,b):
action(a,b)
return do_action
@check_time
def go_to_bed(a,b):
print('去睡觉'.format(a,b))
go_to_bed("zhangsan","床上")
不定长参数
def test(cal):
def do_cal(*args,**kwargs):
# 此时传入内部函数的是一个元组,需要拆包
cal(*args,**kwargs)
return do_cal
# 体会多个参数的传递,需要*args
@test
def demo(a, b):
sum = a + b
print(sum)
# 体会关键字参数的传递,需要**kwargs
@test
def demo2(a, b, c = 4):
sum = a + b + c
print(sum)
demo(1, 2)
demo2(1, b = 2)
所以一般情况下,装饰器中的参数都会写成这两个可变参数
装饰器修饰有返回值的函数
一般情况下为了让装饰器更通用,可以有return
def t_2(cal):
def do_cal(*args,**kwargs):
return cal(*args,**kwargs) # 需要再这里写return语句,表示调用函数,获取函数的返回值并返回
return do_cal
@t_2
def demo(a,b):
return a + b
print(demo(1, 2)) #3
装饰器带参数
了解
执行顺序如下,先检测到outer_check(23),将参数23带入outer_check函数中,执行该函数,返回check_time
然后执行check_time,将play_game传入函数中,返回do_action,此时play_game = check_time
def outer_check(time):
def check_time(action):
def do_action():
if time < 22:
return action()
else:
return '对不起,您不具有该权限'
return do_action
return check_time
@outer_check(23)
def play_game():
return '玩儿游戏'
print(play_game())
带有多个装饰器
可见,先加载最靠近的装饰器,再加载较远的装饰器
# 定义函数:完成包裹数据
def makeBold(fn):
print(1)
def wrapped():
return "<b>" + fn() + "</b>"
print(2)
return wrapped
# 定义函数:完成包裹数据
def makeItalic(fn):
print(3)
def wrapped():
return "<i>" + fn() + "</i>"
print(4以上是关于Python学习3(函数装饰器)的主要内容,如果未能解决你的问题,请参考以下文章