2008-01-25
ruby way之动态特性之二
1 得到所定义的实体的列表
ruby的反射api能够使我们在运行时检测类和对象。因此我们下面将会介绍Module, Class, 和Object中的定义的一些方法。
Module模块有一个constants 的方法,它将会返回系统中所有的常量名,包括类名和模块名。nesting 方法则是返回当前调用点上的嵌套的模块的列表.
Module#ancestors 返回指定的类或者模块的所有包含的类或者模块.
class_variables 方法返回给定的类和他的超类的所有类变量的一个表。included_modules 方法列出包含在这个类中的所有模块。
Class的方法instance_methods和public_instance_methods 是同义的。他们返回这个类的所有公有的方法。private_instance_methods和 protected_instance_methods 也就是返回私有和保护的实例方法。这几个方法都还带有一个参数,默认是true的。如果被设置为false,超类将不会被搜索。
Object 类有很多操作实例的类似的方法。methods 方法将会返回这个对象的所有可以被调用的方法。调用 public_methods 方法,将会返回所有公有的方法。他也有一个参数来判断是否去父类搜索。private_methods, protected_methods,和singleton_methods 也都有类似的参数。
2 测试调用栈
有时我们想要知道我们的调用者是谁,这个有时是非常有用的。看下面的例子:
3 监控程序的执行
一个ruby程序能够内省,或者说是检测他自己的执行。很多程序都用到了这个功能,有兴趣的话可以看看这几个ruby的源码:debug.rb, profile.rb, 和 tracer.rb。
我们能够使用 set_trace_func方法,他接受一个block作为参数。无论在程序的执行中发生任何事情时,这个block都会被调用,我们看下面的例子:
可以看到输出类似这样的:
还有一个方法是Kernel#trace_var,它是当全局变量被赋值时,才会被自动调用。
假设你想要在程序的外面得到程序的运行轨迹。最简单的方法就是使用tracer 库,假设有个prog.rb的文件:
然后我们在命令行load TRacer :
当源代码执行时每一个事件类型都包含"'-'",'>'表示一个调用,'<' 表示一个返回,'C'代表一个类,'E' 代表结束。
4 Traversing the Object Space
ruby的运行系统需要保存所有已知对象的踪迹(只是为了能够垃圾回收那些没有长时间使用的引用,也就是gc)。这个信息是通过 ObjectSpace.each_object 来得到的。
如果你指定一个类名或者模块名给each_object,它就只会返回这种类型的对象.
5 使用method_missing
起始和前面的const_missing差不多,也就是说当你在这个对象上调用一个不存在的方法时,它就会默认调用method_missing 方法:
在Object中定义的method_missing方法是默认抛出一个异常的.
6 跟踪类或者对象的改变
我们现在想要写一个模块,它能够被任何类所包含,然后在这个类中的每调用一个方法,都会打印出相应的信息,比如我们所期待的是这样的:
对于子类我们也是能够跟踪的:
下面来看它的实现:
这个代码其实很简单.首先它有两个主方法,第一个是included,它是一个回调方法,当这个模块被插入到一个类中时,就会调用这个方法。在我们上面的例子中,他做了两件事,一件是为include这个模块的类的每一个方法调用hook_method方法,第二件事是,为这个类重新定义了method_added 方法。这就意味着,这个类如果加方法的话,就会调用这个方法,也就是说会被检测到。
而hook_method的实现也是很漂亮,使用了define_method来动态的定义方法,打印出信息后,再使用send来调用老的方法。
这里还要注意一个alias_method ,它和alias很类似,只不过它只能用在方法。并且他自己就是一个方法:
检测一个新的类方法被加到一个类或者模块,我们能够定义一个类方法singleton_method_added :
输出将会是这样子:
inherited 方法使用也很类似,当一个类被子类化,就会调用这个方法:
我们也能够跟踪加一个模块的实例方法到一个对象。extend_object 方法就是做这个的:
这里要注意super是必须的,和append_features 重的原因一样。
7 为对象定义一个Finalizers
ruby类有构造器,可是没有析构器。原因很简单,就是因为ruby使用了mark-and-sweep garbage collection 来删除没有引用的对象。
虽然在ruby中不能真正的做到调用析构器来删除对象。这里有个方法define_finalizer,它是当一个对象被gc时,就会调用它:
输出的结果类似这样的:
当finalizer 被调用的同时对象就被销毁了。如果此时你调用ObjectSpace._id2ref,然后参数为刚才那个对象的id,则会报一个RangeError:
由于ruby使用的是mark-and-sweep GC 的策略,那么他就不能保证当程序结束之前,对象什么时候被GC。
可是所有的一切都是不确定的。在ruby中,经常使用block来压缩一个源(也就是一段代码)的使用,在block的结尾,这个源将会被删除(也就是说。然后其他的对象没有任何改变(其实这个也就是说讲代码封装在block里面比较好,因为当block退出时,它的那些局部变量,或者说,传进来的哪些参数都会被GC掉,因此在ruby1.9里面,block里面的局部变量和外面的变量已经不是同一个变量了(假设名字相同)。)。看下面的代码:
在这里当block退出时,file 被删除,这一切都是在open方法里控制。如果你想要实现一个open方法的子集,你可以这么做:
这边使用begin ensure是因为要做到就算异常被抛出,文件也要被关闭.
ruby的反射api能够使我们在运行时检测类和对象。因此我们下面将会介绍Module, Class, 和Object中的定义的一些方法。
Module模块有一个constants 的方法,它将会返回系统中所有的常量名,包括类名和模块名。nesting 方法则是返回当前调用点上的嵌套的模块的列表.
list = Math.constants # ["E", "PI"]
Module#ancestors 返回指定的类或者模块的所有包含的类或者模块.
list = Array.ancestors # [Array, Enumerable, Object, Kernel]
class_variables 方法返回给定的类和他的超类的所有类变量的一个表。included_modules 方法列出包含在这个类中的所有模块。
class Parent @@var1 = nil end class Child < Parent @@var2 = nil end list1 = Parent.class_variables # ["@@var1"] list2 = Array.included_modules # [Enumerable, Kernel]
Class的方法instance_methods和public_instance_methods 是同义的。他们返回这个类的所有公有的方法。private_instance_methods和 protected_instance_methods 也就是返回私有和保护的实例方法。这几个方法都还带有一个参数,默认是true的。如果被设置为false,超类将不会被搜索。
n1 = Array.instance_methods.size # 121 n2 = Array.public_instance_methods.size # 121 n3 = Array.private_instance_methods.size # 71 n4 = Array.protected_instance_methods.size # 0 n5 = Array.public_instance_methods(falsee).size # 71
Object 类有很多操作实例的类似的方法。methods 方法将会返回这个对象的所有可以被调用的方法。调用 public_methods 方法,将会返回所有公有的方法。他也有一个参数来判断是否去父类搜索。private_methods, protected_methods,和singleton_methods 也都有类似的参数。
class SomeClass
def initialize
@a = 1
@b = 2
end
def mymeth
#...
end
protected :mymeth
end
x = SomeClass.new
def x.newmeth
# ...
end
iv = x.instance_variables # ["@b", "@a"]
p x.methods.size # 42
p x.public_methods.size # 41
p x.public_methods(false).size # 1
p x.private_methods.size # 71
p x.private_methods(false).size # 1
p x.protected_methods.size # 1
p x.singleton_methods.size # 1
2 测试调用栈
有时我们想要知道我们的调用者是谁,这个有时是非常有用的。看下面的例子:
def func1 puts caller[0] end def func2 func1 end func2 # 打印出fucn1在那里被调用
3 监控程序的执行
一个ruby程序能够内省,或者说是检测他自己的执行。很多程序都用到了这个功能,有兴趣的话可以看看这几个ruby的源码:debug.rb, profile.rb, 和 tracer.rb。
我们能够使用 set_trace_func方法,他接受一个block作为参数。无论在程序的执行中发生任何事情时,这个block都会被调用,我们看下面的例子:
def meth(n)
sum = 0
for i in 1..n
sum += i
end
sum
end
set_trace_func(proc do |event, file, line,
id, binding, klass, *rest|
printf "%8s %s:%d %s/%s\n", event, file, line,
klass, id
end)
meth(2)
可以看到输出类似这样的:
引用
line D:/develop/rubyWorkspace/RubyWay/lib/DynamicFeaturesTest.rb:171 false/
call D:/develop/rubyWorkspace/RubyWay/lib/DynamicFeaturesTest.rb:157 Object/meth
............................
call D:/develop/rubyWorkspace/RubyWay/lib/DynamicFeaturesTest.rb:157 Object/meth
............................
还有一个方法是Kernel#trace_var,它是当全局变量被赋值时,才会被自动调用。
假设你想要在程序的外面得到程序的运行轨迹。最简单的方法就是使用tracer 库,假设有个prog.rb的文件:
def meth(n)
(1..n).each {|i| puts i}
end
meth(3)
然后我们在命令行load TRacer :
引用
% ruby -r tracer prog.rb
#0:prog.rb:1::-: def meth(n)
#0:prog.rb:1:Module:>: def meth(n)
。。。。。。。。。。。。。。。
#0:prog.rb:1::-: def meth(n)
#0:prog.rb:1:Module:>: def meth(n)
。。。。。。。。。。。。。。。
当源代码执行时每一个事件类型都包含"'-'",'>'表示一个调用,'<' 表示一个返回,'C'代表一个类,'E' 代表结束。
4 Traversing the Object Space
ruby的运行系统需要保存所有已知对象的踪迹(只是为了能够垃圾回收那些没有长时间使用的引用,也就是gc)。这个信息是通过 ObjectSpace.each_object 来得到的。
ObjectSpace.each_object do |obj| printf "%20s: %s\n", obj.class, obj.inspect end
如果你指定一个类名或者模块名给each_object,它就只会返回这种类型的对象.
5 使用method_missing
起始和前面的const_missing差不多,也就是说当你在这个对象上调用一个不存在的方法时,它就会默认调用method_missing 方法:
class CommandWrapper
def method_missing(method, *args)
system(method.to_s, *args)
end
end
cw = CommandWrapper.new
cw.date # Sat Apr 28 22:50:11 CDT 2001
cw.du '-s', '/tmp' # 166749 /tmp
在Object中定义的method_missing方法是默认抛出一个异常的.
6 跟踪类或者对象的改变
我们现在想要写一个模块,它能够被任何类所包含,然后在这个类中的每调用一个方法,都会打印出相应的信息,比如我们所期待的是这样的:
class MyClass include Tracing def one end def two(x, y) end end m = MyClass.new m.one # one called. Params = m.two(1, 'cat') # two called. Params = 1, cat
对于子类我们也是能够跟踪的:
class Fred < MyClass def meth(*a) end end Fred.new.meth(2,3,4,5) # meth called. Params = 2, 3, 4, 5
下面来看它的实现:
module Tracing
def Tracing.included(into)
into.instance_methods(false).each { |m|
Tracing.hook_method(into, m) }
def into.method_added(meth)
unless @adding
@adding = true
Tracing.hook_method(self, meth)
@adding = false
end
end
end
def Tracing.hook_method(klass, meth)
klass.class_eval do
alias_method "old_#{meth}", "#{meth}"
define_method(meth) do |*args|
puts "#{meth} called. Params = #{args.join(', ')}"
self.send("old_#{meth}",*args)
end
end
end
end
class MyClass
include Tracing
def first_meth
end
def second_meth(x, y)
end
end
m = MyClass.new
p m.first_meth # one called. Params =
p m.second_meth(1, 'cat') # two called. Params = 1, cat
这个代码其实很简单.首先它有两个主方法,第一个是included,它是一个回调方法,当这个模块被插入到一个类中时,就会调用这个方法。在我们上面的例子中,他做了两件事,一件是为include这个模块的类的每一个方法调用hook_method方法,第二件事是,为这个类重新定义了method_added 方法。这就意味着,这个类如果加方法的话,就会调用这个方法,也就是说会被检测到。
而hook_method的实现也是很漂亮,使用了define_method来动态的定义方法,打印出信息后,再使用send来调用老的方法。
这里还要注意一个alias_method ,它和alias很类似,只不过它只能用在方法。并且他自己就是一个方法:
# Two other ways to write that line...
# Symbols with interpolation:
alias_method :"old_#{meth}", :"#{meth}"
# Strings converted via to_sym:
alias_method "old_#{meth}".to_sym, meth.to_sym
检测一个新的类方法被加到一个类或者模块,我们能够定义一个类方法singleton_method_added :
class MyClass
def MyClass.singleton_method_added(sym)
puts "Added method #{sym.to_s} to class MyClass."
end
def MyClass.meth1
puts "I'm meth1."
end
end
def MyClass.meth2
puts "And I'm meth2."
end
输出将会是这样子:
引用
Added method singleton_method_added to class MyClass.
Added method meth1 to class MyClass.
Added method meth2 to class MyClass.
Added method meth1 to class MyClass.
Added method meth2 to class MyClass.
inherited 方法使用也很类似,当一个类被子类化,就会调用这个方法:
class MyClass
def MyClass.inherited(subclass)
puts "#{subclass} inherits from MyClass."
end
# ...
end
class OtherClass < MyClass
# ...
end
# Output: OtherClass inherits from MyClass.
我们也能够跟踪加一个模块的实例方法到一个对象。extend_object 方法就是做这个的:
module MyMod
def MyMod.extend_object(obj)
puts "Extending object id #{obj.object_id}, class #{obj.class}"
super
end
# ...
end
x = [1, 2, 3]
x.extend(MyMod)
# Output:
# Extending object id 36491192, type Array
这里要注意super是必须的,和append_features 重的原因一样。
7 为对象定义一个Finalizers
ruby类有构造器,可是没有析构器。原因很简单,就是因为ruby使用了mark-and-sweep garbage collection 来删除没有引用的对象。
虽然在ruby中不能真正的做到调用析构器来删除对象。这里有个方法define_finalizer,它是当一个对象被gc时,就会调用它:
a = "hello"
puts "The string 'hello' has an object id #{a.object_id}"
ObjectSpace.define_finalizer(a) { |id| puts "Destroying #{id}" }
puts "Nothing to tidy"
GC.start
a = nil
puts "The original string is now a candidate for collection"
GC.start
输出的结果类似这样的:
The string 'hello' has an object id 21089680 Nothing to tidy The original string is now a candidate for collection Destroying 21089680
当finalizer 被调用的同时对象就被销毁了。如果此时你调用ObjectSpace._id2ref,然后参数为刚才那个对象的id,则会报一个RangeError:
ObjectSpace._id2ref 21089630 #in `_id2ref': 0x141cd5e is recycled object (RangeError)
由于ruby使用的是mark-and-sweep GC 的策略,那么他就不能保证当程序结束之前,对象什么时候被GC。
可是所有的一切都是不确定的。在ruby中,经常使用block来压缩一个源(也就是一段代码)的使用,在block的结尾,这个源将会被删除(也就是说。然后其他的对象没有任何改变(其实这个也就是说讲代码封装在block里面比较好,因为当block退出时,它的那些局部变量,或者说,传进来的哪些参数都会被GC掉,因此在ruby1.9里面,block里面的局部变量和外面的变量已经不是同一个变量了(假设名字相同)。)。看下面的代码:
File.open("myfile.txt") do |file|
line1 = file.read
# ...
end
在这里当block退出时,file 被删除,这一切都是在open方法里控制。如果你想要实现一个open方法的子集,你可以这么做:
def File.open(name, mode = "r")
f = os_file_open(name, mode)
if block_given?
begin
yield f
ensure
f.close
end
return nil
else
return f
end
end
这边使用begin ensure是因为要做到就算异常被抛出,文件也要被关闭.
评论
Module#ancestors 返回指定的类或者模块的所有存取方法。
--------------------
这个说错了吧,是返回所有包含的模块
哈哈,恩,错了。。。。
--------------------
这个说错了吧,是返回所有包含的模块
哈哈,恩,错了。。。。
dennis_zane
2008-01-25
回复
Module#ancestors 返回指定的类或者模块的所有存取方法。
--------------------
这个说错了吧,是返回所有包含的模块
--------------------
这个说错了吧,是返回所有包含的模块







评论排行榜