Python三大器之装饰器

开放封闭原则

一个良好的项目必定是遵守了开放封闭原则的,就比如一段好的Python代码必定是遵循PEP8规范一样。那么什么是开放封闭原则?具体表现在那些点?

  开放封闭原则的核心的思想是软件实体是可扩展,而不可修改的。 也就是说,对扩展是开放的,而对修改是封闭的。 即使迫不得已要进行修改,也最好不要改变它原本的代码。

  具体表现的点:

    1.写好的项目,后期可以很方便的为其添加新的功能

    2.项目修改应该尽量少的改动原本逻辑代码。而是通过某种补丁的形式完善其功能

初识装饰器

什么是装饰器


  装饰器应该拆开来进行讲解:

  "器"指的是器具,工具。可以理解为函数

  "装饰"指的是为其他事物添加额外的东西点缀。

  "装饰器"就是指定义一个函数,而该函数的主要作用便是用于为其他函数做功能上的一个补充(添加新的功能)。

为什么要有装饰器


  补充一个函数的功能非常简单。但是要在遵循开放封闭原则的前提下为一个函数做功能的补充就不是那么简单了,装饰器就是为了解决这种场景而诞生的。

  装饰器可以在遵循开放封闭原则的前提下为被装饰对象添加新的功能(即不修改源代码及其调用方式的前提下为其添加新功能)

怎么使用装饰器


  装饰器说白了就是闭包函数加上*args以及**kwargs的一个综合应用。下面的内容将带你深入浅出的了解装饰器的定义与使用。

装饰器实现

需求分析


import time
import random


# 通过time模块和random模块模拟下载,上传功能所花费的时间

# 以下两个函数都部署在线上服务器上:
# 要求为其增添新的功能,不违反开放封闭性原则的前提下统计下载和上传花费的时长。
# (即:不修改download和upload的调用方式和源码的情况下为其添加统计时长的功能)
def download(url):
"""下载..."""
print("正在下载{0}的资源...".format(url))
time.sleep(random.randint(1, 3))
print("下载完成...")


def upload(url):
"""上传..."""
print("正在向{0}上传资源...".format(url))
time.sleep(random.randint(1, 3))
print("上传完成...")


download("www.cnblogs.com/xxx/file/x.jpg")
download("www.cnblogs.com/yyy/file/y.jpg")
upload("www.cnblogs.com/zzz/file/img")

解决方案一


  既然要在不修改源码与调用方式的前提下增添新功能。那么我们可以这么做:

import time
import random


# 通过time模块和random模块模拟下载,上传功能所花费的时间

# 以下两个函数都部署在线上服务器上:
# 要求为其增添新的功能,不违反开放封闭性原则的前提下统计下载和上传花费的时长。
# (即:不修改download和upload的调用方式和源码的情况下为其添加统计时长的功能)
def download(url):
"""下载..."""
print("正在下载{0}的资源...".format(url))
time.sleep(random.randint(1, 3))
print("下载完成...")


def upload(url):
"""上传..."""
print("正在向{0}上传资源...".format(url))
time.sleep(random.randint(1, 3))
print("上传完成...")

start = time.time()
download("www.cnblogs.com/xxx/file/x.jpg")
end = time.time()
print("下载www.cnblogs.com/xxx/file/x.jpg资源所耗费的时长为:{0:.2f}秒".format(end-start))

start = time.time()
download("www.cnblogs.com/yyy/file/y.jpg")
end = time.time()
print("下载www.cnblogs.com/yyy/file/y.jpg资源所耗费的时长为:{0:.2f}秒".format(end-start))

start = time.time()
upload("www.cnblogs.com/zzz/file/img")
end = time.time()
print("上传资源至www.cnblogs.com/zzz/file/img所耗费的时长为:{0:.2f}秒".format(end-start))

# ==== 执行结果 ===

