【问题标题】:How to pass on all parameters of a method send如何传递方法发送的所有参数
【发布时间】:2019-02-19 15:56:31
【问题描述】:

问题

在一个方法中,如何将作为方法发送的一部分提供的所有参数传递给另一个方法发送?参数都应该在没有像*args 这样的占位符/捕获符的意义上命名,但它们可以是关键字参数和非关键字参数的混合。另外,内部方法 send 不是super

示例(伪代码)

def some_method(a_parameter, a_named_parameter:)
  ...do something...
  some_other_method([send with original parameters])
  ...do something else...
end

相关问题

Is there a way to access method arguments in Ruby? 7 年前被问过。

丑陋的黑客

根据我的发现,可以对关键字参数做这样的事情:

def some_method(a_named_parameter:, another_named_parameter:)
  ...do something...

  params = method(__method__)
           .parameters
           .map { |p| [p[1], binding.local_variable_get(p[1])] }
           .to_h

  some_other_method(**params)
  ...do something else...
end

这对于非关键字参数:

def some_method(a_named_parameter, another_named_parameter)
  ...do something...

  params = method(__method__)
           .parameters
           .map { |p| binding.local_variable_get(p[1]) }

  some_other_method(*params)
  ...do something else...
end

根据method(__method__).parameters 返回的信息,人们也可以制定出对两者都适用的解决方案,但即使可以将所有这些都提取到帮助程序中,这也太复杂了。

【问题讨论】:

  • 我不明白,你知道some_other_method的签名。为什么不简单地写出它需要的所有参数呢?它简单易读,不需要魔法。
  • 当然是@SergioTulentsev,我也会照常这样做。但是考虑到可以拨打super,而且我觉得这真的很方便,我想知道是否也可以这样做。我希望它不像黑客那样神奇。
  • 我明白了。我希望你能找到它:)
  • @SergioTulentsev 欢迎您对我的回答提出意见,因为我正在努力寻找可能遗漏的边缘案例。

标签: ruby


【解决方案1】:

这是一个非常有趣的问题,所以首先,谢谢你。

鉴于Binding 是一个对象,就像任何其他对象一样,我们可以构建一个类来提供这种类型的功能,并利用原始方法的绑定将所有参数委托给下一个方法,如下所示:

class ArgsBuilder 
  attr_reader
  def initialize(b)
    @standard_args, @kwargs = [],{}
    @binding = b
    build!
  end
  def delegate(m)
    @binding.receiver.send(m,*@standard_args,**@kwargs,&@block)
  end
  private
    def build!
      set_block(&@binding.eval('Proc.new')) if @binding.eval('block_given?')
      @binding.eval('method(__method__)') 
        .parameters.each do |type,name|
          next if type == :block
          val = @binding.local_variable_get(name)
          if type =~ /key/
            @kwargs.merge!(type == :keyrest ? val : {name => val})
          else  
            type == :rest ? @standard_args.concat(val) : @standard_args << val
          end
        end
    end
    def set_block(&block)
      @block = block
    end
end

用法:

class B 

  def some_method(a,b,c, *d, e:, f:, g: nil, **h)
    ArgsBuilder.new(binding).delegate(:some_other_method)      
  end

  def some_other_method(a,b,c, *d, e:, f:, g: , **h)
     yield __method__ if block_given?
    "I received: #{[a,b,c,d,e,f,g,h].inspect}"
  end
end

B.new.some_method(1,2,3,4,5,6, e: 7, f: 8, p: 9, n: 10) do |m| 
  puts "called from #{m}"
end
# called from some_other_method
#=> "I received: [1, 2, 3, [4, 5, 6], 7, 8, nil, {:p=>9, :n=>10}]"
#                 a  b  c  ----d----  e  f   g   -------h-------

看起来很简单,可以作为一个助手并处理跨方法传递的命名和匿名块。

TL;DR

这显然需要在委托方法中匹配或至少可以接受类似的签名,类似于super 的工作方式。然而,我们可以更进一步,为[:req,:opt,:rest,:keyreq,:key,:keyrest,:block] 的参数类型创建类,将它们放入一个集合中,然后询问被委托的方法以确定要传递的正确参数;但是我不确定这种类型的示例是否适合 SO 帖子。

附加说明:因为在initialize 中调用了build!,所以局部变量绑定在ArgsBuilder 类的实例化点是静态的。例如

 def foo(n) 
   builder = ArgsBuilder.new(binding) 
   n = 17
   builder.delegate(:bar)
 end 
 def bar(n) 
   n 
 end 

  foo(42) 
  #=> 42

然而

 def foo(n) 
   n = 17
   ArgsBuilder.new(binding).delegate(:bar)
 end 

 foo(42) 
 #=> 17

这并不意味着可变对象不能改变

 def foo(n)
   builder = ArgsBuilder.new(binding) 
   n.upcase!
   builder.delegate(:bar)
 end

 foo('a')
 #=> "A"

您显然可以通过将呼叫转移到 build! 来改变这一点

【讨论】:

    【解决方案2】:

    也许你是这个意思

    def some_method *args, **kwargs, &block
      ...
      some_other_method(*args, **kwargs, &block)
      ...
    end
    

    但我认为在some _other_method 上使用方法委托更简洁。

    【讨论】:

    • 除非我们在some_method的签名中丢失了所需的args/kwargs。
    • @SergioTulentsev 如果它们是固定的,我们可以将它们塞进两个方法的签名中。是这样吗?我实际上不确定这个问题。
    • 这是个问题,我认为:“但考虑到调用 super 是可能的,我发现这真的很方便,我想知道是否也可以这样做。”。做super 做的事,只是为了一个不相关的方法。
    【解决方案3】:

    出于好奇,您可能需要Module.prepend 的帮助:

    class Origin
      def m1(p, *args, **kw)
        m2()
      end
    
      def m2(p, *args, **kw)
        puts "p: #{p}, args: #{args.inspect}, kw: #{kw.inspect}"
      end
    end
    
    module Wrapper 
      def m1(*args, **kw)
        @__args__, @__kw__ = args, kw
        super
      end
    
      def m2(*)
        super(*@__args__, **@__kw__)
      end
    end
    
    Origin.prepend(Wrapper)
    
    Origin.new.m1(:foo, 42, bar: :baz)
    #⇒ p: foo, args: [42], kw: {:bar=>:baz}
    

    【讨论】:

    • 但这需要在前置模块中定义的已知方法名称作为通道,这似乎不是通用的。
    • @engineersmnky 好吧,是的,也不是。无论在原始代码中如何调用m2,之后都可以管理遗留代码。或在代码生成器中。但我当然不会在家里或学校使用它。
    • 我看到的唯一其他问题是直接调用 m2 将失败(如果首先调用)或保留来自 m1 调用的值(如果随后调用)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-05
    • 2011-01-04
    • 1970-01-01
    • 2011-07-18
    • 1970-01-01
    相关资源
    最近更新 更多