Ruby 结构中的命名参数
我对 Ruby 还很陌生,所以如果这是一个明显的问题,我深表歉意。
我想在实例化 Struct 时使用命名参数,即能够指定 Struct 中的哪些项获取哪些值,并将其余项默认为 nil。
例如我想做:
Movie = Struct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'
这不起作用。
所以我想出了以下方法:
class MyStruct < Struct
# Override the initialize to handle hashes of named parameters
def initialize *args
if (args.length == 1 and args.first.instance_of? Hash) then
args.first.each_pair do |k, v|
if members.include? k then
self[k] = v
end
end
else
super *args
end
end
end
Movie = MyStruct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'
这似乎工作得很好,但我不确定是否有更好的方法来做到这一点,或者我是否正在做一些相当疯狂的事情。如果有人可以验证/分解这种方法,我将不胜感激。
更新
我最初在 1.9.2 中运行了这个,它运行良好;但是在其他版本的 Ruby 中尝试过(谢谢 rvm),它工作/不工作如下:
- 1.8.7:不工作
- 1.9.1:工作
- 1.9.2:工作
- JRuby(设置为运行为 1.9.2 ):不工作
JRuby 对我来说是一个问题,因为我希望保持它与部署目的兼容。
另一个更新
在这个不断增加的漫无目的的问题中,我尝试了 Ruby 的各个版本,发现 1.9.x 中的结构将其成员存储为符号,但在 1.8.7 和 JRuby 中,它们是存储为字符串,因此我将代码更新为以下内容(采纳已经友好给出的建议):
class MyStruct < Struct
# Override the initialize to handle hashes of named parameters
def initialize *args
return super unless (args.length == 1 and args.first.instance_of? Hash)
args.first.each_pair do |k, v|
self[k] = v if members.map {|x| x.intern}.include? k
end
end
end
Movie = MyStruct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'
现在,这似乎适用于我尝试过的所有 Ruby 风格。
I'm pretty new to Ruby so apologies if this is an obvious question.
I'd like to use named parameters when instantiating a Struct, i.e. be able to specify which items in the Struct get what values, and default the rest to nil.
For example I want to do:
Movie = Struct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'
This doesn't work.
So I came up with the following:
class MyStruct < Struct
# Override the initialize to handle hashes of named parameters
def initialize *args
if (args.length == 1 and args.first.instance_of? Hash) then
args.first.each_pair do |k, v|
if members.include? k then
self[k] = v
end
end
else
super *args
end
end
end
Movie = MyStruct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'
This seems to work just fine, but I'm not sure if there's a better way of doing this, or if I'm doing something pretty insane. If anyone can validate/rip apart this approach, I'd be most grateful.
UPDATE
I ran this initially in 1.9.2 and it works fine; however having tried it in other versions of Ruby (thank you rvm), it works/doesn't work as follows:
- 1.8.7: Not working
- 1.9.1: Working
- 1.9.2: Working
- JRuby (set to run as 1.9.2): not working
JRuby is a problem for me, as I'd like to keep it compatible with that for deployment purposes.
YET ANOTHER UPDATE
In this ever-increasing rambling question, I experimented with the various versions of Ruby and discovered that Structs in 1.9.x store their members as symbols, but in 1.8.7 and JRuby, they are stored as strings, so I updated the code to be the following (taking in the suggestions already kindly given):
class MyStruct < Struct
# Override the initialize to handle hashes of named parameters
def initialize *args
return super unless (args.length == 1 and args.first.instance_of? Hash)
args.first.each_pair do |k, v|
self[k] = v if members.map {|x| x.intern}.include? k
end
end
end
Movie = MyStruct.new :title, :length, :rating
m = Movie.new :title => 'Some Movie', :rating => 'R'
This now appears to work for all the flavours of Ruby that I've tried.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
在 Ruby 2.5+ 中,您可以使用
keyword_init: true
With Ruby 2.5+ you can use
keyword_init: true
综合现有的答案揭示了 Ruby 2.0+ 的一个更简单的选项:
用法与现有的
Struct
相同,其中任何未给出的参数将默认为nil
:如果您想需要像 Ruby 2.1+ 所需的 kwargs 那样的参数,这是一个非常小的变化:
此时,重写
initialize
来给出某些 kwargs 默认值也是可行的:Synthesizing the existing answers reveals a much simpler option for Ruby 2.0+:
Usage is identical to the existing
Struct
, where any argument not given will default tonil
:If you want to require the arguments like Ruby 2.1+'s required kwargs, it's a very small change:
At that point, overriding
initialize
to give certain kwargs default values is also doable:你知道的越少越好。无需知道底层数据结构是否使用符号或字符串,甚至不需要知道它是否可以作为
哈希
进行寻址。只需使用属性设置器:它同时接受位置参数和关键字参数:
The less you know, the better. No need to know whether the underlying data structure uses symbols or string, or even whether it can be addressed as a
Hash
. Just use the attribute setters:It takes both positional and keyword arguments:
仅允许 Ruby 关键字参数 (Ruby >=2.0) 的解决方案。
用法:
这种结构作为值对象非常有用,特别是如果您喜欢更严格的方法访问器,它实际上会出错而不是返回 nil (类似于 OpenStruct)。
A solution that only allows Ruby keyword arguments (Ruby >=2.0).
Usage:
This kind of structure can be really useful as a value object especially if you like more strict method accessors which will actually error instead of returning
nil
(a la OpenStruct).您考虑过 OpenStruct 吗?
Have you considered OpenStruct?
您可以重新排列
if
。You could rearrange the
if
s.基于 @Andrew Grimm 的答案,但使用 Ruby 2.0 的关键字参数:
请注意,这不允许混合常规参数和关键字参数——您只能使用其中之一。
Based on @Andrew Grimm's answer, but using Ruby 2.0's keyword arguments:
Note that this does not allow mixing of regular and keyword arguments-- you can only use one or the other.
如果你的哈希键是有序的,你可以调用 splat 运算符来救援:
If your hash keys are in order you can call the splat operator to the rescue:
如果您确实需要混合常规参数和关键字参数,您始终可以手动构造初始值设定项...
这将标题作为强制的第一个参数只是为了说明。它还具有的优点是您可以为每个关键字参数设置默认值(尽管这在处理电影时不太可能有帮助!)。
If you do need to mix regular and keyword arguments, you can always construct the initializer by hand...
This has the title as a mandatory first argument just for illustration. It also has the advantage that you can set defaults for each keyword argument (though that's unlikely to be helpful if dealing with Movies!).
对于与 Struct 行为的 1 对 1 等效(在未给出所需参数时引发),我有时会使用它(Ruby 2+):
并且从那里开始,
如果您错过了一个参数,对于更严格的构造函数和具有大量参数、数据传输对象等的构造函数来说非常好。
For a 1-to-1 equivalent with the Struct behavior (raise when the required argument is not given) I use this sometimes (Ruby 2+):
and from there on
This will raise a
KeyError
if you missed an argument, so real nice for stricter constructors and constructors with lots of arguments, data transfer objects and the like.这并不能完全回答问题,但我发现如果你说你想要构造的值的散列,它会很好地工作。它的优点是不需要记住属性的顺序,同时也不需要子类化 Struct。
MyStruct = Struct.new(:height, :width, :length)
hash = {高度:10,宽度:111,长度:20}
MyStruct.new (*MyStruct.members.map {|key| hash[key] })
this doesn't exactly answer the question but I found it to work well if you have say a hash of values you wish to structify. It has the benefit of offloading the need to remember the order of attributes while also not needing to subClass Struct.
MyStruct = Struct.new(:height, :width, :length)
hash = {height: 10, width: 111, length: 20}
MyStruct.new(*MyStruct.members.map {|key| hash[key] })
仅限 Ruby 2.x(如果您需要必需的关键字参数,则为 2.1)。仅在 MRI 中进行了测试。
用法:
次要的缺点是您必须确保 lambda 以正确的顺序返回值;最大的好处是您可以使用 ruby 2 的关键字参数的全部功能。
Ruby 2.x only (2.1 if you want required keyword args). Only tested in MRI.
Usage:
The minor downside is that you have to ensure the lambda returns the values in the correct order; the big upside is that you have the full power of ruby 2's keyword args.