关于 advice 的参数数目

我在定义 advice 时发现,有时会报错 wrong-number-of-arguments

比如:

(defun some-advice (orig-func &rest args)
  do-something
)
(advice-add #'some-function :after #'some-advice)

这时如果把 some-advice 的 orig-func 参数去掉就可以通过,但是有的时候这样就没有任何问题。

我的问题是,为什么两种情况下 orig-func 都没有在 advice 函数体中被调用,但有时候可以通过,有时候又不能呢?

我是不是可以这样认为,通常只要我不调用 orig-func 也就是原始函数,就不要加这个参数? @xuchunyang

我测试了一下。大概就是这样。 而且用defadvice的话会自动继承原函数的参数列表。

1 个赞

好的,多谢 :thumbsup:

(defun say(something)
  (message "say(args => \"%s\")" something))

(defun after-say (&rest args)
  (message "after-say(args => \"%s\")" args))

(defun after-say2 (f &rest args)
  (message "after-say2(f => %s, args => \"%s\")" f args))

(advice-add 'say :after #'after-say)
(advice-add 'say :after #'after-say2)
(say "foo")

;; output:
;; say(args => "foo")
;; after-say(args => "(foo)")
;; after-say2(f => foo, args => "nil")

:after 得到的只是原函数的参数。:around 才可以有 orig-func

3 个赞

但是 before 貌似可以有 orig-func?因为我运行 before 的时候没有报错

我知道怎么回事了,多谢指点! @twlz0ne

我的使用情形中(这里没有放我的代码,上面问题中的只是伪代码): 因为我 before advise 的函数本身可以跟参数,所以就没有报错; 但是 after advise 的函数本身不能跟参数,所以报错了。 但是并不意味着我 before advise 那样的用法就是正确的,因为 orig-func 在 before 中并不代表原始函数

@twlz0ne 的办法很好,善用 message 函数真的很重要。

虽然你 :before 那样写了,但是不调用 orign-func 应该就不会出错。

我认为 :before/:after 只是用 :around 在特定场景下的简便写法,它在 :before/:after 前/后已经执行过原始函数了,没必要传递过去再执行一遍。

是的,多谢指点。现在心里跟明镜似的,终于解开了我的疑惑。

我发现这样 advice 也有不好的地方,比如 advice 的位置没有 defadvice 中那样容易指定,刚开始容易记错 100 -100 innermost outermost 什么的很费解。当然这样的好处就是掌握以后会比 defadvice 更强大,细节也更完善了。

需要纠正的一点是,如果你 advise 的原始函数本身不能跟参数,那么即使你没有调用 orig-func,一样会报错。

依据文档 (elisp) Advice combinators :after 的调用方式是

(lambda (&rest r) (prog1 (apply OLDFUN r) (apply FUNCTION r)))

也就是说你的 some-advice 要与 some-function 的参数保持一致(不一定要完全一样,满足 apply 的调用规则就行)。其它的位置 :around :after 等的调用规则不清楚的话,使用前看下文档。

1 个赞

advice 的参数调用的问题,我之前也不明白,误以为所有位置的规则是一样的,糊里糊涂试了很久也没有搞懂,后来看下文档立马就明白了。

最好能把这段话加粗,很关键

使用哪一种参数形式主要还是看用途吧,比如:

  (defun div(a b)
    (/ a b))

  (defun before-div1(&rest args)
    "不关心参数是什么")

  (defun before-div2(a b)
    "检查除数是否大于0"
    (if (>= 0 b)
        (message "除数必须大于 0")))

  (advice-add 'div :before #'before-div1)
  (advice-add 'div :before #'before-div2)

  (div 2 0)

例子可能不太恰当,毕竟不能阻止原函数继续执行。