新手问题 Ruby 有没有可能跳过一个带默认值的参数,为下一个带默认值的参数提供值?

ibachue · 2013年03月27日 · 最后由 bhuztez 回复于 2013年03月27日 · 7752 次阅读

Hi all,

请教诸神一个问题

例如:

def f(a = 1, b = 2)
  # ...
end

假设我从没见过这个方法的声明,但是我知道这个方法接受两个参数,并且这两个参数都有默认参数(虽然并不知道这两个默认参数分别是什么)。我现在在 call 这个方法的时候,只想为第二个参数指定值,第一个参数则继续使用默认值,我该怎么 call 这个方法才行?(我只知道 SQL 和 Python 是有解决方案的,不知道 Ruby 里怎么搞)

谢谢诸神

都成神了啊,危险。

ruby 2.0

def f(a: 1, b: 2)
  [a, b]
end

f(b: 4) #=> [1, 4]

2 有一个关键字参数,是不是就是你要的。

#3 楼 @saiga #4 楼 @chenge 这个并不是我想要的,传 Hash 的方法在老版本就有了,这里的关键在于,这个方法的定义已经存在不可修改了,而且定义方式也没有采用 Hash 的方法,这种情况下该如何解决呢?

@iBachue 这个有点反人类了,其实你可以通过hook这个方法,把两个参数对掉一下

Ruby 2.0 的话,可以用 #3 楼 @saiga 2.0 一下就

def f(opts = {})
  a = opts[:a] || 1
  b = opts[:b] || 2
end

f(a: 2)
f(b: 1)

#7 楼 @huacnlee 注意我在 5 楼的回复。。。

#6 楼 @tumayun 这个怎么做?

#8 楼 @iBachue 5 楼那个没看明白

alias_method_chain 然后在里面把参数对掉呗

#10 楼 @huacnlee 就是人家这方法就是这么定义的,参数通过传统的办法传入而不是 Hash,无法采用 Hash 带来的优势。

#12 楼 @iBachue 哦,你是说这样这种方法吧?

function f(a, b) {
}

function f(b) {
}
def f(args*)
  if args.count == 2
     f1(args*)
  else
    f0(args*)
  end
end

def f0(a)
end

def f1(a, b)
end

其实这种问题还有另外一个应用场景就是

class A
  def f(a, b)
    puts "a = #{a}, b = #{b}"
  end
end

class B < A
  def f(a = 2, b = 3)
    super
  end
end

class C < A
  def f(a = 5, b = 7)
    super
  end
end

class D < B
  def f(a = 8, b = 10)
    super
  end
end

多个子类都覆盖了父类的一个方法,并且不同实现都有自己的默认值 我现在有一个 A 或 A 子类的对象,要调用这个方法 f,并且指定第二个参数,而第一个参数设为默认值的时候,我即使知道 f 的实现,也完全没有办法指定第一个参数的值啊。

#15 楼 @huacnlee 汗。。这还是把人家的实现修改了啊

#17 楼 @iBachue 哈哈,我到也没试过直接写重载的方法,我认为用 Hash 的方式完全可以代替“方法重载”,并且用起来,方法的意思更加明确

#11 楼 @tumayun 这样还是无法避免啊

class B < A
  alias_method_chain :f, :change

  def f(b, a)
    f_without_change(a, b)
  end
end

你还是要知道 a 的值,否则如何调用原来的 f 方法呢?

#18 楼 @huacnlee 额 是这样的 但是目前还是传统的办法用得更普遍些

虽然并不知道这两个默认参数分别是什么

这种情况下我不会用这个方法。

除了用 hash 的参数,要么 NO WAY!

办法还是有的,不过很烦。就是先取得这个方法的参数的默认值。调用方法的时候把拿到的默认值再传过去。

可以参考 https://rubygems.org/gems/rdp-arguments 或者以前 merb 里面有个 get_args 的功能 http://stackoverflow.com/questions/622324/getting-argument-names-in-ruby-reflection/625222#625222

这个应该都能取道方法默认值

#23 楼 @kenshin54 好吧 好复杂哦

#24 楼 @iBachue 做法好像是在解析一遍语法,取得默认值。

以前 java 我也干过这个事情,java 的反射没法取到形参的参数名,因为形参的名字本身无意义,形参类型是有意义的,编译成 class 字节码文件后,class 内部不存储方法的形参名。那个时候找到个 hack 的技巧,class 文件在方法内部会放一个局部变量和形参同名,利用 javassist 可以取到那些信息。

具体可以看 http://kenbeit.com/posts/62/javassist%E7%9A%84%E5%AD%A6%E4%B9%A0

可以的哦,前提是 f 是在源文件中定义的,看你的描述应该满足。

假设有方法 f

def f a=1, b=2
  # ...
end

那么获取它的默认参数的值可以这么做:

require "method_source"
require "ripper"
class OptParamsBuilder < Ripper::SexpBuilder
  def initialize meth
    @src = meth.source
    super @src
  end

  def on_def *xs
    @method_name ||= xs[0][1]
    super
  end

  def on_params *xs
    @opt_params ||= xs[1]
    super
  end

  def opt_params
    parse
    params = @opt_params.map do |(_, param), value|
      param
    end
    first_line = @src.lines.to_a[0]
    eval "#{first_line}\nreturn #{params.join ' , '}\nend"
    send @method_name
  end
end

p OptParamsBuilder.new(method(:f)).opt_params #=> [1, 2]

P.S. 现在 ruby-china 的 markdown parser 被搞得很奇怪,~~删除~~ 前后不加空格的话就不能做删除线,代码中的空行也被自动消灭了 ...

#25 楼 @kenshin54 好先进 我对 Java 真是一窍不通

#27 楼 @luikore 代码空行那个我已经发现了。

#29 楼 @huacnlee http://example.com/~user 这样的地址直接就错了

http://example.com/~user

#31 楼 @huacnlee 难道你没看见~莫名奇妙跑到最后去了...

需要 登录 后方可回复, 如果你还没有账号请 注册新账号