调用方法

调用方法会向对象发送消息,以便其执行某些操作。

在 Ruby 中,您可以通过以下方式向对象发送消息

my_method()

请注意,括号是可选的

my_method

除非在使用和省略括号之间存在差异,否则本文档在存在参数时使用括号,以避免混淆。

本节仅介绍调用方法。另请参阅关于定义方法的语法文档

接收者

self 是默认接收者。如果您没有指定任何接收者,则将使用 self。要指定接收者,请使用 .

my_object.my_method

这会向 my_object 发送 my_method 消息。任何对象都可以是接收者,但根据方法的可见性,发送消息可能会引发NoMethodError

您也可以使用 :: 来指定接收者,但这很少使用,因为它可能会与 :: 用于命名空间时造成混淆。

链式方法调用

您可以通过立即在一个方法调用之后跟另一个方法调用来“链式”方法调用。

此示例链式调用了 Array#appendArray#compact 方法。

a = [:foo, 'bar', 2]
a1 = [:baz, nil, :bam, nil]
a2 = a.append(*a1).compact
a2 # => [:foo, "bar", 2, :baz, :bam]

详细信息

您可以链式调用不同类中的方法。此示例链式调用了 Hash#to_aArray#reverse 方法。

h = {foo: 0, bar: 1, baz: 2}
h.to_a.reverse # => [[:baz, 2], [:bar, 1], [:foo, 0]]

详细信息

安全导航操作符

&.,称为“安全导航操作符”,允许在接收者为 nil 时跳过方法调用。如果跳过调用,则它返回 nil 并且不计算方法的参数。

REGEX = /(ruby) is (\w+)/i
"Ruby is awesome!".match(REGEX).values_at(1, 2)
# => ["Ruby", "awesome"]
"Python is fascinating!".match(REGEX).values_at(1, 2)
# NoMethodError: undefined method `values_at' for nil:NilClass
"Python is fascinating!".match(REGEX)&.values_at(1, 2)
# => nil

这允许轻松链式调用可能返回空值的方法。请注意,&. 仅跳过下一个调用,因此对于较长的链,需要在每个级别添加操作符

"Python is fascinating!".match(REGEX)&.values_at(1, 2).join(' - ')
# NoMethodError: undefined method `join' for nil:NilClass
"Python is fascinating!".match(REGEX)&.values_at(1, 2)&.join(' - ')
# => nil

参数

发送消息时有三种类型的参数,即位置参数、关键字(或命名)参数和块参数。每个发送的消息可以使用一种、两种或所有类型的参数,但参数必须按此顺序提供。

Ruby 中的所有参数都按引用传递,并且不会惰性计算。

每个参数都由一个 , 分隔

my_method(1, '2', :three)

参数可以是表达式、哈希参数

'key' => value

或关键字参数

key: value

Hash 和关键字参数必须是连续的,并且必须出现在所有位置参数之后,但可以混合使用

my_method('a' => 1, b: 2, 'c' => 3)

位置参数

消息的位置参数紧跟在方法名称之后

my_method(argument1, argument2)

在许多情况下,发送消息时不需要括号

my_method argument1, argument2

但是,括号对于避免歧义是必要的。这将引发SyntaxError,因为 Ruby 不知道应将哪个方法参数 3 发送到

method_one argument1, method_two argument2, argument3

如果方法定义具有 *argument,则额外的位置参数将作为Array 在方法中分配给 argument

如果方法定义不包含关键字参数,则关键字或哈希类型参数将作为单个哈希分配给最后一个参数

def my_method(options)
  p options
end

my_method('a' => 1, b: 2) # prints: {'a'=>1, :b=>2}

如果给定的位置参数过多,则会引发ArgumentError

默认位置参数

当方法定义了默认参数时,您无需向该方法提供所有参数。Ruby 将按顺序填充缺失的参数。

首先,我们将介绍默认参数出现在右侧的简单情况。考虑以下方法

