元编程定义接受关键字参数的Ruby方法?
元编程定义接受关键字参数的Ruby方法?,ruby,metaprogramming,Ruby,Metaprogramming,Struct允许我创建一个新类,该类接受参数并具有良好的语义。但是,参数不是必需的,它们的顺序需要参考定义: Point = Struct.new(:x, :y) Point.new(111, 222) #=> <point instance with x = 111, y = 222> Point.new(111) #=> <point instance with x = 111, y = nil> 但是大括号中应该放些什么来定义klass上的init
Struct
允许我创建一个新类,该类接受参数并具有良好的语义。但是,参数不是必需的,它们的顺序需要参考定义:
Point = Struct.new(:x, :y)
Point.new(111, 222)
#=> <point instance with x = 111, y = 222>
Point.new(111)
#=> <point instance with x = 111, y = nil>
但是大括号中应该放些什么来定义klass
上的initialize
方法,以便:
- 它需要没有默认值的关键字参数李>
- 关键字在
;及属性中以符号数组的形式给出
方法将它们分配给同名的实例变量initialize
- 我可能误解了这个问题,但你是在寻找这样的问题吗
module StricterStruct
def self.new(*attributes)
klass = Class.new
klass.class_eval do
attributes.map!{|n| n.to_s.downcase.gsub(/[^\s\w\d]/,'').split.join("_")}
define_method("initialize") do |args|
raise ArgumentError unless args.keys.map(&:to_s).sort == attributes.sort
args.each { |var,val| instance_variable_set("@#{var}",val) }
end
attr_accessor *attributes
end
klass
end
end
然后
Point=stricerstruct.new(:x,:y)
#=>点
p=新点(x:12,y:77)
#=> #
p2=新点(x:17)
#=>参数错误
p2=新点(y:12)
#=>参数错误
p2=新点(y:17,x:22)
#=> #
如果你想要更多的东西,请解释,因为我认为这符合你的标准,至少我对它的理解是这样的。因为它定义了方法,可以采用“关键字”(Hash
)参数并分配适当的实例变量
如果希望参数的指定顺序与定义顺序相同,只需删除排序即可
此外,可能还有更干净的实现。由于Ruby 2.0+中的新功能,我最终使用了一种(令人惊讶的Pythonic)**kwargs
策略:
module StricterStruct
def self.new(*attribute_names_as_symbols)
c = Class.new
l = attribute_names_as_symbols
c.instance_eval {
define_method(:initialize) do |**kwargs|
unless kwargs.keys.sort == l.sort
extra = kwargs.keys - l
missing = l - kwargs.keys
raise ArgumentError.new <<-MESSAGE
keys do not match expected list:
-- missing keys: #{missing}
-- extra keys: #{extra}
MESSAGE
end
kwargs.map do |k, v|
instance_variable_set "@#{k}", v
end
end
l.each do |sym|
attr_reader sym
end
}
c
end
end
模块严格结构
def self.new(*属性名称作为符号)
c=新类
l=属性\u名称\u作为\u符号
c、 实例评估{
定义_方法(:初始化)do |**kwargs|
除非kwargs.keys.sort==l.sort
额外=kwargs.keys-l
缺失=l-kwargs.keys
raise ArgumentError.new听起来你在寻找Ruby的内置:
需要“ostruct”
foo=OpenStruct.new(条:1,'baz'=>2)
foo#=>#
foo.bar#=>1
foo[:bar]#=>1
foo.baz#=>2
foo.baz=3
foo#=>#
我认为OpenStruct是散列上的糖果涂层,我们可以在没有任何实际约束的情况下访问和分配实例,这与使用普通访问器创建真正的类不同。我们可以假装它是散列,或者是带有方法的类。它是甜点,不是地板蜡,不是,它是两件事合一!我也在四处寻找t他的,并最终偶然发现了这颗宝石,它正是这样做的:
这并不完全正确——正如我的示例所示,Point.new(x:12)
应该给您一个ArgumentError,因为您没有指定y
@JohnFeminella遗漏了该部分。现在它看起来更难看了:(但函数按要求运行。我不明白为什么要引入类变量,因为定义方法可以看到属性
。@JohnFeminella将其清理干净。我更喜欢您的错误消息,但这将接受字符串或符号,并将其转换为downcase snake\u case。在我看来,使用houtattributes.map!
。正如我在示例中提到的,所需的属性之一是,如果不指定所有内容,则会引发ArgumentError
或类似的异常。因为OpenStruct
可以随时修改,所以在这里这不是一个好的选择。此外,OpenStruct比Struct慢得多,因此如果你做了很多,这很昂贵。我一直认为OpenStruct是为那些不确定他们想要什么的人设计的。一旦创建了实例,你的案例需要更严格的检查,这肯定是OpenStruct失败的地方,我认为这是OpenStruct的一次否决票。也许你的代码将是Ruby的一个很好的补充,一个nd OpenStruct可能会因为速度太慢而被弃用。我喜欢这条消息,我刚刚更改了我的结构,使其具有非常相似的结构,尽管我为实例变量提供了getter和setter方法。很抱歉,我没有像你回答自己的问题那样快:)。另外请注意,我更新了答案,以接受字符串或符号,并将其格式化为snake_大小写。您的意思是def self.new(*attribute_names_as_symbols)
?此外,我相信您可以使用attr\u reader sym
创建每个读取访问器。有趣的问题。@CarySwoveland是的,这是我的复制粘贴中的一个拼写错误。谢谢您捕捉到这一点。您也说得对,attr\u reader更简单;我应该了解这一点!
module StricterStruct
def self.new(*attributes)
klass = Class.new
klass.class_eval do
attributes.map!{|n| n.to_s.downcase.gsub(/[^\s\w\d]/,'').split.join("_")}
define_method("initialize") do |args|
raise ArgumentError unless args.keys.map(&:to_s).sort == attributes.sort
args.each { |var,val| instance_variable_set("@#{var}",val) }
end
attr_accessor *attributes
end
klass
end
end
Point = StricterStruct.new(:x,:y)
#=> Point
p = Point.new(x: 12, y: 77)
#=> #<Point:0x2a89400 @x=12, @y=77>
p2 = Point.new(x: 17)
#=> ArgumentError
p2 = Point.new(y: 12)
#=> ArgumentError
p2 = Point.new(y:17, x: 22)
#=> #<Point:0x28cf308 @y=17, @x=22>
module StricterStruct
def self.new(*attribute_names_as_symbols)
c = Class.new
l = attribute_names_as_symbols
c.instance_eval {
define_method(:initialize) do |**kwargs|
unless kwargs.keys.sort == l.sort
extra = kwargs.keys - l
missing = l - kwargs.keys
raise ArgumentError.new <<-MESSAGE
keys do not match expected list:
-- missing keys: #{missing}
-- extra keys: #{extra}
MESSAGE
end
kwargs.map do |k, v|
instance_variable_set "@#{k}", v
end
end
l.each do |sym|
attr_reader sym
end
}
c
end
end
require 'ostruct'
foo = OpenStruct.new(bar: 1, 'baz' => 2)
foo # => #<OpenStruct bar=1, baz=2>
foo.bar # => 1
foo[:bar] # => 1
foo.baz # => 2
foo.baz = 3
foo # => #<OpenStruct bar=1, baz=3>
class FooBar
kwattr :foo, bar: 21
end
foobar = FooBar.new(foo: 42) # => #<FooBar @foo=42, @bar=21>
foobar.foo # => 42
foobar.bar # => 21
class FooBar
attr_reader :foo, :bar
def initialize(foo:, bar: 21)
@foo = foo
@bar = bar
end
end