"""
正在下载www.cnblogs.com/xxx/file/x.jpg的资源...
下载完成...
下载www.cnblogs.com/xxx/file/x.jpg资源所耗费的时长为:3.00秒
正在下载www.cnblogs.com/yyy/file/y.jpg的资源...
下载完成...
下载www.cnblogs.com/yyy/file/y.jpg资源所耗费的时长为:1.00秒
正在向www.cnblogs.com/zzz/file/img上传资源...
上传完成...
上传资源至www.cnblogs.com/zzz/file/img所耗费的时长为:1.00秒
"""

  问题解决,不修改源代码以及调用方式的前提下为downloadupload函数添加好了新功能,但是这种解决方案是非常愚蠢的。

  写重复代码,程序可读性非常差而且非常耗费人力去单纯的做复制粘贴。

解决方案二


import time
import random


# 通过time模块和random模块模拟下载,上传功能所花费的时间

# 以下两个函数都部署在线上服务器上:
# 要求为其增添新的功能,不违反开放封闭性原则的前提下统计下载和上传花费的时长。
# (即:不修改download和upload的调用方式和源码的情况下为其添加统计时长的功能)
def download(url):
"""下载..."""
print("正在下载{0}的资源...".format(url))
time.sleep(random.randint(1, 3))
print("下载完成...")


def upload(url):
"""上传..."""
print("正在向{0}上传资源...".format(url))
time.sleep(random.randint(1, 3))
print("上传完成...")


def outer(func): # func 是函数的内存地址
def warpper(url):
start = time.time()
func(url) # 这里才是真正运行的 download 或者 upload 函数。
end = time.time()
if func.__name__ == "download": # func.__name__ 可以拿到函数名
print("下载{0}资源所耗费的时长为:{1:.2f}秒".format(url, (end - start)))
else:
print("上传资源至{0}所耗费的时长为:{1:.2f}秒".format(url, (end - start)))

return warpper


download = outer(download) # download函数内存地址传递进去。 变量值download接收,返回的是 warpper 函数的内存地址。此download非彼download
upload = outer(upload)

download("www.cnblogs.com/xxx/file/x.jpg") # 由于warpper指向的函数需要一个参数url,所以直接传即可。
download("www.cnblogs.com/yyy/file/y.jpg")
upload("www.cnblogs.com/zzz/file/img")

# ==== 执行结果 ===

"""
正在下载www.cnblogs.com/xxx/file/x.jpg的资源...
下载完成...
下载www.cnblogs.com/xxx/file/x.jpg资源所耗费的时长为:1.02秒
正在下载www.cnblogs.com/yyy/file/y.jpg的资源...
下载完成...
下载www.cnblogs.com/yyy/file/y.jpg资源所耗费的时长为:2.01秒
正在向www.cnblogs.com/zzz/file/img上传资源...
上传完成...
上传资源至www.cnblogs.com/zzz/file/img所耗费的时长为:2.02秒
"""

  解决方案二已经完美完成需求了。但是还能再做一些修改,基于开放封闭原则,该解决方案的扩展性还不是很强,如果后期upload或者download的源码真的发生改变所需要的参数增多了。那我们的这个outer函数也需要去做改变,很麻烦。所以我们需要在此基础上做一个改进。

解决方案三


import time
import random


# 通过time模块和random模块模拟下载,上传功能所花费的时间

# 以下两个函数都部署在线上服务器上:
# 要求为其增添新的功能,不违反开放封闭性原则的前提下统计下载和上传花费的时长。
# (即:不修改download和upload的调用方式和源码的情况下为其添加统计时长的功能)
def download(url):
"""下载..."""
print("正在下载{0}的资源...".format(url))
time.sleep(random.randint(1, 3))
print("下载完成...")


def upload(url):
"""上传..."""
print("正在向{0}上传资源...".format(url))
time.sleep(random.randint(1, 3))
print("上传完成...")


