深入研究Python装饰器

总览

Python装饰器是我最喜欢的Python功能之一。 它们是我在任何编程语言中都看到过的最面向用户和面向开发人员友好的面向方面编程的实现。

装饰器使您可以扩充,修改或完全替换函数或方法的逻辑。 这种枯燥的描述并不能使装饰工伸张正义。 一旦开始使用它们,您将发现整个整洁的应用程序,这些应用程序可让您的代码保持紧密和整洁,并将重要的“管理”任务从代码的主要流程移出到修饰器中。

在我们跳入一些很酷的示例之前,如果您想进一步了解装饰器的起源,那么函数装饰器首先出现在Python 2.4中。 请参阅PEP-0318 ,以了解有关“装饰器”名称的历史,原理和选择的有趣讨论。 类装饰器首先出现在Python 3.0中。 请参阅PEP-3129 ,它非常简短,它建立在函数装饰器的所有概念和思想之上。

酷装饰器示例

有太多的例子让我很难选择。 我在这里的目标是开放您的可能性,并向您介绍超级有用的功能,您可以通过单线真正注释功能来立即添加到代码中。

经典示例是内置的@staticmethod和@classmethod装饰器。 这些装饰器分别将类方法转换为静态方法(不提供自身的第一个自变量)或类方法(第一个自变量为类而不是实例)。

经典装饰

class A(object):
    @classmethod
    def foo(cls):
        print cls.__name__

    @staticmethod
    def bar():
        print 'I have no use for the instance or class'
        
       
A.foo()
A.bar()

输出:

A
I have no use for the instance or class

当您没有实例时,静态和类方法很有用。 它们使用了很多,并且在没有装饰语法的情况下应用它们确实很麻烦。

记忆化

@memoize装饰器会记住针对特定参数集的函数第一次调用的结果并将其缓存。 随后具有相同参数的调用将返回缓存的结果。

对于执行昂贵的处理(例如,访问远程数据库或调用多个REST API)并且经常使用相同参数调用的功能,这可能是巨大的性能提升。

@memoize
def fetch_data(items):
    """Do some serious work here"""
    result = [fetch_item_data(i) for i in items]
    return result

基于合同的程序设计

几个称为@precondition和@postcondition的装饰器来验证输入参数和结果如何? 考虑以下简单功能:

def add_small ints(a, b):
    """Add two ints whose sum is still an int"""
    return a + b

如果有人用大整数,长整型甚至是字符串来调用它,它会悄悄地成功,但是会违反协定,即结果必须为int。 如果有人使用不匹配的数据类型来调用它,则会收到一般的运行时错误。 您可以将以下代码添加到函数中:

def add_small ints(a, b):
    """Add two ints in the whose sum is still an int"""
    assert(isinstance(a, int), 'a must be an int')
    assert(isinstance(a, int), 'b must be an int')
    result = a + b
    assert(isinstance(result, int), 
           'the arguments are too big. sum is not an int')
    return result

我们漂亮的单行add_small_ints()函数变成了一个丑陋的断言,带有丑陋的断言。 在现实世界中的功能中,一眼就能看出其实际作用是非常困难的。 使用装饰器,前置条件和后置条件可以移出函数主体:

@precondition(isinstance(a, int), 'a must be an int')
@precondition(isinstance(b, int), 'b must be an int')
@postcondition(isinstance(retval, int), 
               'the arguments are too big. sum is not an int')
def add_small ints(a, b):
    """Add two ints in the whose sum is still an int"""
    return a + b

授权书

假设您有一个需要为其所有方法通过秘密进行授权的类。 作为完善的Python开发人员,您可能会选择@authorized方法装饰器,如下所示:

class SuperSecret(object):
    @authorized
    def f_1(*args, secret):
        """ """
        
    @authorized
    def f_2(*args, secret):
        """ """
    .
    .
    .
    @authorized
    def f_100(*args, secret):
        """ """

那绝对是个好方法,但是重复做起来有点烦人,特别是如果您有很多这样的课程的话。

更重要的是,如果有人添加了新方法而忘记添加@authorized装饰,那么您的手上就会遇到安全问题。 没有恐惧。 Python 3类装饰器为您服务。 以下语法将允许您(使用正确的类装饰器定义)自动授权目标类的每个方法:


@authorized
class SuperSecret(object):
    def f_1(*args, secret):
        """ """
        
    def f_2(*args, secret):
        """ """
    .
    .
    .
    def f_100(*args, secret):
        """ """

您要做的就是装饰类本身。 请注意,装饰器可以很聪明,可以忽略__init __()之类的特殊方法,也可以根据需要配置为应用于特定子集。 天空(或您的想象力)是极限。

更多例子

