Python装饰器是必要的吗?

4
我对装饰器有基本的了解,但它们似乎是多余和“hacky”,就像C宏一样,只不过是针对函数的。
一篇强调装饰器重要性的文章举了装饰器用法的例子。
from myapp.log import logger

def log_order_event(func):
    def wrapper(*args, **kwargs):
        logger.info("Ordering: %s", func.__name__)
        order = func(*args, **kwargs)
        logger.debug("Order result: %s", order.result)
        return order
    return wrapper

@log_order_event
def order_pizza(*toppings):
    # let's get some pizza!

order_pizza(*toppings) # 用法

这不是等于下面没有修饰符的代码吗?

from myapp.log import logger

def log_order_event(func, *args, **kwargs):
    logger.info("Ordering: %s", func.__name__)
    order = func(*args, **kwargs)
    logger.debug("Order result: %s", order.result)
    return order

def order_pizza(*toppings):
    # let's get some pizza!

log_order_event(order_pizza, *toppings) # 使用方法

实际上,第二个代码片段是否更加容易编写,因为它只有一个函数而不是一个函数和一个包装函数?虽然使用方法调用比较长,但更清楚地指示了实际被调用的内容。

这仅仅是一种口味和语法糖问题,还是我遗漏了什么?


1
这个问题并不是Stackoverflow的好选择...你说的那两个语句基本上是等价的...你也是对的,这只是一种语法糖和偏好。 - Joran Beasley
1
你认为如何进行扩展?如果你有大量的函数并且需要对每个函数进行跟踪或类似的操作(例如)。 - Rahul P
5
无装饰器的代码需要您调用一个不相关的函数,以获得您想要的行为。考虑在其他代码已经使用您的函数后添加此装饰器时的差异。(装饰器只是调用函数并在定义函数后替换名称的简洁语法,例如 order_pizza = log_order_event(order_pizza),因此绝对不是一种黑客技巧。) - Ry-
1
不,使用高阶函数并不是hacky的,也不像宏一样。@syntax只是为了让order_pizza = log_order_event(order_pizza)更加简洁明了。 - juanpa.arrivillaga
1
@geckos 嗯,更准确地说,它是在添加行为,"装饰" 可调用对象 - juanpa.arrivillaga
显示剩余6条评论
1个回答

4

这两个代码片段在行为上是相等的。然而,我会选择使用装饰器版本,原因有两点:

  1. Readability: With snippet 2, you'd require an explicit function call to a less relevant method log_order_event() whose main purpose is logging. This logging functionality may be required by other methods such as order_appetizers() and therefore, you would require individual calls to log_order_event() for every such method. Wouldn't it be better(from a readability perspective) to have a wrapper defined once & for all, that performs logging and you may call the wrapper(as a decorator) for such methods whenever necessary? Decorators are syntactic sugar but certainly not a hack. In fact, PEP 318 clearly talks about the motivation behind decorators, you must read that.
  2. Scalability: Consider a situation where your code has several methods(say 20) such as order_appetizers(), order_maincourse(), order_desserts() and so on. Let's also assume that you'd like to perform some prior checks before ordering any of these items, say check for toppings(for pizza's), calories(for desserts) and so on. When using decorators, its just a matter of defining the wrapper functions in a separate wrapper class for each check, import it in your main code and decorate the required methods respectively. That way, your wrapper functions are completely isolated and the main logic remains clean and easy to maintain.

    @check_toppings
    @log_order_event
    def order_pizza(*toppings):
        # let's get some pizza!
    
    @check_calories
    @log_order_event
    def order_desserts():
        # let's order some desserts!
    ...and so on for 20 more methods
    

    With a decoratorless logic, while you can separate the check(and log) methods in a separate class and import in your main logic, you would still have to add individual calls to each of these methods inside each order method which from a code maintenance perspective could prove to be daunting, especially for new eyes.


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接