2011-03-23 31 views
27

对于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' 

这似乎工作得很好,但我不知道是否有这样做的更好的办法,或者如果我做得相当疯狂。如果任何人都可以验证/拆分这种方法,我将非常感激。

UPDATE

我在1.9.2最初跑了这一点,并能正常工作;在Ruby中的其他版本但已经尝试过(谢谢你,RVM),它的工作/不工作如下:

  • 1.8.7:不工作
  • 1.9.1:工作
  • 1.9。 2:工作
  • 的JRuby(设置为1.9.2运行):不工作

JRuby是我的一个问题,因为我想保持其与部署的目的兼容。

另一个UPDATE

在这个日益散漫的问题,我尝试用Ruby的各种版本和发现的Structs在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的所有口味的工作。

+0

你的代码看起来很好,真的。 – 2011-03-23 21:19:54

+0

我一直在寻找同样的东西 - 是否有一个标准的gem来做到这一点,并指定哪些参数是必需的? – 2013-02-22 17:49:52

+0

https://github.com/chemica/solid-struct这样做,但不强制执行所需的参数。 – 2015-06-20 12:47:27

回答

5

你有没有考虑过OpenStruct?

require 'ostruct' 

person = OpenStruct.new(:name => "John", :age => 20) 
p person    # #<OpenStruct name="John", age=20> 
p person.name   # "John" 
p person.adress  # nil 
+5

注意:'OpenStruct'实例将允许实际分配任何属性:'person.feather_color =:blue'将适用于您的示例。 'Struct'不允许这样做。 – 2011-03-23 21:15:46

+0

感谢您的建议;我看了一下OpenStruct,似乎介于Struct和哈希之间。在过去我使用散列,但我想尝试Structs的主要原因是他们严格定义它们的成员,就像基于散列和OpenStruct的数据一样,它们是任意的,如果数据结构很难维护没有很好的记录。使用结构,很容易知道应该在哪里(尽管可以和不可以为零是完全的另一个问题)。 – 2011-03-24 09:01:49

+0

我建议只使用OpenStruct进行配置或类似的操作,但不能用于常规开发。对于任何缺少的消息获得'nil'会伤害你 – ecoologic 2014-09-08 14:33:20

4

您可以重新排列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 
+0

你的代码看起来更像Ruby和干净。我一定会采用那种风格;感谢那! – 2011-03-24 09:04:42

2

基于@Andrew格林回答,但使用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 

注意,这不允许的定期和关键字混合arguments--你只能使用一个或另一个。

+0

对super(* args)的调用允许混合位置参数和关键字参数,并为其余行提供相当大的简化。 – 2014-12-24 15:16:22

12

知道的越少越好。无需知道底层数据结构是使用符号还是字符串,或者是否可以将其作为Hash进行处理。只需使用属性setter方法:

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" 
=> #<struct KwStruct qwer="q", asdf=nil, zxcv="z"> 
+1

Ruby魔法。谢谢,我喜欢这个。 – Subimage 2016-08-01 00:40:38

+0

非常聪明的答案!测试并在Ruby 1.9和2.x中工作 – ahirschberg 2017-06-14 15:23:12

1

如果你需要混合经常和关键字参数,你可以随时通过手工构建初始...

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') 
=> #<struct Movie title="Star Wars", length="too long", rating="PG13"> 

这有一个标题作为强制性的第一个参数只是为了说明。它还具有可以为每个关键字参数设置默认值的优点(尽管如果处理电影,这不太可能有用!)。

9

解决方案只有允许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)更严格的方法存取值对象非常有用。

+0

当给出零参数时,此解决方案失败:'foo = Foo.new()'让你''。改为使用@ indirect的[answer](https://stackoverflow.com/a/38811145/165673)。 – Yarin 2018-01-04 19:11:09

1

对于1对1当量与STRUCT行为(当未给出必需的参数提高)我使用此有时(红宝石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,对于更严格的构造函数和有很多参数,数据传输对象等的构造函数来说,这真的很好。

+1

如果你想要所有参数,这很好。避免不必要的继承的两个改进:1)不需要'Class.new';只需使用'Struct.new(...)do ... end'; 2)只是分配,而不是继承:'SimpleExecutor = Struct.keyed ...' – Kelvin 2016-02-04 22:48:04

+0

啊,所以Struct.new允许方法定义,并让你进入构造函数体!非常酷@凯尔文不知道,谢谢 – Julik 2016-07-23 21:07:10

0

这并不完全回答这个问题,但我发现它运作良好,如果你有一个你希望结构化的值的散列。它具有卸载需要记住属性顺序的好处,同时也不需要子类Struct。

MyStruct = Struct.new(:height, :width, :length)

hash = {height: 10, width: 111, length: 20}

MyStruct.new(*MyStruct.members.map {|key| hash[key] })

0

红宝石2.X只(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') 
=> #<struct Foo a=-1, b=3, splat=[4], c=5, d=2, kwargs={:other=>"foo"}> 

的小缺点是,你必须确保拉姆达按照正确的顺序返回值;最大的好处是你拥有ruby 2关键字参数的全部能力。

1

如果你的散列键,以便您可以致电图示操作救援:

NavLink = Struct.new(:name, :url, :title) 
link = { 
    name: 'Stack Overflow', 
    url: 'https://stackoverflow.com', 
    title: 'Sure whatever' 
} 
actual_link = NavLink.new(*link.values) 
#<struct NavLink name="Stack Overflow", url="https://stackoverflow.com", title="Sure whatever"> 
12

合成现有的答案揭示了红宝石2更简单的选择。0+:

class KeywordStruct < Struct 
    def initialize(**kwargs) 
    super(*members.map{|k| kwargs[k] }) 
    end 
end 

用法是相同的现有Struct,在没有给出任何参数将默认为nil:如果你想要求,比如Ruby 2.1 +的要求kwargs参数

Pet = KeywordStruct.new(:animal, :name) 
Pet.new(animal: "Horse", name: "Bucephalus") # => #<struct Pet animal="Horse", name="Bucephalus"> 
Pet.new(name: "Bob") # => #<struct Pet animal=nil, name="Bob"> 

,这是一个非常小的变化:

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") # => #<struct Pet animal="Cat", name="Bob"> 
+0

有趣。我在你的实现上运行了benchmark/ips,比简单实现Pet对象慢2倍,而不是结构。 Obj大约快了2倍。然而,你的关键字结构仍然比Openstruct快3倍,所以如果你不需要这种灵活性,这是一个可行的选择。 – konung 2017-10-08 03:43:04