为什么我的 define_method 内容在 class 范围内被评估?
Why is my define_method content being evaluated in the class scope?
我正在做一些(太)花哨的元编程,我很难理解为什么在以下两种情况下范围不同:
案例一:
class TesterA
def the_method
puts "I'm an instance_method!"
end
def self.the_method
puts "I'm a class_method!"
end
def self.define_my_methods *method_names
method_names.each do |name|
define_method("method_#{name}") do
the_method
end
end
end
define_my_methods :a, :b, :c
end
t = TesterA.new
t.method_a #=> I'm an instance_method!
t.method_b #=> I'm an instance_method!
t.method_c #=> I'm an instance_method!
案例二
class TesterB
def the_method
puts "I'm an instance_method!"
end
def self.the_method
puts "I'm a class_method!"
end
def self.define_the_method attr
define_method("method_#{attr}") do
begin
yield
rescue
raise $!, "method_#{attr} was called: #$!", $@
end
end
end
def self.define_my_methods *method_names
method_names.each do |name|
define_the_method(name) do
the_method
end
end
end
define_my_methods :a, :b, :c
end
t = TesterB.new
t.method_a #=> I'm a class_method!
t.method_b #=> I'm a class_method!
t.method_c #=> I'm a class_method!
在第二个示例中,我介绍了一种 "helper-mothod" define_the_method
,我用它来定义方法,而不是 define_method
它自己。这样做的原因是,我想将动态方法的名称附加到这些方法中可能出现的任何异常消息。然而,问题是,内容(当使用后一种情况时)似乎在 class 范围内进行评估。
为什么会这样,我怎样才能让它在实例范围内得到评估?
这个区块:
do
the_method
end
它在 class 范围内定义和限定范围。没有理由期望它会以某种方式注入实例范围。要解决此问题,只需显式传递接收器即可:
yield(self)
和:
def self.define_my_methods *method_names
method_names.each do |name|
define_the_method(name) do |receiver|
receiver.the_method
end
end
end
这是因为您在 class 方法 self.define_my_methods
的范围内提供块,其中 self
是 class,而不是实例。所以你可以做的是 yield
define_method
本身的范围:
def self.define_the_method attr
define_method("method_#{attr}") do
begin
yield self
rescue
raise $!, "method_#{attr} was called: #$!", $@
end
end
end
def self.define_my_methods *method_names
method_names.each do |name|
define_the_method(name) do |scope|
scope.send(:the_method)
end
end
end
那是因为用define_method
定义的proc
会被instance_eval
调用。
第一个样本是:
do
the_method
end
秒:
do
begin
yield
rescue
raise $!, "method_#{attr} was called: #$!", $@
end
end
但是 yield
将从定义它的地方开始拥有父作用域。
这是我的建议:
def self.define_the_method attr, &block
define_method("method_#{attr}") do
begin
instance_eval(&block)
rescue
raise $!, "method_#{attr} was called: #$!", $@
end
end
end
我正在做一些(太)花哨的元编程,我很难理解为什么在以下两种情况下范围不同:
案例一:
class TesterA
def the_method
puts "I'm an instance_method!"
end
def self.the_method
puts "I'm a class_method!"
end
def self.define_my_methods *method_names
method_names.each do |name|
define_method("method_#{name}") do
the_method
end
end
end
define_my_methods :a, :b, :c
end
t = TesterA.new
t.method_a #=> I'm an instance_method!
t.method_b #=> I'm an instance_method!
t.method_c #=> I'm an instance_method!
案例二
class TesterB
def the_method
puts "I'm an instance_method!"
end
def self.the_method
puts "I'm a class_method!"
end
def self.define_the_method attr
define_method("method_#{attr}") do
begin
yield
rescue
raise $!, "method_#{attr} was called: #$!", $@
end
end
end
def self.define_my_methods *method_names
method_names.each do |name|
define_the_method(name) do
the_method
end
end
end
define_my_methods :a, :b, :c
end
t = TesterB.new
t.method_a #=> I'm a class_method!
t.method_b #=> I'm a class_method!
t.method_c #=> I'm a class_method!
在第二个示例中,我介绍了一种 "helper-mothod" define_the_method
,我用它来定义方法,而不是 define_method
它自己。这样做的原因是,我想将动态方法的名称附加到这些方法中可能出现的任何异常消息。然而,问题是,内容(当使用后一种情况时)似乎在 class 范围内进行评估。
为什么会这样,我怎样才能让它在实例范围内得到评估?
这个区块:
do
the_method
end
它在 class 范围内定义和限定范围。没有理由期望它会以某种方式注入实例范围。要解决此问题,只需显式传递接收器即可:
yield(self)
和:
def self.define_my_methods *method_names
method_names.each do |name|
define_the_method(name) do |receiver|
receiver.the_method
end
end
end
这是因为您在 class 方法 self.define_my_methods
的范围内提供块,其中 self
是 class,而不是实例。所以你可以做的是 yield
define_method
本身的范围:
def self.define_the_method attr
define_method("method_#{attr}") do
begin
yield self
rescue
raise $!, "method_#{attr} was called: #$!", $@
end
end
end
def self.define_my_methods *method_names
method_names.each do |name|
define_the_method(name) do |scope|
scope.send(:the_method)
end
end
end
那是因为用define_method
定义的proc
会被instance_eval
调用。
第一个样本是:
do
the_method
end
秒:
do
begin
yield
rescue
raise $!, "method_#{attr} was called: #$!", $@
end
end
但是 yield
将从定义它的地方开始拥有父作用域。
这是我的建议:
def self.define_the_method attr, &block
define_method("method_#{attr}") do
begin
instance_eval(&block)
rescue
raise $!, "method_#{attr} was called: #$!", $@
end
end
end