第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_nameself.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。我很想知道我的读者是如何使用内置装饰器的,以及他们是如何使用自己的定制装饰器的。