第25章-装饰师¶
Python装饰师真的很酷,但一开始可能有点难以理解。python中的decorator是接受另一个函数作为参数的函数。装饰器通常会修改或增强它接受的函数,并返回修改后的函数。这意味着当您调用一个修饰函数时,您将得到一个可能与基本定义稍有不同的函数,该函数可能具有其他特性。但我们还是后退一点。我们可能应该回顾一下装饰器的基本组成部分,即功能。
一个简单的函数¶
函数是以python关键字开头的代码块。 def 后跟函数的实际名称。函数可以接受零个或多个参数、关键字参数或二者的混合。函数总是返回一些东西。如果不指定函数应该返回什么,它将返回 None .下面是一个非常简单的函数,它只返回一个字符串:
def a_function():
"""A pretty useless function"""
return "1+1"
if __name__ == "__main__":
value = a_function()
print(value)
我们在上面的代码中所做的就是调用函数并打印返回值。让我们创建另一个函数:
def another_function(func):
"""
A function that accepts another function
"""
def other_func():
val = "The result of %s is %s" % (func(),
eval(func())
)
return val
return other_func
此函数接受一个参数,该参数必须是函数或可调用的。实际上,它实际上只应该使用以前定义的函数来调用。您将注意到这个函数内部有一个嵌套函数,我们正在调用它 other_func .它将获取传递给它的函数的结果,对其进行评估,并创建一个字符串,告诉我们它做了什么,然后返回。让我们看看代码的完整版本:
def another_function(func):
"""
A function that accepts another function
"""
def other_func():
val = "The result of %s is %s" % (func(),
eval(func())
)
return val
return other_func
def a_function():
"""A pretty useless function"""
return "1+1"
if __name__ == "__main__":
value = a_function()
print(value)
decorator = another_function(a_function)
print(decorator())
这就是装饰工的工作原理。我们创建一个函数,然后将其传递给第二个函数。第二个功能是 decorator 功能。装饰器将修改或增强传递给它的函数,并返回修改。如果运行此代码,则应将以下内容视为输出到stdout:
1+1
The result of 1+1 is 2
我们稍微改一下代码 another_function 装饰师:
def another_function(func):
"""
A function that accepts another function
"""
def other_func():
val = "The result of %s is %s" % (func(),
eval(func())
)
return val
return other_func
@another_function
def a_function():
"""A pretty useless function"""
return "1+1"
if __name__ == "__main__":
value = a_function()
print(value)
您将注意到,在python中,修饰符从 @ 符号后面是函数名,我们将用它来“装饰”我们的常规函数。要应用这个修饰器,只需将它放在函数定义之前的行上。当我们打电话的时候 a_function ,它将被装饰,我们将得到以下结果:
The result of 1+1 is 2
让我们创建一个真正有用的装饰器。
创建日志装饰器¶
有时,您需要创建一个关于函数正在执行的操作的日志。大多数时候,您可能会在函数本身中进行日志记录。有时,您可能希望在功能级别执行该操作,以了解程序的流程,或者实现一些业务规则,比如审计。下面是一个小装饰器,我们可以使用它来记录任何函数的名称及其返回:
import logging
def log(func):
"""
Log what function is called
"""
def wrap_log(*args, **kwargs):
name = func.__name__
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
# add file handler
fh = logging.FileHandler("%s.log" % name)
fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
formatter = logging.Formatter(fmt)
fh.setFormatter(formatter)
logger.addHandler(fh)
logger.info("Running function: %s" % name)
result = func(*args, **kwargs)
logger.info("Result: %s" % result)
return func
return wrap_log
@log
def double_function(a):
"""
Double the input parameter
"""
return a*2
if __name__ == "__main__":
value = double_function(2)
这个小剧本有一个 log 接受函数作为其唯一参数的函数。它将根据函数名创建一个记录器对象和一个日志文件名。然后,log函数将记录调用的函数以及函数返回的内容(如果有)。
内置装饰器¶
python附带了几个内置的装饰器。三大公司是:
@分类方法
@静态法
@财产
在python的标准库的各个部分也有一些装饰。一个例子是 functools.wraps .不过,我们将把范围限制在上述三个方面。
@classmethod和@staticmethod¶
我自己从来没有用过这些,所以我做了一些研究。这个 @分类方法 decorator可以用类的实例调用,也可以直接由类本身作为第一个参数调用。根据python文档: 它可以在类(如c.f())或实例(如c().f())上调用。实例被忽略,但类除外。如果为派生类调用了类方法,则派生类对象将作为隐含的第一个参数传递 .我在研究中发现的@classmethod装饰器的主要用例是作为用于初始化的备用构造函数或辅助方法。
这个 @静态法 decorator只是类内部的一个函数。可以同时调用它,也可以不实例化类。一个典型的用例是当你有一个你认为它与类有联系的函数时。这在很大程度上是一种风格选择。
查看这两个装饰器如何工作的代码示例可能会有所帮助:
class DecoratorTest(object):
"""
Test regular method vs @classmethod vs @staticmethod
"""
def __init__(self):
"""Constructor"""
pass
def doubler(self, x):
""""""
print("running doubler")
return x*2
@classmethod
def class_tripler(klass, x):
""""""
print("running tripler: %s" % klass)
return x*3
@staticmethod
def static_quad(x):
""""""
print("running quad")
return x*4
if __name__ == "__main__":
decor = DecoratorTest()
print(decor.doubler(5))
print(decor.class_tripler(3))
print(DecoratorTest.class_tripler(3))
print(DecoratorTest.static_quad(2))
print(decor.static_quad(3))
print(decor.doubler)
print(decor.class_tripler)
print(decor.static_quad)
这个例子演示了可以以相同的方式调用正则方法和两个修饰方法。您会注意到,您可以直接从类或类的实例调用@classmethod和@staticmethod修饰函数。如果尝试用类(即decoratorest.doubler(2))调用常规函数,您将收到一个 TypeError .您还将注意到,最后一条print语句显示decor.static_quad返回一个正则函数,而不是绑定方法。
python属性¶
python有一个整洁的小概念,叫做属性,它可以做一些有用的事情。我们将研究如何执行以下操作:
将类方法转换为只读属性
将setter和getter重新实现为属性
使用属性的最简单方法之一是将其用作方法的修饰器。这允许您将类方法转换为类属性。当我需要进行某种价值组合时,我发现这很有用。其他人发现它对于编写他们想要访问的转换方法非常有用。让我们来看一个简单的例子:
class Person(object):
""""""
def __init__(self, first_name, last_name):
"""Constructor"""
self.first_name = first_name
self.last_name = last_name
@property
def full_name(self):
"""
Return the full name
"""
return "%s %s" % (self.first_name, self.last_name)
在上面的代码中,我们创建了两个类属性或属性: self.first_name 和 self.last_name .接下来我们创建一个 full_name 方法具有 @财产 附属于它的装饰物。这允许我们在口译员会话中执行以下操作:
>>> person = Person("Mike", "Driscoll")
>>> person.full_name
'Mike Driscoll'
>>> person.first_name
'Mike'
>>> person.full_name = "Jackalope"
Traceback (most recent call last):
File "<string>", line 1, in <fragment>
AttributeError: can't set attribute
如您所见,因为我们将该方法转换为一个属性,所以我们可以使用普通的点表示法来访问它。但是,如果我们尝试将属性设置为不同的值,则会导致 AttributeError 有待提高。唯一改变 full_name 财产是间接的:
>>> person.first_name = "Dan"
>>> person.full_name
'Dan Driscoll'
这是一种限制,所以让我们来看另一个例子,在这里我们可以创建一个允许我们设置它的属性。
用python属性替换setter和getter¶
让我们假设我们有一些遗留代码,有些人编写的代码不太了解Python。如果你和我一样,你以前就见过这种代码:
from decimal import Decimal
class Fees(object):
""""""
def __init__(self):
"""Constructor"""
self._fee = None
def get_fee(self):
"""
Return the current fee
"""
return self._fee
def set_fee(self, value):
"""
Set the fee
"""
if isinstance(value, str):
self._fee = Decimal(value)
elif isinstance(value, Decimal):
self._fee = value
要使用这个类,我们必须使用定义的setter和getter:
>>> f = Fees()
>>> f.set_fee("1")
>>> f.get_fee()
Decimal('1')
如果您希望在不破坏依赖于这段代码的所有应用程序的情况下,将属性的常规点标记访问添加到此代码,则只需添加一个属性即可更改它:
from decimal import Decimal
class Fees(object):
""""""
def __init__(self):
"""Constructor"""
self._fee = None
def get_fee(self):
"""
Return the current fee
"""
return self._fee
def set_fee(self, value):
"""
Set the fee
"""
if isinstance(value, str):
self._fee = Decimal(value)
elif isinstance(value, Decimal):
self._fee = value
fee = property(get_fee, set_fee)
我们在代码末尾添加了一行。现在我们可以这样做:
>>> f = Fees()
>>> f.set_fee("1")
>>> f.fee
Decimal('1')
>>> f.fee = "2"
>>> f.get_fee()
Decimal('2')
如你所见,当我们使用 属性 通过这种方式,它允许Fee属性在不破坏遗留代码的情况下自行设置和获取值。让我们用属性修饰器重写这段代码,看看是否可以让它允许设置。
from decimal import Decimal
class Fees(object):
""""""
def __init__(self):
"""Constructor"""
self._fee = None
@property
def fee(self):
"""
The fee property - the getter
"""
return self._fee
@fee.setter
def fee(self, value):
"""
The setter of the fee property
"""
if isinstance(value, str):
self._fee = Decimal(value)
elif isinstance(value, Decimal):
self._fee = value
if __name__ == "__main__":
f = Fees()
上面的代码演示了如何为 fee 属性。您可以通过修饰第二个方法来实现这一点,该方法也被调用 fee 和一个叫做 @fee.setter .当您执行如下操作时,将调用setter:
>>> f = Fees()
>>> f.fee = "1"
如果你看签名 属性 它将fget、fset、fdel和doc作为“参数”。可以使用相同的名称创建另一个修饰方法,以对应于使用 @fee.deleter 如果你想抓住 del 针对属性的命令。
总结¶
此时,您应该知道如何创建自己的装饰器,以及如何使用一些Python的内置装饰器。我们研究了@classmethod、@property和@staticmethod。我很想知道我的读者是如何使用内置装饰器的,以及他们是如何使用自己的定制装饰器的。