Ruby Structs中的命名参数
如果这是一个显而易见的问题,我对Ruby很新,所以道歉。
我想在实例化Struct时使用命名参数,即能够指定Struct中哪些项获取什么值,并将其余项默认为nil。
例如,我想做:
Movie = Struct.new :title, :length, :rating m = Movie.new :title => 'Some Movie', :rating => 'R'
这不起作用。
所以我想出了以下内容:
class MyStruct 'Some Movie', :rating => 'R'
这似乎工作得很好,但我不确定是否有更好的方法来做到这一点,或者如果我做的事情非常疯狂。 如果有人能够validation/撕开这种方法,我将非常感激。
UPDATE
我最初在1.9.2中运行它并且它工作正常; 但是在其他版本的Ruby中尝试过它(谢谢rvm),它的工作原理不起作用,如下所示:
- 1.8.7:不工作
- 1.9.1:工作
- 1.9.2:工作
- JRuby(设置为1.9.2运行):不工作
JRuby对我来说是一个问题,因为我想保持它与部署目的兼容。
再来一次更新
在这个不断增加的漫无边际的问题中,我尝试了各种版本的Ruby并发现1.9.x中的Structs将其成员存储为符号,但在1.8.7和JRuby中,它们存储为字符串,因此我将代码更新为如下(接受已经给出的建议):
class MyStruct 'Some Movie', :rating => 'R'
现在这似乎适用于我尝试过的所有Ruby风格。
合成现有答案揭示了Ruby 2.0+的一个更简单的选项:
class KeywordStruct < Struct def initialize(**kwargs) super(*members.map{|k| kwargs[k] }) end end
用法与现有的Struct
相同,其中未给出的任何参数将默认为nil
:
Pet = KeywordStruct.new(:animal, :name) Pet.new(animal: "Horse", name: "Bucephalus") # => # Pet.new(name: "Bob") # => #
如果你想要像Ruby 2.1 +所需的kwargs这样的参数,这是一个非常小的变化:
class RequiredKeywordStruct < Struct def initialize(**kwargs) super(*members.map{|k| kwargs.fetch(k) }) end end
此时,覆盖initialize
以给出某些kwargs默认值也是可行的:
Pet = RequiredKeywordStruct.new(:animal, :name) do def initialize(animal: "Cat", **args) super(**args.merge(animal: animal)) end end Pet.new(name: "Bob") # => #
你知道的越少越好。 无需知道底层数据结构是使用符号还是字符串,甚至是否可以将其作为Hash
进行寻址。 只需使用属性设置器:
class KwStruct < Struct.new(:qwer, :asdf, :zxcv) def initialize *args opts = args.last.is_a?(Hash) ? args.pop : Hash.new super *args opts.each_pair do |k, v| self.send "#{k}=", v end end end
它需要位置和关键字参数:
> KwStruct.new "q", :zxcv => "z" => #
一个只允许Ruby关键字参数的解决方案(Ruby> = 2.0)。
class KeywordStruct < Struct def initialize(**kwargs) super(kwargs.keys) kwargs.each { |k, v| self[k] = v } end end
用法:
class Foo < KeywordStruct.new(:bar, :baz, :qux) end foo = Foo.new(bar: 123, baz: true) foo.bar # --> 123 foo.baz # --> true foo.qux # --> nil foo.fake # --> NoMethodError
这种结构可以作为一个值对象非常有用,特别是如果你喜欢更严格的方法访问器,它实际上会出错而不是返回nil
(一个OpenStruct)。
你考虑过OpenStruct吗?
require 'ostruct' person = OpenStruct.new(:name => "John", :age => 20) p person # # p person.name # "John" p person.adress # nil
你可以重新排列if
s。
class MyStruct < Struct # Override the initialize to handle hashes of named parameters def initialize *args # I think this is called a guard clause # I suspect the *args is redundant but I'm not certain return super *args unless (args.length == 1 and args.first.instance_of? Hash) args.first.each_pair do |k, v| # I can't remember what having the conditional on the same line is called self[k] = v if members.include? k end end end
基于@Andrew Grimm的回答,但使用Ruby 2.0的关键字参数:
class Struct # allow keyword arguments for Structs def initialize(*args, **kwargs) param_hash = kwargs.any? ? kwargs : Hash[ members.zip(args) ] param_hash.each { |k,v| self[k] = v } end end
请注意,这不允许混合常规和关键字参数 – 您只能使用其中一个。
如果你确实需要混合常规和关键字参数,你总是可以手工构建初始化程序…
Movie = Struct.new(:title, :length, :rating) do def initialize(title, length: 0, rating: 'PG13') self.title = title self.length = length self.rating = rating end end m = Movie.new('Star Wars', length: 'too long') => #
这个标题是强制性的第一个参数,仅用于说明。 它还具有以下优点:您可以为每个关键字参数设置默认值(尽管在处理电影时这不太有用!)。
如果您的哈希键是有序的,您可以调用splat运算符来进行救援:
NavLink = Struct.new(:name, :url, :title) link = { name: 'Stack Overflow', url: 'https://stackoverflow.com', title: 'Sure whatever' } actual_link = NavLink.new(*link.values) #
对于具有Struct行为的1对1等价物(当没有给出所需的参数时引发)我有时会使用它(Ruby 2+):
def Struct.keyed(*attribute_names) Struct.new(*attribute_names) do def initialize(**kwargs) attr_values = attribute_names.map{|a| kwargs.fetch(a) } super(*attr_values) end end end
并从那里
class SimpleExecutor < Struct.keyed :foo, :bar ... end
如果你错过了一个参数,这将引发KeyError
,因此对于具有大量参数,数据传输对象等的更严格的构造函数和构造函数来说真的很好。
这并没有完全回答这个问题,但我发现如果你想说出你希望结构化的价值观,那就可以了。 它具有卸载记住属性顺序的需要,同时也不需要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(如果需要必需的关键字args,则为2.1)。 仅在MRI中测试过。
def Struct.new_with_kwargs(lamb) members = lamb.parameters.map(&:last) Struct.new(*members) do define_method(:initialize) do |*args| super(* lamb.(*args)) end end end Foo = Struct.new_with_kwargs( ->(a, b=1, *splat, c:, d: 2, **kwargs) do # must return an array with values in the same order as lambda args [a, b, splat, c, d, kwargs] end )
用法:
> Foo.new(-1, 3, 4, c: 5, other: 'foo') => #"foo"}>
一个小缺点是你必须确保lambda以正确的顺序返回值; 最大的好处是你拥有ruby 2的关键词args的全部function。