这个问题是关于Ruby 2.2。
让我们说我有一个采用位置和命名参数的方法。
class Parent
def foo(positional, named1: "parent", named2: "parent")
puts positional.inspect
puts named1.inspect
puts named2.inspect
end
end
子类想要覆盖一些默认值,并添加自己的命名参数。我最好怎么做?理想情况下,如果父级想要添加一些可选的位置参数,则不必知道父级签名的详细信息。我的第一次尝试就是这个。
class Child < Parent
def foo(*args, named1: "child", named3: "child" )
super
end
end
但是这会因为未知的named3:
被传递给父母而爆炸。
Child.new.foo({ this: 23 })
/Users/schwern/tmp/test.rb:10:in `foo': unknown keyword: this (ArgumentError)
from /Users/schwern/tmp/test.rb:15:in `<main>'
我试着明确地将参数传递给super,但那也没有用。似乎第一个位置参数被视为命名参数。
class Child < Parent
def foo(*args, named1: "child", named3: "child" )
super(*args, named1: "child")
end
end
Child.new.foo({ this: 23 })
/Users/schwern/tmp/test.rb:10:in `foo': unknown keyword: this (ArgumentError)
from /Users/schwern/tmp/test.rb:15:in `<main>'
我可以让孩子知道第一个位置参数,它有效......
class Child < Parent
def foo(arg, named1: "child", named3: "child" )
super(arg, named1: "child")
end
end
Child.new.foo({ this: 23 })
Parent.new.foo({ this: 23 })
{:this=>23}
"child"
"parent"
{:this=>23}
"parent"
"parent"
...直到我传入一个命名参数。
Child.new.foo({ this: 23 }, named2: "caller")
Parent.new.foo({ this: 23 }, named2: "caller")
/Users/schwern/tmp/test.rb:10:in `foo': unknown keyword: named2 (ArgumentError)
from /Users/schwern/tmp/test.rb:15:in `<main>'
如何使这项工作并保留命名参数检查的好处?我打开将位置参数转换为命名参数。
答案 0 :(得分:4)
这里的问题是,由于父母对子的参数一无所知,因此无法知道传递给它的第一个参数是否是一个位置参数,或者是否打算为父方法提供关键字参数。这是因为Ruby允许哈希作为关键字参数样式参数传递的历史特性。例如:
def some_method(options={})
puts options.inspect
end
some_method(arg1: "Some argument", arg2: "Some other argument")
打印:
{:arg1=>"Some argument", :arg2=>"Some other argument"}
如果 Ruby不允许使用该语法(这会破坏与现有程序的向后兼容性),您可以使用double splat operator编写这样的子方法:
class Child < Parent
def foo(*args, named1: "child", named2: "child", **keyword_args)
puts "Passing to parent: #{[*args, named1: named1, **keyword_args].inspect}"
super(*args, named1: named1, **keyword_args)
end
end
实际上,除了位置参数之外还传递关键字参数时,这种方法很有效:
Child.new.foo({ this: 23 }, named2: "caller")
打印:
Passing to parent: [{:this=>23}, {:named1=>"child"}]
{:this=>23}
"child"
"parent"
但是,由于当您只传递一个哈希值时,Ruby无法区分位置参数和关键字参数,Child.new.foo({ this: 23 })
会导致this: 23
被孩子解释为关键字参数,并且父方法最终将作为单个位置参数(哈希)转发给它的两个关键字参数解释为:
Child.new.foo({this: 23})
打印:
Passing to parent: [{:named1=>"child", :this=>23}]
{:named1=>"child", :this=>23}
"parent"
"parent"
有几种方法可以解决这个问题,但没有一种方法可以解决这个问题。
正如您在第三个示例中尝试的那样,您可以告诉孩子传递的第一个参数将始终是位置参数,其余的将是关键字args:
class Child < Parent
def foo(arg, named1: "child", named2: "child", **keyword_args)
puts "Passing to parent: #{[arg, named1: named1, **keyword_args].inspect}"
super(arg, named1: named1, **keyword_args)
end
end
Child.new.foo({this: 23})
Child.new.foo({this: 23}, named1: "custom")
打印:
Passing to parent: [{:this=>23}, {:named1=>"child"}]
{:this=>23}
"child"
"parent"
Passing to parent: [{:this=>23}, {:named1=>"custom"}]
{:this=>23}
"custom"
"parent"
完全切换到使用命名参数。这完全避免了这个问题:
class Parent
def foo(positional:, named1: "parent", named2: "parent")
puts positional.inspect
puts named1.inspect
puts named2.inspect
end
end
class Child < Parent
def foo(named1: "child", named3: "child", **args)
super(**args, named1: named1)
end
end
Child.new.foo(positional: {this: 23})
Child.new.foo(positional: {this: 23}, named2: "custom")
打印:
{:this=>23}
"child"
"parent"
{:this=>23}
"child"
"custom"
编写一些代码以编程方式计算所有内容。
此解决方案可能非常复杂,并且很大程度上取决于您希望它的工作方式,但您的想法是使用Module#instance_method
和UnboundMethod#parameters
来读取签名。 parent的foo方法并相应地传递参数。除非您真的需要这样做,否则我建议您改用其他解决方案。
答案 1 :(得分:1)
据我所知,你想:
我认为您的问题可以通过捕获关键字参数来解决,这些参数将直接传递给子方法中kwargs
的单独变量中的父方法,如下所示:
class Parent
def foo(positional, parent_kw1: "parent", parent_kw2: "parent")
puts "Positional: " + positional.inspect
puts "parent_kw1: " + parent_kw1.inspect
puts "parent_kw2: " + parent_kw2.inspect
end
end
class Child < Parent
def foo(*args, parent_kw1: "child", child_kw1: "child", **kwargs)
# Here you can use `child_kw1`.
# It will not get passed to the parent method.
puts "child_kw1: " + child_kw1.inspect
# You can also use `parent_kw1`, which will get passed
# to the parent method along with any keyword arguments in
# `kwargs` and any positional arguments in `args`.
super(*args, parent_kw1: parent_kw1, **kwargs)
end
end
Child.new.foo({this: 23}, parent_kw2: 'ABCDEF', child_kw1: 'GHIJKL')
打印:
child_kw1: "GHIJKL"
Positional: {:this=>23}
parent_kw1: "child"
parent_kw2: "ABCDEF"