了解使用method_added来动态覆盖实例方法的ruby元编程 [英] Understanding ruby metaprogramming using method_added to overwrite instance methods dynamically

查看:68
本文介绍了了解使用method_added来动态覆盖实例方法的ruby元编程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有来自Ruby 1.9编程的以下代码(略有改编),我只是想确保自己的思维过程正确

I have the following code from Programming Ruby 1.9 (slightly adapted) I just want to ensure my thought process is accurate

module Trace
  def self.included(culprit)
    #Inject existing methods with tracing code:
    culprit.instance_methods(false).each do |func|
      inject(culprit, func)
    end

    #Override the singletons method_added to ensure all future methods are injected.
    def culprit.method_added(meth)
      unless @trace_calls_internal
        @trace_calls_internal = true
        Trace.inject(self, meth) #This will call method_added itself, the condition prevents infinite recursion.
        @trace_calls_internal = false
      end
    end
  end

  def self.inject(target, meth)
    target.instance_eval do
      #Get the method
      method_object = instance_method(meth)
      #Rewrite dat sheet
      define_method(meth) do |*args, &block|
        puts "==> Called #{meth} with #{args.inspect}"
        #the bind to self will put the context back to the class.
        result = method_object.bind(self).call(*args, &block)
        puts "<== #{meth} returned #{result.inspect}"
        result
      end
    end
  end
end

class Example
  def one(arg)
    puts "One called with #{arg}"
  end
end
#No tracing for the above.
ex1 = Example.new
ex1.one("Sup") #Not affected by Trace::inject

class Example #extend the class to include tracing.
  include Trace #calls Trace::inject on existing methods via Trace::included
  def two(a1, a2) #triggers Example::method_added(two) and by extension Trace::inject
    a1 + a2
  end
end

ex1.one("Sup again") #Affected by Trace::inject
puts ex1.two(5, 4) #Affected by Trace::inject

我仍在努力解决这个问题.我希望有人能够确认我的思考过程,因为我想确保自己了解这里发生的事情.这些注释是我自己添加的.我真的认为我对方法绑定,单例类和元编程的了解充其量只是新手.

I'm still trying to wrap my head around how this works. I was hoping if someone could confirm my thought process as I want to make sure I understand what is going on here. The comments were added by myself.I really consider my understanding of method binding, singleton classes, and meta-programming to be novice at best.

首先,任何包含它的类都将调用Trace :: included.此方法有两件事,获取该类中现有功能的列表(如果有),并使用inject覆盖其方法.然后,它修改包含该模块的类的单例类,并覆盖默认的method_added方法,以确保每次添加方法超过附加的include注入都会影响它.此方法使用一个变量来防止无限递归,因为对inject的调用会根据其性质调用method_added.

Firstly, Trace::included is called by any class that includes it. This method does two things, gets a list of existing functions in that class (if any) and overrides their methods using inject. Then it modifies the singleton class of the class that included the module and overrides the default method_added method to ensure every time a method is added past the additional include inject will affect it. This method uses a variable to prevent infinite recursion because the call to inject will evoke method_added by its nature.

Trace ::如下工作:使用instance_eval将self设置为类定义中存在的上下文.因此,将 scope(?)修改为它在该类定义中的显示方式.

Trace::works as follows: set self to the context that exists within the class definition using instance_eval. Therefore the scope(?) is modified to how it would be within that class definition.

然后将method_object设置为instance_method(meth),这将获得要添加的原始方法.由于 instance_method没有显式的接收者,它将默认设置为self,这与位于类定义中的上下文相同吗?

Then we set method_object to instance_method(meth) which will get the original method that will added. Since instance_method does not have an explicit receiver, it will default to self which will be the same as the context of being within the class definition?

然后,我们使用define_method定义一个具有相同名称的方法.因为我们在instance_eval的上下文中,所以这等效于定义实例方法,并将覆盖现有方法.我们的方法接受任意数量的参数和一个参数(如果存在).

Then we use define_method to define a method with the same name. Because we are in the context of instance_eval, this is equivalent to defining an instance method and will override the existing method. our method accepts an arbitrary number of arguments and a block if one exists.

我们添加了一些光晕来放置跟踪",然后我们还调用了存储在method_object中的原始方法,因为原始方法已被覆盖.此方法是 unbound,因此我们必须使用bind(self)将其绑定到当前上下文,以便它具有与原始上下文相同的上下文?,然后我们使用call并传递参数和代码块,存储其返回值,并在打印后返回其返回值.

We add some flare to place our "Tracing" then we also call the original method that we stored in method_object, as the original is being overwritten. This method is unbound, so we must bind it to the current context using bind(self) so it has the same context as it would originally? we then use call and pass through the arguments and the block, storing its return value, and returning its return value after printing it.

我真的希望我能对此做充分的描述.我的描述准确吗?对于粗体内容和以下内容,我尤其不确定:

I really hope that I am describing this adequately. Is my description accurate? I am particularly uncertain about the bolded content and the following line:

method_object.bind(self).call(*args, &block)

推荐答案

Trace ::的工作方式如下:使用instance_eval将self设置为类定义中存在的上下文.因此范围(?)是 修改为该类定义中的内容.

Trace::works as follows: set self to the context that exists within the class definition using instance_eval. Therefore the scope(?) is modified to how it would be within that class definition.

使用实例eval可以对与对象进行自我绑定的块进行评估,在这种情况下,该块将是包含模块的类. (即元凶).只是为了清楚起见,它们之间存在区别:

Using instance eval you evaluate the block with self bound to the object, which in this case will be the class that is including the module. (I.e. the culprit). Just for clarity, there is a difference between:

o = Object.new
o.instance_eval do
  puts self
end

class Foo < Object end
Foo.instance_eval do  puts self end

答案:是的,您在这个假设中是正确的!

Answer: So yes you are correct in this assumption!

然后我们将method_object设置为instance_method(meth),这将获得要添加的原始方法.由于instance_method不 有一个明确的接收者,它将默认为self,这将是 是否与包含在类定义中的上下文相同?

Then we set method_object to instance_method(meth) which will get the original method that will added. Since instance_method does not have an explicit receiver, it will default to self which will be the same as the context of being within the class definition?

是的,您的假设是正确的.请注意,并询问:

Yes, you are correct in your assumption. Do note that, with asking:

culprit.instance_methods(false) => [:someselector, :someotherselector]

在此上下文中调用实例方法的确与调用self.instance_method相同.

And calling instance method in this context is indeed the same as calling self.instance_method.

此方法是未绑定的,因此我们必须将其绑定到当前上下文 使用bind(self)使其具有与最初相同的上下文?

This method is unbound, so we must bind it to the current context using bind(self) so it has the same context as it would originally?

是的.当您以跟踪模块中定义的方式获取方法时,您将获得一个未绑定的方法对象,该对象可以按所述再次绑定.

Yes. When you get a method in the way defined in the trace module, you get an unbound method object which can be bound again as described.

如果您想深入了解Ruby的元编程,我建议您阅读以下书籍: http://pragprog.com/book/ppmetr/metaprogramming-ruby 解释了Ruby对象系统,mixin,块以及您可以想象的任何东西背后的所有细节.

If you want to dive into Ruby's metaprogramming, I do recommend the following book: http://pragprog.com/book/ppmetr/metaprogramming-ruby it explains all the gritty details behind Ruby's object system, mixins, blocks and anything you can imagine.

这篇关于了解使用method_added来动态覆盖实例方法的ruby元编程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