def my_method(a, b, c = 3, d = 4)
  p [a, b, c, d]
end

这里 cd 具有默认值,Ruby 将为您应用这些默认值。如果您只向该方法发送两个参数

my_method(1, 2)

您将看到 Ruby 打印 [1, 2, 3, 4]

如果您发送三个参数

my_method(1, 2, 5)

您将看到 Ruby 打印 [1, 2, 5, 4]

Ruby 从左到右填充缺失的参数。

Ruby 允许默认值出现在位置参数的中间。考虑这个更复杂的方法

def my_method(a, b = 2, c = 3, d)
  p [a, b, c, d]
end

这里 bc 具有默认值。如果您只向该方法发送两个参数

my_method(1, 4)

您将看到 Ruby 打印 [1, 2, 3, 4]

如果您发送三个参数

my_method(1, 5, 6)

您将看到 Ruby 打印 [1, 5, 3, 6]

用文字描述这一点会变得复杂且令人困惑。我将改用变量和值来描述它。

首先,1 被分配给 a,然后 6 被分配给 d。这只剩下具有默认值的参数。由于 5 尚未分配给值,因此将其赋予 b,而 c 使用其默认值 3

关键字参数

关键字参数跟在任何位置参数之后,并像位置参数一样用逗号分隔

my_method(positional1, keyword1: value1, keyword2: value2)

任何未给出的关键字参数都将使用方法定义中的默认值。如果给出了该方法未列出的关键字参数,并且该方法定义不接受任意关键字参数,则会引发ArgumentError

可以省略关键字参数值,这意味着将从上下文中按键的名称获取该值

keyword1 = 'some value'
my_method(positional1, keyword1:)
# ...is the same as
my_method(positional1, keyword1: keyword1)

请注意,当方法括号也被省略时,解析顺序可能会出乎意料

my_method positional1, keyword1:

some_other_expression

# ...is actually parsed as
my_method(positional1, keyword1: some_other_expression)

块参数

块参数将调用范围中的闭包发送到方法。

将消息发送到方法时,块参数始终是最后一个。使用 do ... end{ ... } 将块发送到方法

my_method do
  # ...
end

my_method {
  # ...
}

do end 的优先级低于 { },因此

method_1 method_2 {
  # ...
}

将块发送到 method_2,而

method_1 method_2 do
  # ...
end

将块发送到 method_1。请注意,在第一种情况下,如果使用了括号,则块将发送到 method_1