def outer(func): # func 是函数的内存地址
def warpper(*args,**kwargs): # 优化1:参数转接,warpper直接对接func,多少参数都没问题。取决于func
start = time.time()
res = func(*args,**kwargs) # 这里才是真正运行的 download 或者 upload 函数。 优化2:如果函数有了返回值,我们就可以对他进行返回
end = time.time()
if func.__name__ == "download": # func.__name__ 可以拿到函数名
print("下载{0}资源所耗费的时长为:{1:.2f}秒".format(args[0], (end - start))) # 优化3:这里改成args[0]即可。位置传参url必须一一对应,其实有个小bug,关键字传参有问题。
else:
print("上传资源至{0}所耗费的时长为:{1:.2f}秒".format(args[0], (end - start)))
return res # 优化2:返回func的返回值
return warpper


download = outer(download) # download函数内存地址传递进去。 变量值download接收,返回的是 warpper 函数的内存地址。此download非彼download
upload = outer(upload)

download("www.cnblogs.com/xxx/file/x.jpg") # 由于warpper指向的函数需要一个参数url,所以直接传即可。
download("www.cnblogs.com/yyy/file/y.jpg")
upload("www.cnblogs.com/zzz/file/img")

# ==== 执行结果 ===

"""
正在下载www.cnblogs.com/xxx/file/x.jpg的资源...
下载完成...
下载www.cnblogs.com/xxx/file/x.jpg资源所耗费的时长为:1.02秒
正在下载www.cnblogs.com/yyy/file/y.jpg的资源...
下载完成...
下载www.cnblogs.com/yyy/file/y.jpg资源所耗费的时长为:2.01秒
正在向www.cnblogs.com/zzz/file/img上传资源...
上传完成...
上传资源至www.cnblogs.com/zzz/file/img所耗费的时长为:2.02秒
"""

无参装饰器模板

# ==== 无参普通方式调用 ====

def test(x,y,z):
print(x,y,z)
return "结果"

def outer(func): # 装饰器名字应该取好,func代表被装饰函数。
def warpper(*args,**kwargs): # warpper函数对应func,也就是被执行的函数。这里做对接
# 功能扩展区,函数运行前
res = func(*args,**kwargs) # 接收func的return值
# 功能扩展区,函数运行后
return res # 将func的返回值返回出去
return warpper

test = outer(test) # test变量名执行warpper内存地址。
res = test(1,2,3) # 拿到warpper的return值,看起来在执行test,实际上在执行warpper
print(res)

无参普通方式调用

# ==== 无参wraps普通方式调用 ====
from functools import wraps

def test(x,y,z):
print(x,y,z)
return "结果"

def outer(func): # 装饰器名字应该取好,func代表被装饰函数。
@wraps(func) # 这里主要完全将warpper的所有文档信息等等都与func保持一致。下面会讲
def warpper(*args,**kwargs): # warpper函数对应func,也就是被执行的函数。这里做对接
# 功能扩展区,函数运行前
res = func(*args,**kwargs) # 接收func的return值
# 功能扩展区,函数运行后
return res # 将func的返回值返回出去
return warpper

test = outer(test)
res = test(1,2,3) # 拿到warpper的return值
print(res)

无参wraps普通方式调用

# ==== 无参wraps@语法糖方式调用 ====

from functools import wraps
def outer(func): # 装饰器名字应该取好,func代表被装饰函数。
@wraps(func) # 这里主要完全将warpper的所有文档信息等等都与func保持一致。下面会讲
def warpper(*args,**kwargs): # warpper函数对应func,也就是被执行的函数。这里做对接
# 功能扩展区,函数运行前
res = func(*args,**kwargs) # 接收func的return值
# 功能扩展区,函数运行后
return res # 将func的返回值返回出去
return warpper

@outer # 将下面一坨被装饰函数放入func中。相当于 --> test = outer(test)。此时执行test就相当于执行warpper。
def test(x,y,z):
print(x,y,z)
return "结果"

