目录
简述装饰器
装饰器是 Python 的重要组成部分。简而言之:它们是修改其他功能的功能。它们有助于使我们的代码更短、更 Pythonic。大多数初学者不知道在哪里使用它们,所以我将分享一些装饰器可以让你的代码更简洁的地方。这可能是最难掌握的概念之一。我们将一步一步地进行,以便您完全理解它。
一. 函数也是对象
首先要搞清楚函数的性质:
def hi(name="yasoob"):
return "hi " + name
print(hi())
# output: 'hi yasoob'
# We can even assign a function to a variable like
greet = hi
# We are not using parentheses here because we are not calling the function hi
# instead we are just putting it into the greet variable. Let's try to run this
print(greet())
# output: 'hi yasoob'
# Let's see what happens if we delete the old hi function!
del hi
print(hi())
#outputs: NameError
print(greet())
#outputs: 'hi yasoob'eer
结论1:函数可以当对象变量用。
二、在函数中定义函数
因此,这些是功能方面的基础知识。让我们把你的知识更进一步。在 Python 中,我们可以在其他函数中定义函数:
def hi(name="yasoob"):
print("now you are inside the hi() function")
def greet():
return "now you are in the greet() function"
def welcome():
return "now you are in the welcome() function"
print(greet())
print(welcome())
print("now you are back in the hi() function")
hi()
#output:now you are inside the hi() function
# now you are in the greet() function
# now you are in the welcome() function
# now you are back in the hi() function
# This shows that whenever you call hi(), greet() and welcome()
# are also called. However the greet() and welcome() functions
# are not available outside the hi() function e.g:
greet()
#outputs: NameError: name 'greet' is not defined
所以现在我们知道我们可以在其他函数中定义函数。换句话说:我们可以制作嵌套函数。现在你需要再学习一件事,函数也可以返回函数。
三、从函数内部返回函数:
不必在另一个函数中执行一个函数,我们也可以将其作为输出返回:
def hi(name="yasoob"):
def greet():
return "now you are in the greet() function"
def welcome():
return "now you are in the welcome() function"
if name == "yasoob":
return greet
else:
return welcome
a = hi()
print(a)
#outputs: <function greet at 0x7f2143c01500>
#This clearly shows that `a` now points to the greet() function in hi()
#Now try this
print(a())
#outputs: now you are in the greet() function
再看一遍代码。在 if/else 子句中,我们返回的是 greet 和welcome,而不是 greet() 和welcome()。这是为什么?这是因为当你在它后面加上一对括号时,函数就会被执行;而如果你不把括号放在它后面,那么它可以被传递并可以分配给其他变量而不执行它。你明白了吗?让我更详细地解释一下。当我们写 a = hi() 时,hi() 会被执行,因为默认名称是 yasoob,所以会返回函数 greet。如果我们将语句更改为 a = hi(name = "ali") 则将返回欢迎函数。我们还可以执行 print hi()() 输出,现在您在 greet() 函数中。
四、将一个函数作为参数提供给另一个函数
def hi():
return "hi yasoob!"
def doSomethingBeforeHi(func):
print("I am doing some boring work before executing hi()")
print(func())
doSomethingBeforeHi(hi)
#outputs:I am doing some boring work before executing hi()
# hi yasoob!
现在,您已经掌握了了解装饰器的真正含义所需的所有知识。装饰器让您可以在函数之前和之后执行代码。
五、写一个装饰器
在上一个示例中,我们实际上制作了一个装饰器!让我们修改之前的装饰器,制作一个更实用的程序:
def a_new_decorator(a_func):
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction
def a_function_requiring_decoration():
print("I am the function which needs some decoration to remove my foul smell")
a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()
a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
你明白了吗?我们只是应用了之前学到的原理。这正是装饰器在 Python 中所做的!它们包装一个函数并以一种或另一种方式修改其行为。现在您可能想知道为什么我们没有在代码中的任何地方使用@?这只是组成装饰功能的一种简短方法。下面是我们如何使用 @ 运行前面的代码示例。
@a_new_decorator
def a_function_requiring_decoration():
"""Hey you! Decorate me!"""
print("I am the function which needs some decoration to "
"remove my foul smell")
a_function_requiring_decoration()
#outputs: I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()
#the @a_new_decorator is just a short way of saying:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
我希望您现在对装饰器在 Python 中的工作方式有一个基本的了解。现在我们的代码有一个问题。如果我们运行:
print(a_function_requiring_decoration.__name__) # Output: wrapTheFunction
这不是我们所期望的!它的名字是“a_function_requiring_decoration”。好吧,我们的函数被 wrapTheFunction 取代了。它覆盖了我们函数的名称和文档字符串。幸运的是,Python 为我们提供了一个简单的函数来解决这个问题,那就是 functools.wraps。让我们修改之前的示例以使用 functools.wraps:
from functools import wraps
def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction
@a_new_decorator
def a_function_requiring_decoration():
"""Hey yo! Decorate me!"""
print("I am the function which needs some decoration to "
"remove my foul smell")
print(a_function_requiring_decoration.__name__)
# Output: a_function_requiring_decoration
现在好多了。让我们继续学习装饰器的一些用例。
Blueprint:
from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated
@decorator_name
def func():
return("Function is running")
can_run = True
print(func())
# Output: Function is running
can_run = False
print(func())
# Output: Function will not run
注意:@wraps 接受一个要装饰的函数,并添加复制函数名称、文档字符串、参数列表等的功能。这允许我们在装饰器中访问预装饰函数的属性。
5.1. 应用
现在让我们看一下装饰器真正闪耀的领域,以及它们的使用使事情变得非常容易管理。下面看看装饰器的管理应用。
5.2. 用于权限管理
装饰器可以帮助检查某人是否被授权使用 Web 应用程序中的端点。它们广泛用于 Flask Web 框架和 Django。这是一个使用基于装饰器的身份验证的示例:
Example :
from functools import wraps
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
authenticate()
return f(*args, **kwargs)
return decorated
5.3. 用于日志管理
日志记录是装饰者大放异彩的另一个领域。这是一个例子:
from functools import wraps
def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logit
def addition_func(x):
"""Do some math."""
return x + x
result = addition_func(4)
# Output: addition_func was called
我相信你已经在考虑装饰器的一些巧妙用途。
六. 装饰器参数
想一想,@wraps 不也是一个装饰器吗?但是,它需要一个参数,就像任何普通函数一样。那么,为什么我们也不能这样做呢?
这是因为当您使用 @my_decorator 语法时,您正在应用一个将单个函数作为参数的包装函数。请记住,Python 中的一切都是对象,这包括函数!考虑到这一点,我们可以编写一个返回包装函数的函数。
6.1.函数装饰器
让我们回到我们的日志示例,并创建一个包装器,让我们指定要输出到的日志文件。
from functools import wraps
def logit(logfile='out.log'):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# Open the logfile and append
with open(logfile, 'a') as opened_file:
# Now we log to the specified logfile
opened_file.write(log_string + '\n')
return func(*args, **kwargs)
return wrapped_function
return logging_decorator
@logit()
def myfunc1():
pass
myfunc1()
# Output: myfunc1 was called
# A file called out.log now exists, with the above string
@logit(logfile='func2.log')
def myfunc2():
pass
myfunc2()
# Output: myfunc2 was called
# A file called func2.log now exists, with the above string
6.2. 类装饰器
现在我们在生产环境中使用了 logit 装饰器,但是当我们的应用程序的某些部分被认为是关键时,故障可能需要立即引起注意。假设有时您只想登录到文件。其他时候,您希望发送电子邮件,以便引起您的注意,并且仍然保留日志以供您自己记录。这是使用继承的一个例子,但到目前为止我们只看到了用于构建装饰器的函数。
class logit(object):
_logfile = 'out.log'
def __init__(self, func):
self.func = func
def __call__(self, *args):
log_string = self.func.__name__ + " was called"
print(log_string)
# Open the logfile and append
with open(self._logfile, 'a') as opened_file:
# Now we log to the specified logfile
opened_file.write(log_string + '\n')
# Now, send a notification
self.notify()
# return base func
return self.func(*args)
def notify(self):
# logit only logs, no more
pass
这个实现还有一个额外的优点,就是比嵌套函数方法更简洁,并且包装函数仍然会使用与以前相同的语法:
logit._logfile = 'out2.log' # if change log file
@logit
def myfunc1():
pass
myfunc1()
# Output: myfunc1 was called
现在,让我们将 logit 子类化以添加电子邮件功能(尽管此处不会涉及此主题)。
class email_logit(logit):
'''
A logit implementation for sending emails to admins
when the function is called.
'''
def __init__(self, email='admin@myproject.com', *args, **kwargs):
self.email = email
super(email_logit, self).__init__(*args, **kwargs)
def notify(self):
# Send an email to self.email
# Will not be implemented here
pass
从这里开始,@email_logit 的工作方式与 @logit 类似,但除了记录日志外,还会向管理员发送一封电子邮件。