【问题标题】:Dynamically creating class method动态创建类方法
【发布时间】:2012-12-13 18:22:20
【问题描述】:

我正在编写一个类方法来创建另一个类方法。 class_evalinstance_eval 如何在类方法的上下文中运行似乎有些奇怪。举例说明:

class Test1
  def self.add_foo
    self.class_eval do # does what it says on the tin
      define_method :foo do
        puts "bar"
      end
    end
  end
end

Test1.add_foo # creates new instance method, like I'd expect
Test1.new.foo # => "bar"


class Test2
  def self.add_foo
    self.instance_eval do # seems to do the same as "class_eval"
      define_method :foo do
        puts "bar"
      end
    end
  end
end

Test2.add_foo # what is happening here?!
Test2.foo # => NoMethodError
Test2.new.foo # => "bar"


class Test3
  def self.add_foo
    (class << self; self; end).instance_eval do # call explicitly on metaclass
      define_method :foo do
        puts "bar"
      end
    end
  end
end

Test3.add_foo # => creates new class method, as I'd expect
Test3.foo # => "bar"

我的理解是类方法是在相关类的元类上定义的实例方法(在这种情况下为Test2)。基于这个逻辑,我希望类方法调用add_foo 的接收者是元类。

  • selfTest2.add_foo 方法中指的是什么?
  • 为什么在这个接收器对象上调用instance_eval会创建一个实例方法?

【问题讨论】:

标签: ruby metaprogramming


【解决方案1】:

instance_evalclass_eval 之间的主要区别在于instance_eval 在实例的上下文中工作,而class_eval 在类的上下文中工作。我不确定您对 Rails 的熟悉程度,但让我们看一个示例:

class Test3 < ActiveRecord::Base

end

t = Test3.first
t.class_eval { belongs_to :test_25 } #=> Defines a relationship to test_25 for this instance
t.test_25 #=> Method is defined (but fails because of how belongs_to works)

t2 = Test3.find(2)
t2.test_25 #=> NoMethodError

t.class.class_eval { belongs_to :another_test }
t.another_test #=> returns an instance of another_test (assuming relationship exists)
t2.another_test #=> same as t.another_test

t.class_eval { id } #=> NameError
t.instance_eval { id } #=> returns the id of the instance
t.instance_eval { belongs_to :your_mom } #=> NoMethodError

这是因为belongs_to 实际上是发生在类体上下文中的方法调用,您不能从实例调用它。当您尝试使用class_eval 调用id 时,它会失败,因为id 是在实例上定义的方法,而不是在类中。

使用class_evalinstance_eval 定义方法在调用实例时基本相同。他们只会在调用它的对象的实例上定义一个方法。

t.class_eval do 
  def some_method
    puts "Hi!"
  end
end

t.instance_eval do
  def another_method
    puts "Hello!"
  end
end

t.some_method #=> "Hi!"
t.another_method #=> "Hello!"

t2.some_method #=> NoMethodError
t2.another_method #=> NoMethodError

但是,在处理类时,它们会有所不同。

t.class.class_eval do
  def meow
    puts "meow!"
  end
end

t.class.instance_eval do
  def bark
    puts "woof!"
  end
end

t.meow #=> meow!
t2.meow #=> meow!

t.bark #=> NoMethodError
t2.bark #=> NoMethodError

那么树皮去哪儿了?它是在类的单例类的实例上定义的。我将在下面进行更多解释。但现在:

t.class.bark #=> woof!
Test3.bark #=> woof!

因此,要回答您关于 self 在类体内指的是什么的问题,您可以构建一个小测试:

a = class Test4
  def bar
    puts "Now, I'm a #{self.inspect}"
  end

  def self.baz
    puts "I'm a #{self.inspect}"
  end

  class << self
    def foo
      puts "I'm a #{self.inspect}"
    end

    def self.huh?
      puts "Hmmm? indeed"
    end

    instance_eval do
      define_method :razors do
        puts "Sounds painful"
      end
    end

    "But check this out, I'm a #{self.inspect}"
  end
end

puts Test4.foo #=> "I'm a Test4"
puts Test4.baz #=> "I'm a Test4"
puts Test4.new.bar #=> Now I'm a #<Test4:0x007fa473358cd8>
puts a #=> But check this out, I'm a #<Class:Test4>

所以这里发生的情况是,在上面的第一个 puts 语句中,我们看到 inspect 告诉我们类方法体上下文中的 self 指的是类 Test4。在第二个puts 中,我们看到了相同的东西,只是定义不同(使用self.method_name 符号定义类方法)。在第三个中,我们看到self 引用了Test4 的一个实例。最后一个有点有趣,因为我们看到self 指的是Class 的一个实例,称为Test4。那是因为当你定义一个类时,你就是在创建一个对象。 Ruby 中的一切都是对象。此对象实例称为元类或特征类或单例类。

您可以使用class &lt;&lt; self 成语访问特征类。当您在那里时,您实际上可以访问特征类的内部。您可以在 eigenclass 内部定义实例方法,这与调用 self.method_name 一致。但是由于您在 eigenclass 的上下文中,您可以将方法附加到 eigenclass 的 eigenclass。

Test4.huh? #=> NoMethodError
Test4.singleton_class.huh? #=> Hmmm? indeed

当您在方法的上下文中调用instance_eval 时,您实际上是在类本身上调用instance_eval,这意味着您正在Test4 上创建实例方法。我在特征类中调用 instance_eval 的位置怎么样?它在 Test4 的 eigenclass 实例上创建一个方法:

Test4.razors #=> Sounds painful

希望这可以解决您的一些问题。我知道我在输入这个答案时学到了一些东西!

【讨论】:

  • 答案可以改进。我将扩展 instance_eval 和 class_eval 在类上执行时如何做同样的事情,以及在 eigenclass 上定义方法的不同之处。
  • 感谢您的回复!我同意@SergioTulentsev - 在这里得到更多澄清会很好。最重要的是,selfTest2 中指的是什么实例?
  • 您可能想解释上面第一个和第二个例子的方法解析有何不同(一个解析为类,另一个解析为在类上找不到该方法后的特征类)。
  • 感谢详细的文章,我确实学到了一些新东西!引起我最初困惑的是 Ruby 的 inspect 仅将 Class 对象显示为类名 - puts Test4.inspect 只是打印Test4,没有表明它是一个对象(Class 类的一个实例)。
  • 另外,我想我现在明白为什么 class_evalinstance_eval 在我的原始示例中最终会做同样的事情,最好的说明是:Test2.instance_eval{self}.equal? Test2.class_eval{self} 返回 true。调用instance_eval 将在Test2 类对象的上下文中执行,而class_eval 将在类的上下文中执行,当然这也是Test2 类对象。这听起来对吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-08
  • 2022-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-30
  • 2023-02-08
相关资源
最近更新 更多