res = test(1,2,3) # 拿到warpper的return值
print(res)

无参wraps@语法糖方式调用

  (对于@语法糖调用形式来说):outer层的参数只应该有一个func形参来接收被装饰的函数对象,wapper层就只该有*args**kwargs,为的是和传入的func(函数对象)做参数对接,参数不能多传递。

@语法糖

@调用装饰器


  上面的第三种解决方案其实就是装饰器。那么我们可以看到他还是有一些复杂的地方,比如:

  其实Python为了简化这一步骤。提供了@语法糖,我们看一下它是如何使用的。

# ==== @语法糖的使用 ====

# 第一步:装饰器所有代码拖上来,拖到被装饰对象的上面。
def outer(func): # func 是函数的内存地址
def warpper(*args, **kwargs): # 优化1:参数转接,warpper直接对接func,多少参数都没问题。取决于func
start = time.time()
res = func(*args, **kwargs) # 这里才是真正运行的 download 或者 upload 函数。 优化2:如果函数有了返回值,我们就可以对他进行返回
end = time.time()
if func.__name__ == "download": # func.__name__ 可以拿到函数名
print("下载{0}资源所耗费的时长为:{1:.2f}秒".format(args[0], (end - start))) # 优化3:这里改成args[0]即可。位置传参必须一一对应,其实有个小bug,关键字传参有问题。
else:
print("上传资源至{0}所耗费的时长为:{1:.2f}秒".format(*args[0], (end - start)))
return res # 优化2:返回func的返回值

return warpper

# 第二步:@后面跟上装饰器名称,
# 其实@outer就相当于把下面一坨被装饰函数当做参数传给outer --> download = outer(download)。而下面执行download的时候相当于就是执行返回值warpper函数
@outer
def download(url):
"""下载..."""
print("正在下载{0}的资源...".format(url))
time.sleep(random.randint(1, 3))
print("下载完成...")

@outer
def upload(url):
"""上传..."""
print("正在向{0}上传资源...".format(url))
time.sleep(random.randint(1, 3))
print("上传完成...")

# 第三步: 删除偷梁换柱的操作

download("www.cnblogs.com/xxx/file/x.jpg") # 由于warpper指向的函数需要一个参数url,所以直接传即可。
download("www.cnblogs.com/yyy/file/y.jpg")
upload("www.cnblogs.com/zzz/file/img")

@到底做了什么


  想要了解@到底做了什么,先介绍一个内置函数叫做help(),它可以获得一些对于当前函数的帮助信息。

# ==== @内部做了那些事 ====

def test():
"""这是一个测试"""
pass

res = help(test) # 获得帮助。 # help的返回值是None,并且help会自动调用print打印出结果。注意!!!
print(res) # 这里只是打印help方法的返回值。

# ==== 执行结果 ====

"""
Help on function test in module __main__:

test()
这是一个测试

None # <--这里是print的结果,上面全是help的结果,help内部自动调用print方法打印出了上面的信息
"""

  我们再来测试看结果:

# ==== @内部做了那些事 ====

@help # 由于help内部会自动调用print,所以会先出现它的信息
def test():
"""这是一个测试"""
pass

test()

# ==== 执行结果 ====

"""
Help on function test in module __main__:

test()
这是一个测试

Traceback (most recent call last):
File "C:/Users/Administrator/PycharmProjects/learn/FunctionLearn.py", line 82, in <module>
test()
TypeError: 'NoneType' object is not callable # <-- 注意,意思是说空类型不可被调用的意思
"""

  结论如下:

# # ==== @内部做了那些事(结论) ====

# @后面跟着函数名。会自动将被装饰函数当做参数拿进来(如果手动加括号,被装饰函数就不能灵活的传进来,这是一种限制),help(test),并且会将它返回给变量test,那么便是 test = help(test)...
# 由于help的返回值为None,所以...

@help
def test():
"""这是一个测试"""
pass