如果您想进一步了解示例,请查看PythonDecoratorLibrary

什么是装饰器?

现在您已经看到了一些实际的例子,是时候揭开魔术的面纱了。 正式定义是装饰器是一个可调用的对象,它接受一个可调用对象(目标)并返回一个可调用对象(修饰对象),该对象接受与原始目标相同的参数。

哇! 那是很多词,彼此难以理解。 首先,可调用的是什么? 可调用对象只是具有__call __()方法的Python对象。 这些通常是函数,方法和类,但是您可以在其中一个类上实现__call __()方法,然后您的类实例也将变为可调用对象。 要检查Python对象是否可调用,可以使用callable()内置函数:


callable(len)
True

callable('123')
False

请注意, callable()函数已从Python 3.0中删除,并在Python 3.2中重新带回,因此,如果由于某种原因而使用Python 3.0或3.1,则必须像hasattr(len, '__call__')那样检查__call__属性的存在。 hasattr(len, '__call__')

当您使用这样的装饰器,并使用@语法将其应用到某些可调用对象时,原始可调用对象将替换为从装饰器返回的可调用对象。 这可能有点难以理解,所以让我们通过研究一些简单装饰器的勇气来举例说明。

功能装饰器

函数装饰器是用于装饰函数或方法的装饰器。 假设我们要打印字符串“是的,它有用!” 每次在实际调用原始函数之前调用修饰的函数或方法。 这是一种非装饰的方法。 这是函数foo() ,它在此处显示“ foo()”:

def foo():
    print 'foo() here'

foo()

Output:

foo() here

这是达到预期结果的丑陋方法:

original_foo = foo

def decorated_foo():
    print 'Yeah, it works!'
    original_foo()

foo = decorated_foo
foo()

Output:

Yeah, it works!
foo() here

这种方法存在几个问题:

  • 这是很多工作。
  • 您使用中间名称(如original_foo()decorated_foo())污染名称空间。
  • 您必须对要使用相同功能进行装饰的所有其他功能重复执行此操作。

一个可以达到相同结果并且可以重用和可组合的装饰器如下所示:

def yeah_it_works(f):
    def decorated(*args, **kwargs):
        print 'Yeah, it works'
        return f(*args, **kwargs)
   return decorated

请注意,yeah_it_works()是一个接受可调用** f **作为参数的函数(因此可调用),并且它返回一个接受任何数量和类型的参数的可调用(嵌套的函数** decorated **)。

现在我们可以将其应用于任何功能:


@yeah_it_works
def f1()
    print 'f1() here'

@yeah_it_works
def f2()
    print 'f3() here'

@yeah_it_works
def f3()
    print 'f3() here'

f1()
f2()
f3()


Output:


Yeah, it works
f1() here
Yeah, it works
f2() here
Yeah, it works
f3() here

它是如何工作的? 原来的f1f2f3函数被yeah_it_works返回的修饰嵌套函数代替 。 对于每个单独的函数,捕获的f都是原始函数( f1f2f3 ),因此修饰的函数是不同的,并且做对了,这是打印的“是的,它起作用了!” 然后调用原始函数f

类装饰器

类装饰器在更高级别上运行并装饰整个类。 它们的效果发生在类定义时。 您可以使用它们来添加或删除任何修饰类的方法,甚至可以将函数修饰符应用于整个方法集。

假设我们要在类属性中跟踪从特定类引发的所有异常。 假设我们已经有一个名为track_exceptions_decorator的函数装饰器来执行此功能。 没有类装饰器,您可以将其手动应用于每种方法,也可以求助于metaclasses 。 例如:


class A(object):
    @track_exceptions_decorator
    def f1():
        ...
        
    @track_exceptions_decorator
    def f2():
        ...
    .
    .
    .
    @track_exceptions_decorator
    def f100():
        ...

实现相同结果的类装饰器是:


def track_exception(cls):
    # Get all callable attributes of the class
    callable_attributes = {k:v for k, v in cls.__dict__.items() 
                           if callable(v)}
    # Decorate each callable attribute of to the input class
    for name, func in callable_attributes.items():
        decorated = track_exceptions_decorator(func)
        setattr(cls, name, decorated)
    return cls

@track_exceptions
class A:
    def f1(self): 
        print('1')
    
    def f2(self):
        print('2')

结论

Python以其灵活性而闻名。 装饰者将其提升到一个新的水平。 您可以将横切关注点打包在可重用的装饰器中,并将其应用于函数,方法和整个类。 我强烈建议每个认真的Python开发人员熟悉装饰器,并充分利用其优势。

翻译自: https://code.tutsplus.com/tutorials/deep-dive-into-python-decorators--cms-29725

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值