调用方法¶ ↑
调用一个方法会向一个对象发送消息,使其能够执行某些操作。
在 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
转换为数组并返回[[: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 不知道应将哪个方法参数 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
这里 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
定义了 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
中查找匹配的方法 -
R
的包含模块,按相反的顺序排列
如果 R
是一个带有超类的类,则会对 R
的超类重复此过程,直到找到方法为止。
一旦找到匹配项,方法查找就会停止。
如果没有找到匹配项,则会从头开始重复此过程,但会查找 method_missing
。默认的 method_missing
是 BasicObject#method_missing
,当被调用时会引发 NameError
。
如果启用了 refinements(一个实验性功能),则方法查找会发生变化。有关详细信息,请参阅refinements 文档。