test() # 现在的test,就是help的返回结果。也就是None,一个名字加括号代表要执行它。而None类型不可被执行。

# ==== 执行结果 ====

"""
Help on function test in module __main__:

test()
这是一个测试

Traceback (most recent call last):
File "C:/Users/Administrator/PycharmProjects/learn/FunctionLearn.py", line 82, in <module>
test()
TypeError: 'NoneType' object is not callable
"""

  现在我们知道了。@会自动调用的(),并且将下面的被装饰函数当做参数传递进去,如果我们手动做这件事会发生什么呢?

# ==== @手动传参 ====

@help(print)
def test():
"""这是一个测试"""
pass

test() # 现在的test,就是help的返回结果。也就是None,一个名字加括号代表要执行它。而None类型不可被执行。

# ==== 执行结果 ==== Ps:可以看到,它打印出了print的帮助信息。而test的帮助信息并没有传递进去。

"""
Help on built-in function print in module builtins:

print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False) Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.

Traceback (most recent call last):
File "C:/Users/Administrator/PycharmProjects/learn/FunctionLearn.py", line 80, in <module>
def test():
TypeError: 'NoneType' object is not callable
"""

多个@装饰器分析


  如果一次性使用多个@装饰器,会发生什么?

  记住一点:

  多个装饰器的执行顺序自下而上

# ==== 多个@装饰器执行顺序 ====

def f1(func):
print("执行f1装饰器了")
def warpper(*args,**kwargs):
print("我是f1中的warpper.我的参数是..", func)
res = func(*args,**kwargs)
return res
return warpper

def f2(func):
print("执行f2装饰器了")
def warpper(*args,**kwargs):
print("我是f2中的warpper.我的参数是..", func)
res = func(*args,**kwargs)
return res
return warpper

def f3(func):
print("执行f3装饰器了")
def warpper(*args,**kwargs):
print("我是f3中的warpper.我的参数是..", func)
res = func(*args,**kwargs)
return res
return warpper

@f1
@f2
@f3
def test():
print("我是test,我没有参数")

test()

# 注意:@装饰器的执行顺序是从下至上
# 第一步:test = f3(test),打印第1句,返回的是f3中的warpper。
# 第二步:test = f2(test),打印第2句,此时的test是f3中的warpper,返回的是f2中的warpper
# 第三步:test = f1(test),打印第3句,此时的test是f2中的warpper,返回的是f1中的warpper
# 第四步,开始执行test.也就是执行f1中的warpper。打印第4句,运行func
# 第五步:此时运行的func是f2中的warpper,所以打印第5句,继续运行func
# 第六步:此时运行的func是f3中的warpper,所以打印第6句,继续运行func
# 第七步:此时运行的func才是真正的test,打印第7句。分析结束。

# ==== 执行结果 ====

"""
执行f3装饰器了
执行f2装饰器了
执行f1装饰器了
我是f1中的warpper.我的参数是.. <function f2.<locals>.warpper at 0x00000246A34F6940>
我是f2中的warpper.我的参数是.. <function f3.<locals>.warpper at 0x00000246A34F68B0>
我是f3中的warpper.我的参数是.. <function test at 0x00000246A34F6820>
我是test,我没有参数
"""

有参装饰器

有参装饰器应用场景


  无参装饰器已经能够解决百分之九十的应用场景。但是有的时候还是需要有参装饰器来解决一些特定的需求,在不允许为内部函数传参的情况下就该使用到有参装饰器。它其实也是利用了闭包函数中传参的这一作用,直接再往上封装一层就好了。

  基于无参装饰器,也就是解决方案三。我们再提一个需求:

  下载或上传的时候如果用户是普通用户,则拒绝他通过本软件进行下载或上传,如果是VIP用户,则可以使用我们的所有功能。

  解决方案一 不使用@语法糖:不使用@语法糖可以很轻松的解决,只需要为outer加上一个参数传进去即可。

import time
import random

