Python 装饰器

一、python 函数传递函数(形参类型为函数对象)

写在前面:函数的形参是函数类型,传递的是一个函数别名,也即函数地址

Tips:
1、给函数起别名(即函数引用):相当于把一个函数对象的地址赋值给另一个变量,此时操作这个变量也就相当于操作这个函数本身。
2、我们传递到函数形参中的,也是另一个函数的别名,这样也就相当于在一个函数中操作另一个函数。

def user():
	print("in user()")

def newuser(funcname):
	print(funcname)
	funcname()

newuser(user)

# 输出:
<function user at 0x7faad18fa7a0> (user函数的地址)
hh

稍作解释:这里是先调用newuser这个函数,然后把函数user作为一个实参传递给newuser函数,也就是把user的地址传递给newuser(可以通过打印语句看出,传递的是一个函数地址),这时newuser中的funcname和user函数就是等价的了,所以可以像操作user函数一样去操作funcname。

🌰2

def convert(func, seq):
	return [func(num) for num in seq]  # 列表生成式 循环seq,将循环的结果返回给func()函数,最后执行func()函数

myseq = (123, 45.67, -6.2e8, 9999999)
print(convert(int, myseq)) # int()是一个内置函数,这里传递int,是传递这个函数的地址,所以在convert()函数中,func这个形参代表的就是int()这个函数,两个的意义是一样的。
print(convert(float, myseq)) # 和上述int是同一个道理

🌰3:在调用以函数别名作为形参的函数时,传递的实参是函数的调用

def user():
    print("hh")
    return 1


def newuser(funcname):
    # # print(funcname)
    print(funcname)

# 在这里已经调用函数,而这里作为实参再传递给newuser()函数,其实是传递了user这个函数的返回值
newuser(user())  

# 输出:
hh  =>这个输出其实是在执行这一句语句newuser(user())时输出的,执行这句语句时就已经在调用user()这个函数了
1 =>这个输出是newuser(user())传递了user()函数的返回值,然后再newuser()函数中打印出了返回值

二、函数嵌套

  • 内部/内嵌函数
    1、定义:在一个函数的函数体内使用关键字def关键字定义一个新的函数,这个新的函数就叫做内部/内嵌函数。
    2、注意点:内部函数的整个函数体都在外部函数的作用域内,如果在内部函数内没有对外部函数变量的引用,即访问,那么除了在外部函数体内,在其他任何地方都不能对内部函数进行调用。
    3、内部函数可以访问外部函数的变量,但是不能对外部函数中的变量进行使用,即不能试图改变外部函数中的变量。但可以使用nonlocal关键字修饰内部函数的变量,修饰后内部函数就可以访问并使用外部函数的变量。
  • 🌰1
def out_func(out_name):
    def in_func(in_name):
        print(out_name.title() + " " + in_name.title())  # 内部函数可以访问外部函数的变量,但不能使用不能改变
    print("this is user()")
    return in_func # 返回的是内部函数对象,也就是内部函数地址。

a = out_func("tom")
print(a)
print(type(a))

输出结果:

在这里插入图片描述
通过输出内容我们可以看到,调用out_func函数后,返回的内容是一个函数地址,类型是一个函数

  • 🌰2
def out_func(out_name):
    def in_func(in_name):
        print(out_name.title() + " " + in_name.title())  # 内部函数可以访问外部函数的变量,但不能使用不能改变
    print("this is user()")
    return in_func # 返回的是内部函数对象,也就是内部函数地址。

a = out_func("tom") # 返回的是in_func这个函数对象
a("jerry") # 相当于in_func("jerry")

out_func("tom")("jerry") 

输出结果:

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210520121318570.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dpbmR5SjgwOQ==,size_16,color_FFFFFF,t_7

a = out_func("tom")语句是调用外部函数,执行后变量a就相当于in_funca("jerry")语句相当于in_func("jerry")

out_func("tom")("jerry") 语句和a = out_func("tom")a("jerry")结果是一样的。

  • 🌰3
def out_func(out_name):
    def in_func(in_name):
        print(out_name.title() + " " + in_name.title())  # 内部函数可以访问外部函数的变量,但不能使用不能改变
    print("this is user()")
    return in_func # 返回的是内部函数对象,也就是内部函数地址。
    
in_func("test")  # 局部变量 不能在外部函数外部使用

输出结果:

在这里插入图片描述

内部函数也算是外部函数的一个局部变量,所以在外部函数外部,它也是没有作用域的。

Tip:

  • title():将调用其的字符串首字母大写

三、装饰器

1、普通装饰器

写在前面:其实装饰器也就是一个函数嵌套,只不过写的更简洁了一些。我们可以再通过一个函数嵌套的例子来引入装饰器。

"""
计算函数运行时间
"""
import time

def func(f1):
	print(f1)


def run_time(f):
	def inner(*args):
		before_time = time.time()
		f(*args)
		print("func 运行时间为:{}".format(time.time() - before_time))

	return inner

# 先调用run_time函数,返回值时inner这个内部函数地址
# 再调用返回值,即inner函数
run_time(func)("func函数")

