Ruby元编程:如何区分类的自定义方法与attr系列属性方法
嘿,我明白你现在的困扰——用klass.public_instance_methods(false)拿到的方法里混着attr_reader这类宏生成的属性方法,想只保留自己写的自定义方法对吧?而且之前试klass.instance_variables返回空数组,确实会让人懵,先给你理清楚为啥那方法没用,再给你靠谱的解决方案。
首先说为啥klass.instance_variables没用:这个方法是用来获取类本身的实例变量的,和你要找的实例方法完全不搭边,所以返回空数组是正常的,你找错方向啦😅。
接下来看核心问题:attr_reader :a本质是Ruby帮你自动生成了一个和实例变量同名的公开实例方法,所以它会被public_instance_methods(false)算进去(因为是当前类定义的,不是继承来的)。要区分它和你手写的方法,最可靠的方式是看方法的定义来源——你自己写的方法会有明确的代码文件和行号,而attr系列生成的方法通常是Ruby内部实现的,没有对应的用户代码位置。
给你两种可行的方案:
方案一:通过source_location过滤(推荐)
每个方法对象都有source_location方法,它会返回一个数组[文件路径, 行号],自定义方法会指向你的代码文件,而自动生成的attr方法要么返回nil,要么指向Ruby的核心库文件。你可以基于这个来过滤:
require_relative 'T.rb' class Meta def initialize target_class = T custom_methods = target_class.public_instance_methods(false).select do |method_name| method = target_class.instance_method(method_name) # 判断方法是否定义在你的目标文件里(这里是T.rb) method.source_location && method.source_location.first.end_with?('T.rb') end p custom_methods # => [:meth] end end
这个方法的优势是精准,不会误删任何你手写的方法,哪怕方法名和实例变量同名也没问题。
方案二:通过实例变量名排除(有局限性)
如果你确定自己不会写和实例变量同名的自定义方法,也可以先拿到实例的所有变量名,再从方法列表里减去这些名字对应的方法:
require_relative 'T.rb' class Meta def initialize target_instance = T.new(1) # 把实例变量转成对应的方法名(去掉@符号) attr_method_names = target_instance.instance_variables.map { |var| var.to_s[1..-1].to_sym } custom_methods = T.public_instance_methods(false) - attr_method_names p custom_methods # => [:meth] end end
但要注意,如果你自己写了一个叫a的自定义方法,这个方案会把它误删掉,所以只适合场景简单的情况。
最后再总结下:public_instance_methods(false)包含attr生成的方法是因为这些方法确实是当前类定义的公开实例方法,要过滤它们就得从方法的定义来源入手,方案一是最稳妥的选择~
内容的提问来源于stack exchange,提问作者Lusheez