user_genre = random.randint(0, 1) # 这里懒得做登录了,用随机数代替。随机数是0则代表普通用户,随机数是1就代表vip用户。


def outer(func, genre): # func 是函数的内存地址,genre是用户类型。
def warpper(*args, **kwargs): # 优化1:参数转接,warpper直接对接func,多少参数都没问题。取决于func
if genre == 1:
start = time.time()
res = func(*args, **kwargs) # 这里才是真正运行的 download 或者 upload 函数。 优化2:如果函数有了返回值,我们就可以对他进行返回
end = time.time()
if func.__name__ == "download": # func.__name__ 可以拿到函数名
print("下载{0}资源所耗费的时长为:{1:.2f}秒".format(args[0], (
end - start))) # 优化3:这里改成args[0]即可。位置传参必须一一对应,其实有个小bug,关键字传参有问题。
else:
print("上传资源至{0}所耗费的时长为:{1:.2f}秒".format(args[0], (end - start)))
return res # 优化2:返回func的返回值
else:
print("不允许下载或上传")

return warpper


def download(url):
"""下载..."""
print("正在下载{0}的资源...".format(url))
time.sleep(random.randint(1, 3))
print("下载完成...")


def upload(url):
"""上传..."""
print("正在向{0}上传资源...".format(url))
time.sleep(random.randint(1, 3))
print("上传完成...")


download = outer(download, user_genre) # download函数内存地址传递进去。 变量值download接收,返回的是 warpper 函数的内存地址。此download非彼download
upload = outer(upload, user_genre) # 这里直接将用户类型传递进去。(图省事,没做登录这些,求一个精简)

download("www.cnblogs.com/xxx/file/x.jpg") # 由于warpper指向的函数需要一个参数url,所以直接传即可。
download("www.cnblogs.com/yyy/file/y.jpg")
upload("www.cnblogs.com/zzz/file/img")

# ==== 执行结果 ===

"""
不允许下载或上传
不允许下载或上传
不允许下载或上传
"""

  解决方案二 使用@语法糖:由于@语法糖的限制,我们的outer函数应当只该去接收func,而不应该有其他的变量(实际上也接收不了其他的变量,不信你可以试试)。所以,我们需要通过闭包嵌套再给他传入一个变量。

import time
import random

user_genre = random.randint(0, 1) # 这里懒得做登录了,用随机数代替。随机数是0则代表普通用户,随机数是1就代表vip用户。


def outer(genre): # genre是用户类型。
def inner(func): # func 是函数的内存地址,由于改为第二层,故改名inner。
def warpper(*args, **kwargs): # 优化1:参数转接,warpper直接对接func,多少参数都没问题。取决于func
if genre == 1:
start = time.time()
res = func(*args, **kwargs) # 这里才是真正运行的 download 或者 upload 函数。 优化2:如果函数有了返回值,我们就可以对他进行返回
end = time.time()
if func.__name__ == "download": # func.__name__ 可以拿到函数名
print("下载{0}资源所耗费的时长为:{1:.2f}秒".format(args[0], (
end - start))) # 优化3:这里改成args[0]即可。位置传参必须一一对应,其实有个小bug,关键字传参有问题。
else:
print("上传资源至{0}所耗费的时长为:{1:.2f}秒".format(args[0], (end - start)))
return res # 优化2:返回func的返回值
else:
print("不允许下载或上传")

return warpper

return inner


@outer(user_genre) # 手动传值进去,否则运行不了。传入用户类型,如果不手动传值将会把下面被装饰函数传入进去。导致认为inner是warpper。
def download(url):
"""下载..."""
print("正在下载{0}的资源...".format(url))
time.sleep(random.randint(1, 3))
print("下载完成...")


@outer(user_genre)
def upload(url):
"""上传..."""
print("正在向{0}上传资源...".format(url))
time.sleep(random.randint(1, 3))
print("上传完成...")