输出:
在这里插入图片描述

这个程序运行过程是:

1、先调用run_time(func),进入run_time函数中,在run_time函数中只对内部函数inner进行了定义,并没有执行,然后走到return返回了这个内部函数inner。
2、此时run_time(func)即相当于inner,所以这时候就会直接去执行内部函数inner("func函数")
3、在内部函数inner里面,调用了func函数(f(*args)这一句语句),这时候回去执行func函数,执行完后继续执行inner函数。inner函数执行完,程序也就执行完毕。

这个例子看懂了之后,我们就可以来了解装饰器是怎么整的了。

首先看一些概念性的:
1、装饰器实际上就是一个函数它可以让其他函数在不做任何代码改动的情况下增加额外的功能,装饰器的返回值也是一个对象。(有没有觉得其实上面例子中的run_time函数就可以当做装饰器,哈哈,待会底下的例子就可以看出来。)
2、装饰器是在函数调用之上的修饰。这些修饰只有在声明一个函数或方法时才会被调用。
3、语法:以@开头,后面是函数装饰器的名字和可选的参数。下面一行是被修饰的函数和可选的参数。如下。在这里插入图片描述

我们现在就对上面的例子做一个改造,把它变成装饰器。看代码。

def run_time(f):
	print("执行装饰器")
	def inner(*args):
		before_time = time.time()
		func(*args)
		print("func 运行时间为:{}".format(time.time() - before_time))

	return inner

@run_time
def func(f1):
	print(f1)

func("func函数")

我们可以看出来,在定义函数func()时在上面加了一行语句@run_time,这个就是我们说的装饰器。它其实就是程序里面的run_time()函数。那么在函数定义时加上@run_time,就相当于给这个函数加上了装饰器,也就是说给func这个函数加上run_time()函数里面的功能,好听点就是被run_time()函数装饰了,哈哈。我们来看这个程序的执行过程。

1、首先我们在定义func()时给他加上了@run_time装饰器,那么这个装饰器在函数定义时就会被执行。因为说白了装饰器也是一个函数嘛,@run_time语句就相当于run_time(func),也就是调用了run_time函数。那执行完run_time函数,就会返回一个函数地址。(我们可以通过先不调用func函数,执行代码,看会不会打印“执行装饰器”文案,如果会,那就说明装饰器是在函数定义时就被执行了的。)
2、func("func函数")语句调用函数fun,会先走@run_time语句,执行完后这句语句的值就是inner函数地址。所以此时我们执行的func("func函数")语句里面的func就相当于inner,func("func函数")语句调用了func函数,就相当于调用了inner函数,所以程序就去执行run_time函数的内部函数inner函数,参数是func("func函数")中的参数“func函数”。
3、执行inner函数时,f(*args)语句会调用func()函数,然后程序会去执行func()函数,执行完后继续执行inner函数,inner执行完后,程序也就执行完毕了。

2、叠加使用装饰器

装饰器可以叠加使用,像下面这样。
在这里插入图片描述

在叠加使用时,还是按照程序执行顺序,一步一步去执行。我们可以把@deco这个语句就当做deco(参数是被装饰的函数名)这个语句,意思就是去调用deco这个函数,然后把函数的返回值返回来,在调用被装饰的函数时使用它。

3、有参数的装饰器

我们上面的例子都是无参数的装饰器,即在@deco后面没有任何的参数。如下图。

在这里插入图片描述

下面我们再说说有参数的装饰器。
有参数的装饰器即需要自己返回以函数作为参数的装饰器。

在这里插入图片描述
说明下,装饰器deco()用参数deco_arg在函数内做了些事情并返回来函数对象。而这个函数对象是以func函数作为参数的装饰器。
在这里插入图片描述
我们来看个例子

"""
对年龄做非法限制:
1、首先需要是个人。
2、年龄在【0~160之间】,若年龄符合,则打印年龄,若不符合打印年龄非法.
"""
def peo(flag):
	def age_limit(f):
		def age(p_age):
			if flag:
				if 0 < p_age < 100:
					f(p_age)
				else:
					print("输入的年龄不合法")
			return age
		return age_limit

@peo(True)
def getage(age):
	print("age is %d" % age)

getage(10)

我们来看下这个程序的执行过程:

  • 在上面我们已经说过,@peo(True)语句其实是去调用peo函数,那在这里我们的@peo(True)语句其实就相当于peo(True)(getage)。为什么后面peo函数只有一个flag参数,而这里是相当于还调了一遍参数为getage的函数呢?我们可以看程序,在执行完peo函数后,返回的是一个age_limit函数地址,然后程序发现还需要继续调用,于是就把函数getage作为参数传递给age_limit函数,最后返回一个age函数地址,这个age的函数地址就相当于我们的getage函数了,所以在调用getage函数时,也就相当于调用了装饰器函数中的age这个内部函数。
  • 其实也就是在执行@deco语句时,要去找参数为函数对象的内部函数,执行到它为止,才算这个语句执行完毕。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值