块将接受来自发送给它的方法的参数。参数的定义方式类似于方法定义参数的方式。块的参数在开头的 do{ 之后的 | ... | 中定义

my_method do |argument1, argument2|
  # ...
end

块局部参数

您还可以使用块参数列表中的 ; 来声明块的局部参数。为块局部参数赋值不会覆盖调用者范围中块外部的局部参数

def my_method
  yield self
end

place = "world"

my_method do |obj; place|
  place = "block"
  puts "hello #{obj} this is #{place}"
end

puts "place is: #{place}"

这会打印

hello main this is block
place is world

因此,块中的 place 变量与块外部的 place 变量不同。从块参数中删除 ; place 会得到此结果

hello main this is block
place is block

解包位置参数

给定以下方法

def my_method(argument1, argument2, argument3)
end

您可以使用 *(或 splat)操作符将Array 转换为参数列表

arguments = [1, 2, 3]
my_method(*arguments)

arguments = [2, 3]
my_method(1, *arguments)

两者等效于

my_method(1, 2, 3)

* 解包操作符可以应用于任何对象,而不仅仅是数组。如果对象响应 to_a 方法,则会调用此方法,并且期望返回一个Array,并且此数组的元素将作为单独的位置参数传递

class Name
  def initialize(name)
    @name = name
  end

  def to_a = @name.split(' ')
end

name = Name.new('Jane Doe')
p(*name)
# prints separate values:
#   Jane
#   Doe

如果对象没有 to_a 方法,则该对象本身将作为单个参数传递

class Name
  def initialize(name)
    @name = name
  end
end

name = Name.new('Jane Doe')
p(*name)
# Prints the object itself:
#   #<Name:0x00007f9d07bca650 @name="Jane Doe">

这允许以多态方式处理一个或多个参数。另请注意,nil 定义了 NilClass#to_a 以返回一个空数组,因此可以进行条件解包。

my_method(*(some_arguments if some_condition?))

如果存在 to_a 方法且该方法未返回 Array,则在解包时会出错。

class Name
  def initialize(name)
    @name = name
  end

  def to_a = @name
end

name = Name.new('Jane Doe')
p(*name)
#  can't convert Name to Array (Name#to_a gives String) (TypeError)

您还可以使用 **(接下来描述)将 Hash 转换为关键字参数。

如果 Array 中的对象数量与该方法的参数数量不匹配,则会引发 ArgumentError

如果 splat 运算符在调用中位于最前面,则必须使用圆括号,以避免将其解释为解包运算符或乘法运算符产生歧义。在这种情况下,Ruby 会在详细模式下发出警告。

my_method *arguments  # warning: '*' interpreted as argument prefix
my_method(*arguments) # no warning

解包关键字参数

给定以下方法

def my_method(first: 1, second: 2, third: 3)
end

您可以使用 **(关键字 splat)运算符将 Hash 转换为关键字参数。

arguments = { first: 3, second: 4, third: 5 }
my_method(**arguments)

arguments = { first: 3, second: 4 }
my_method(third: 5, **arguments)

两者等效于

my_method(first: 3, second: 4, third: 5)

** 解包运算符可以应用于任何对象,而不仅仅是哈希。如果对象响应 to_hash 方法,则会调用此方法,并期望返回 Hash,并且此哈希的元素将作为关键字参数传递。

class Name
  def initialize(name)
    @name = name
  end

  def to_hash = {first: @name.split(' ').first, last: @name.split(' ').last}
end

name = Name.new('Jane Doe')
p(**name)
# Prints: {name: "Jane", last: "Doe"}

* 运算符不同,** 在用于不响应 to_hash 的对象时会引发错误。唯一例外是 nil,它没有明确定义此方法,但仍然允许在 ** 解包中使用,不会添加任何关键字参数。

同样,这允许进行条件解包。

my_method(some: params, **(some_extra_params if pass_extra_params?))

* 运算符一样,当对象响应 to_hash,但未返回 Hash 时,** 会引发错误。

如果方法定义使用关键字 splat 运算符来收集任意关键字参数,则这些参数不会被 * 收集。

def my_method(*a, **kw)
  p arguments: a, keywords: kw
end

my_method(1, 2, '3' => 4, five: 6)

打印

{:arguments=>[1, 2], :keywords=>{'3'=>4, :five=>6}}

Proc 到代码块的转换

给定一个使用代码块的方法

def my_method
  yield self
end

您可以使用 &(代码块转换)运算符将 proc 或 lambda 转换为代码块参数。

argument = proc { |a| puts "#{a.inspect} was yielded" }

my_method(&argument)

如果代码块转换运算符在调用中位于最前面,则必须使用圆括号以避免警告。

my_method &argument  # warning
my_method(&argument) # no warning

Method 查找

当您发送消息时,Ruby 会查找与接收者的消息名称匹配的方法。方法存储在类和模块中,因此方法查找会遍历这些类和模块,而不是对象本身。

以下是接收者的类或模块 R 的方法查找顺序:

如果 R 是一个带有超类的类,则会对 R 的超类重复此过程,直到找到方法为止。

一旦找到匹配项,方法查找就会停止。

如果没有找到匹配项,则会从头开始重复此过程,但查找 method_missing。默认的 method_missingBasicObject#method_missing,它在被调用时会引发 NameError

如果启用了 refinements(一个实验性功能),则方法查找会发生变化。有关详细信息,请参阅 refinements 文档