download("www.cnblogs.com/xxx/file/x.jpg") # 由于warpper指向的函数需要一个参数url,所以直接传即可。
download("www.cnblogs.com/yyy/file/y.jpg")
upload("www.cnblogs.com/zzz/file/img")

# ==== 执行结果 ===

"""
不允许下载或上传
不允许下载或上传
不允许下载或上传
"""

有参装饰器的实现模板


# ==== 有参wraps@语法糖方式调用 ====

from functools import wraps
def outer(x):
def inner(func): # 装饰器名字应该取好,func代表被装饰函数。
@wraps(func) # 这里主要完全将warpper的所有文档信息等等都与func保持一致。下面会讲
def warpper(*args,**kwargs): # warpper函数对应func,也就是被执行的函数。这里做对接
# 功能扩展区,函数运行前
res = func(*args,**kwargs) # 接收func的return值
# 功能扩展区,函数运行后
return res # 将func的返回值返回出去
return warpper
return inner

@outer(1) # 执行outer(注意:必须手动传入参数,不然会将test传进去),拿到返回值inner.相当于@inner,再自动执行inner,将test传给形参func,返回warpper.执行test的时候实际上就是执行的wapper
def test(x,y,z):
print(x,y,z)
return "结果"

res = test(1,2,3) # 拿到warpper的return值
print(res)

有参wraps@语法糖方式调用

  (对于@语法糖调用形式来说):inner层的参数只应该有一个func形参来接收被装饰的函数对象,wapper层就只该有*args**kwargs,为的是和传入的func(函数对象)做参数对接,参数不能多传递。所以就在外面套上一个outer层函数,通过闭包传参即可,目的是做功能拓展。

扩展:wraps装饰器修饰伪装


  如果不使用wraps装饰器对warpper函数进行装饰,那么其实偷梁换柱是有瑕疵的。我们看...

def test(x,y,z):
print(x,y,z)
return "结果"

def outer(func):
def warpper(*args,**kwargs):
res = func(*args,**kwargs)
return res # 将func的返回值返回出去
return warpper

test = outer(test) # test变量名执行warpper内存地址。
help(test)

# ==== 执行结果 ==== Ps:可以看到test的help信息还是warpper,这个偷梁换柱不彻底。

"""
Help on function warpper in module __main__:

warpper(*args, **kwargs)
"""

  在使用wraps装饰器对warpper函数进行装饰后,那么就是真正意义上的移花接木偷天换日。继续看...

from functools import wraps

def test(x,y,z):
print(x,y,z)
return "结果"

def outer(func):
@wraps(func) # 传入被装饰对象。完成移花接木
def warpper(*args,**kwargs):
res = func(*args,**kwargs)
return res # 将func的返回值返回出去
return warpper

test = outer(test) # test变量名执行warpper内存地址。
help(test)

# ==== 执行结果 ==== Ps:移花接木成功

"""
Help on function test in module __main__:

test(x, y, z)
"""

