当前的 Ruby 方法是通过 super 调用的吗?
Is the current Ruby method called via super?
在运行时的方法中,有没有办法知道该方法是否已在子类中通过 super
调用?例如
module SuperDetector
def via_super?
# what goes here?
end
end
class Foo
include SuperDetector
def bar
via_super? ? 'super!' : 'nothing special'
end
end
class Fu < Foo
def bar
super
end
end
Foo.new.bar # => "nothing special"
Fu.new.bar # => "super!"
我怎么写via_super?
,或者,如果需要,via_super?(:bar)
?
可能有更好的方法,但总体思路是 Object#instance_of?
仅限于当前 class,而不是层次结构:
module SuperDetector
def self.included(clazz)
clazz.send(:define_method, :via_super?) do
!self.instance_of?(clazz)
end
end
end
class Foo
include SuperDetector
def bar
via_super? ? 'super!' : 'nothing special'
end
end
class Fu < Foo
def bar
super
end
end
Foo.new.bar # => "nothing special"
Fu.new.bar # => "super!"
但是,请注意,这不需要在 child 中显式 super
。如果 child 没有这样的方法而使用 parent 的方法,via_super?
仍然会 return true
。我认为除了检查堆栈跟踪或代码本身之外,没有办法只捕获 super
情况。
出色的@ndn 方法的附录:
module SuperDetector
def self.included(clazz)
clazz.send(:define_method, :via_super?) do
self.ancestors[1..-1].include?(clazz) &&
caller.take(2).map { |m| m[/(?<=`).*?(?=')/] }.reduce(&:==)
# or, as by @ndn: caller_locations.take(2).map(&:label).reduce(&:==)
end unless clazz.instance_methods.include? :via_super?
end
end
class Foo
include SuperDetector
def bar
via_super? ? 'super!' : 'nothing special'
end
end
class Fu < Foo
def bar
super
end
end
puts Foo.new.bar # => "nothing special"
puts Fu.new.bar # => "super!"
这里我们使用Kernel#caller
来确保调用的方法的名称与super中的名称匹配class。如果不是直接后代,这种方法可能需要一些额外的调整(caller(2)
应该更改为更复杂的分析,)但你可能明白了。
UPD 感谢@Stefan 对其他答案的评论,更新为 unless defined
以使其在 Foo
和 Fu
include SuperDetector
.
UPD2 使用祖先来检查超级而不是直接比较。
编辑 根据 Stefan 的建议进行了改进。
module SuperDetector
def via_super?
m0, m1 = caller_locations[0].base_label, caller_locations[1]&.base_label
m0 == m1 and
(method(m0).owner rescue nil) == (method(m1).owner rescue nil)
end
end
这是一个更简单(几乎微不足道)的方法,但您必须同时传递当前 class 和方法名称:(我还将方法名称从 via_super?
更改为 called_via?
)
module CallDetector
def called_via?(klass, sym)
klass == method(sym).owner
end
end
用法示例:
class A
include CallDetector
def foo
called_via?(A, :foo) ? 'nothing special' : 'super!'
end
end
class B < A
def foo
super
end
end
class C < A
end
A.new.foo # => "nothing special"
B.new.foo # => "super!"
C.new.foo # => "nothing special"
, and 答案与递归支持的终极组合:
module SuperDetector
def self.included(clazz)
unless clazz.instance_methods.include?(:via_super?)
clazz.send(:define_method, :via_super?) do
first_caller_location = caller_locations.first
calling_method = first_caller_location.base_label
same_origin = ->(other_location) do
first_caller_location.lineno == other_location.lineno and
first_caller_location.absolute_path == other_location.absolute_path
end
location_changed = false
same_name_stack = caller_locations.take_while do |location|
should_take = location.base_label == calling_method and !location_changed
location_changed = !same_origin.call(location)
should_take
end
self.kind_of?(clazz) and !same_origin.call(same_name_stack.last)
end
end
end
end
唯一不起作用的情况 (AFAIK) 是如果你在基础中有间接递归 class,但除了解析代码之外我不知道如何处理它。
在运行时的方法中,有没有办法知道该方法是否已在子类中通过 super
调用?例如
module SuperDetector
def via_super?
# what goes here?
end
end
class Foo
include SuperDetector
def bar
via_super? ? 'super!' : 'nothing special'
end
end
class Fu < Foo
def bar
super
end
end
Foo.new.bar # => "nothing special"
Fu.new.bar # => "super!"
我怎么写via_super?
,或者,如果需要,via_super?(:bar)
?
可能有更好的方法,但总体思路是 Object#instance_of?
仅限于当前 class,而不是层次结构:
module SuperDetector
def self.included(clazz)
clazz.send(:define_method, :via_super?) do
!self.instance_of?(clazz)
end
end
end
class Foo
include SuperDetector
def bar
via_super? ? 'super!' : 'nothing special'
end
end
class Fu < Foo
def bar
super
end
end
Foo.new.bar # => "nothing special"
Fu.new.bar # => "super!"
但是,请注意,这不需要在 child 中显式
super
。如果 child 没有这样的方法而使用 parent 的方法,via_super?
仍然会 return true
。我认为除了检查堆栈跟踪或代码本身之外,没有办法只捕获 super
情况。
出色的@ndn 方法的附录:
module SuperDetector
def self.included(clazz)
clazz.send(:define_method, :via_super?) do
self.ancestors[1..-1].include?(clazz) &&
caller.take(2).map { |m| m[/(?<=`).*?(?=')/] }.reduce(&:==)
# or, as by @ndn: caller_locations.take(2).map(&:label).reduce(&:==)
end unless clazz.instance_methods.include? :via_super?
end
end
class Foo
include SuperDetector
def bar
via_super? ? 'super!' : 'nothing special'
end
end
class Fu < Foo
def bar
super
end
end
puts Foo.new.bar # => "nothing special"
puts Fu.new.bar # => "super!"
这里我们使用Kernel#caller
来确保调用的方法的名称与super中的名称匹配class。如果不是直接后代,这种方法可能需要一些额外的调整(caller(2)
应该更改为更复杂的分析,)但你可能明白了。
UPD 感谢@Stefan 对其他答案的评论,更新为 unless defined
以使其在 Foo
和 Fu
include SuperDetector
.
UPD2 使用祖先来检查超级而不是直接比较。
编辑 根据 Stefan 的建议进行了改进。
module SuperDetector
def via_super?
m0, m1 = caller_locations[0].base_label, caller_locations[1]&.base_label
m0 == m1 and
(method(m0).owner rescue nil) == (method(m1).owner rescue nil)
end
end
这是一个更简单(几乎微不足道)的方法,但您必须同时传递当前 class 和方法名称:(我还将方法名称从 via_super?
更改为 called_via?
)
module CallDetector
def called_via?(klass, sym)
klass == method(sym).owner
end
end
用法示例:
class A
include CallDetector
def foo
called_via?(A, :foo) ? 'nothing special' : 'super!'
end
end
class B < A
def foo
super
end
end
class C < A
end
A.new.foo # => "nothing special"
B.new.foo # => "super!"
C.new.foo # => "nothing special"
module SuperDetector
def self.included(clazz)
unless clazz.instance_methods.include?(:via_super?)
clazz.send(:define_method, :via_super?) do
first_caller_location = caller_locations.first
calling_method = first_caller_location.base_label
same_origin = ->(other_location) do
first_caller_location.lineno == other_location.lineno and
first_caller_location.absolute_path == other_location.absolute_path
end
location_changed = false
same_name_stack = caller_locations.take_while do |location|
should_take = location.base_label == calling_method and !location_changed
location_changed = !same_origin.call(location)
should_take
end
self.kind_of?(clazz) and !same_origin.call(same_name_stack.last)
end
end
end
end
唯一不起作用的情况 (AFAIK) 是如果你在基础中有间接递归 class,但除了解析代码之外我不知道如何处理它。