调用方法
调用方法会向对象发送一条消息,以便它执行某些操作。
在 Ruby 中,您这样向对象发送消息:
my_method()
请注意,括号是可选的
my_method
除非使用和省略括号存在差异,否则本文档在存在参数时使用括号,以避免混淆。
本节仅介绍调用方法。另请参阅定义方法的语法文档。
接收者
self 是默认接收者。如果您不指定任何接收者,将使用 self。要指定接收者,请使用 .
my_object.my_method
这将向 my_object 发送 my_method 消息。任何对象都可以是接收者,但根据方法的可见性,发送消息可能会引发 NoMethodError。
您也可以使用 :: 来指定接收者,但这很少使用,因为与命名空间中的 :: 容易混淆。
链式方法调用
您可以通过立即将一个方法调用紧跟另一个方法调用来“链式”调用方法。
此示例链式调用方法 Array#append 和 Array#compact
a = [:foo, 'bar', 2] a1 = [:baz, nil, :bam, nil] a2 = a.append(*a1).compact a2 # => [:foo, "bar", 2, :baz, :bam]
详情
-
第一个方法
merge创建a的副本,将a1的每个元素(单独)追加到副本中,然后返回。[:foo, "bar", 2, :baz, nil, :bam, nil]
-
链式方法
compact创建该返回值的副本,删除其nil值项,然后返回。[:foo, "bar", 2, :baz, :bam]
您可以链式调用属于不同类的相同方法。此示例链式调用方法 Hash#to_a 和 Array#reverse
h = {foo: 0, bar: 1, baz: 2} h.to_a.reverse # => [[:baz, 2], [:bar, 1], [:foo, 0]]
详情
-
第一个方法
Hash#to_a将a转换为 Array,然后返回。[[:foo, 0], [:bar, 1], [:baz, 2]]
-
链式方法
Array#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 不知道应该将哪个方法参数发送到 `argument3`。
method_one argument1, method_two argument2, argument3
如果方法定义中有 *argument,额外的位置参数将被分配给方法中的 argument,作为一个 Array。
如果方法定义不包含关键字参数,则关键字参数或哈希类型参数将作为单个哈希分配给最后一个参数。
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
这里 c 和 d 有默认值,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
这里 b 和 c 有默认值。如果您只向此方法传递两个参数:
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 会解包为空参数列表,因此可以进行条件解包。
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
方法查找
当您发送消息时,Ruby 会查找与接收者的消息名称匹配的方法。方法存储在类和模块中,因此方法查找会遍历它们,而不是对象本身。
以下是接收者类或模块 R 的方法查找顺序:
-
R的预置模块(反向顺序) -
在
R中找到匹配方法 -
R的包含模块(反向顺序)
如果 R 是一个具有超类的类,则会用 R 的超类重复此过程,直到找到方法为止。
找到匹配项后,方法查找将停止。
如果未找到匹配项,则从头开始重复此过程,但查找 method_missing。默认的 method_missing 是 BasicObject#method_missing,它在调用时会引发 NameError。
如果启用了 refinement(一种实验性功能),方法查找会发生变化。有关详细信息,请参阅refinement 文档。