Python三大器之装饰器的更多相关文章

  1. python基础(补充):python三大器之装饰器

    函数作为返回值 高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回. 我们来实现一个可变参数的求和.通常情况下,求和的函数是这样定义的: def calc_sum(*args): i = ...

  2. python三大器之装饰器的练习

    装饰器 加载顺序从下至上 执行顺序从上至下 ''' 多层装饰器 ''' def deco1(func): #func=deco2 def wrapper1(*args, **kwargs): '''t ...

  3. python笔记 - day4-之装饰器

                 python笔记 - day4-之装饰器 需求: 给f1~f100增加个log: def outer(): #定义增加的log print("log") ...

  4. Python进阶(六)----装饰器

    Python进阶(六)----装饰器 一丶开放封闭原则 开放原则: ​ 增加一些额外的新功能 封闭原则: ​ 不改变源码.以及调用方式 二丶初识装饰器 装饰器: ​ 也可称装饰器函数,诠释开放封闭原则 ...

  5. python高级之装饰器

    python高级之装饰器 本节内容 高阶函数 嵌套函数及闭包 装饰器 装饰器带参数 装饰器的嵌套 functools.wraps模块 递归函数被装饰 1.高阶函数 高阶函数的定义: 满足下面两个条件之 ...

  6. [python基础]关于装饰器

    在面试的时候,被问到装饰器,在用的最多的时候就@classmethod ,@staticmethod,开口胡乱回答想这和C#的static public 关键字是不是一样的,等面试回来一看,哇,原来是 ...

  7. Python深入05 装饰器

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 装饰器(decorator)是一种高级Python语法.装饰器可以对一个函数.方法 ...

  8. Day04 - Python 迭代器、装饰器、软件开发规范

    1. 列表生成式 实现对列表中每个数值都加一 第一种,使用for循环,取列表中的值,值加一后,添加到一空列表中,并将新列表赋值给原列表 >>> a = [0, 1, 2, 3, 4, ...

  9. Noah的学习笔记之Python篇:装饰器

    Noah的学习笔记之Python篇: 1.装饰器 2.函数“可变长参数” 3.命令行解析 注:本文全原创,作者:Noah Zhang  (http://www.cnblogs.com/noahzn/) ...

随机推荐

  1. 以太坊智能合约开发框架Truffle

    前言 部署智能合约有多种方式,命令行的浏览器的渠道都有,但往往跟我们程序员的风格不太相符,因为我们习惯了在IDE里写了代码然后打包运行看效果. 虽然现在IDE中已经存在了Solidity插件,可以编写 ...

  2. Java实现 LeetCode 650 只有两个键的键盘(递归 || 数学)

    650. 只有两个键的键盘 最初在一个记事本上只有一个字符 'A'.你每次可以对这个记事本进行两种操作: Copy All (复制全部) : 你可以复制这个记事本中的所有字符(部分的复制是不允许的). ...

  3. Java中TreeSet的详细用法

    第1部分 TreeSet介绍 TreeSet简介 TreeSet 是一个有序的集合,它的作用是提供有序的Set集合.它继承于AbstractSet抽象类,实现了NavigableSet, Clonea ...

  4. Java实现蓝桥杯勾股定理

    勾股定理,西方称为毕达哥拉斯定理,它所对应的三角形现在称为:直角三角形. 已知直角三角形的斜边是某个整数,并且要求另外两条边也必须是整数. 求满足这个条件的不同直角三角形的个数. [数据格式] 输入一 ...

  5. Java实现哥德巴赫猜想

    验证哥德巴赫猜想:任何一个大于 6 的偶数,都能分解成两个质数的和.要求输入一个整数,输出这个 数能被分解成哪两个质数的和. eg : 14 14=3+11 14=7+7 public class T ...

  6. PAT 部分A+B

    正整数​​A的“DA(为 1 位整数)部分”定义为由A中所有DA组成的新整数PA,例如:给定A=3862767,DA=6,则A的“6 部分”PA是 66,因为A中有 2 个 6. 现给定A,DA,B, ...

  7. DirectX11 With Windows SDK--31 阴影映射

    前言 阴影既暗示着光源相对于观察者的位置关系,也从侧面传达了场景中各物体之间的相对位置.本章将起底最基础的阴影映射算法,而像复杂如级联阴影映射这样的技术,也是在阴影映射的基础上发展而来的. 学习目标: ...

  8. 常见的几种java排序算法

    一.分类: 1)插入排序(直接插入排序.希尔排序) 2)交换排序(冒泡排序.快速排序) 3)选择排序(直接选择排序.堆排序) 4)归并排序 5)分配排序(基数排序) 所需辅助空间最多:归并排序 所需辅 ...

  9. jQuery实现瀑布流布局

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  10. eurekaAutoServiceRegistration 异常

    方案来自:https://github.com/spring-cloud/spring-cloud-netflix/issues/1952 解决办法: @Component public class ...