方法¶ ↑
方法实现程序的功能。这是一个简单的方法定义
def one_plus_one 1 + 1 end
方法定义包含 def
关键字、方法名称、方法体、return
值和 end
关键字。调用时,该方法将执行方法体。此方法返回 2
。
自 Ruby 3.0 起,对于仅包含一个表达式的方法,还有一种简写语法
def one_plus_one = 1 + 1
本节仅介绍如何定义方法。另请参阅关于调用方法的语法文档。
方法
名称¶ ↑
方法
名称可以是运算符之一,或者必须以字母或第八位设置的字符开头。它可以包含字母、数字、_
(下划线或底线)或第八位设置的字符。约定是在多词方法名称中使用下划线分隔单词
def method_name puts "use underscores to separate words" end
Ruby 程序必须使用与 US-ASCII 兼容的字符集编写,例如 UTF-8、ISO-8859-1 等。在这样的字符集中,如果设置了第八位,则表示扩展字符。Ruby 允许方法名称和其他标识符包含此类字符。Ruby 程序不能包含某些字符,如 ASCII NUL (\x00
)。
以下是有效 Ruby 方法的示例
def hello "hello" end def こんにちは puts "means hello in Japanese" end
通常,方法名称与 US-ASCII 兼容,因为键入它们所需的键在所有键盘上都存在。
方法
名称可以以 !
(感叹号)、?
(问号)或 =
(等号)结尾。
感叹号方法(方法名称末尾的 !
)的调用和执行方式与其他方法相同。但是,按照惯例,带有感叹号或感叹号的方法被认为是危险的。在 Ruby 的核心库中,危险的方法表示当一个方法以感叹号 (!
) 结尾时,它与它的非感叹号等效方法不同,会永久修改其接收器。几乎总是,Ruby 核心库会为每个感叹号方法(以 !
结尾的方法名称)提供一个非感叹号对应的项(不以 !
结尾的方法名称),该方法不会修改接收器。此约定通常适用于 Ruby 核心库,但可能不适用于其他 Ruby 库。
按照惯例,以问号结尾的方法返回布尔值,但它们可能不总是只返回 true
或 false
。通常,它们会返回一个对象来表示真值(或“真值”)。
以等号结尾的方法表示赋值方法。
class C def attr @attr end def attr=(val) @attr = val end end c = C.new c.attr #=> nil c.attr = 10 # calls "attr=(10)" c.attr #=> 10
不能使用简写语法定义赋值方法。
这些是各种 Ruby 运算符的方法名称。每个运算符仅接受一个参数。运算符后面是运算符的典型用法或名称。为运算符创建其他含义可能会导致混淆,因为用户希望加号添加内容,减号减去内容等。此外,您无法更改运算符的优先级。
+
-
加法
-
-
减法
*
-
乘法
**
-
幂
/
-
除法
%
-
模数除法,
字符串#%
&
-
与
^
-
异或
>>
-
右移
<<
-
左移,追加
==
-
等于
!=
-
不等于
===
-
大小写相等。请参阅
对象#===
=~
-
模式匹配。(不仅用于正则表达式)
!~
-
不匹配
<=>
-
比较运算符,又名太空船运算符。请参阅
可比较
<
-
小于
<=
-
小于或等于
>
-
大于
>=
-
大于或等于
要定义一元方法减号和加号,请在运算符后面加上 @
,如 +@
class C def -@ puts "you inverted this object" end end obj = C.new -obj # prints "you inverted this object"
需要 @
来区分一元减号和加号运算符与二元减号和加号运算符。
您也可以在波浪号和非 (!
) 一元方法后面加上 @
,但这不是必需的,因为没有二元波浪号和非运算符。
一元方法接受零个参数。
此外,可以定义用于元素引用和赋值的方法:分别为 []
和 []=
。两者都可以接受一个或多个参数,并且元素引用可以不接受任何参数。
class C def [](a, b) puts a + b end def []=(a, b, c) puts a * b + c end end obj = C.new obj[2, 3] # prints "5" obj[2, 3] = 4 # prints "10"
返回值¶ ↑
默认情况下,方法返回方法体中计算的最后一个表达式。在上面的示例中,最后一个(也是唯一一个)计算的表达式是简单的求和 1 + 1
。可以使用 return
关键字显式表示方法返回一个值。
def one_plus_one return 1 + 1 end
它也可以用于使方法在最后一个表达式计算之前返回。
def two_plus_two return 2 + 2 1 + 1 # this expression is never evaluated end
请注意,对于赋值方法,当使用赋值语法时,返回值将被忽略。而是会返回参数
def a=(value) return 1 + value end p(self.a = 5) # prints 5
直接调用该方法时,将返回实际返回值
p send(:a=, 5) # prints 6
作用域¶ ↑
定义方法的标准语法
def my_method # ... end
将方法添加到类中。您可以使用 class
关键字在特定类上定义实例方法
class C def my_method # ... end end
可以在另一个对象上定义方法。您可以定义一个“类方法”(在类上定义的方法,而不是类的实例),如下所示
class C def self.my_method # ... end end
但是,这只是 Ruby 中更大语法能力的一个特例,即向任何对象添加方法的能力。类是对象,因此添加类方法只是向 类
对象添加方法。
向对象添加方法的语法如下
greeting = "Hello" def greeting.broaden self + ", world!" end greeting.broaden # returns "Hello, world!"
self
是一个关键字,指的是编译器当前正在考虑的对象,这可能会使在上面的类方法定义中使用 self
稍微更清楚一些。实际上,将 hello
方法添加到类 String
的示例可以这样重写
def String.hello "Hello, world!" end
像这样定义的方法称为“单例方法”。broaden
将仅存在于字符串实例 greeting
上。其他字符串将不具有 broaden
。
重写¶ ↑
当 Ruby 遇到 def
关键字时,如果该方法已经存在,它不会将其视为错误:它只是重新定义它。这称为重写。与扩展核心类一样,这是一种潜在的危险能力,应谨慎使用,因为它可能会导致意外结果。例如,请考虑以下 irb 会话
>> "43".to_i => 43 >> class String >> def to_i >> 42 >> end >> end => nil >> "43".to_i => 42
这将有效地破坏任何使用方法 String#to_i
从字符串解析数字的代码。
参数¶ ↑
方法可以接受参数。参数列表位于方法名称之后
def add_one(value) value + 1 end
调用时,add_one
方法的用户必须提供一个参数。参数是方法体中的局部变量。然后,该方法将此参数加一并返回该值。如果给定 1
,此方法将返回 2
。
参数周围的括号是可选的
def add_one value value + 1 end
在简写方法定义中,括号是必需的
# OK def add_one(value) = value + 1 # SyntaxError def add_one value = value + 1
多个参数用逗号分隔
def add_values(a, b) a + b end
调用时,必须按照确切的顺序提供参数。换句话说,参数是位置的。
默认值¶ ↑
参数可以具有默认值
def add_values(a, b = 1) a + b end
默认值不需要首先出现,但具有默认值的参数必须分组在一起。这是可以的
def add_values(a = 1, b = 2, c) a + b + c end
这将引发 SyntaxError
def add_values(a = 1, b, c = 1) a + b + c end
默认参数值可以引用已被计算为局部变量的参数,并且始终从左到右计算参数值。所以这是允许的
def add_values(a = 1, b = a) a + b end add_values # => 2
但这将引发 NameError
(除非定义了名为 b
的方法)
def add_values(a = b, b = 1) a + b end add_values # NameError (undefined local variable or method `b' for main:Object)
数组
分解¶ ↑
你可以使用参数中的额外括号来分解(解包或提取值)一个 Array
。
def my_method((a, b)) p a: a, b: b end my_method([1, 2])
这会打印出
{:a=>1, :b=>2}
如果 Array
中有额外的元素,它们将被忽略。
def my_method((a, b)) p a: a, b: b end my_method([1, 2, 3])
这与上面的输出相同。
你可以使用 *
来收集剩余的参数。这将一个 Array
分割为第一个元素和其余部分。
def my_method((a, *b)) p a: a, b: b end my_method([1, 2, 3])
这会打印出
{:a=>1, :b=>[2, 3]}
如果参数响应 to_ary 方法,它将被分解。只有当你的对象可以代替 Array
使用时,你才应该定义 to_ary 方法。
使用内部括号只使用发送的参数之一。如果参数不是 Array
,它将被分配给分解中的第一个参数,分解中的其余参数将为 nil
。
def my_method(a, (b, c), d) p a: a, b: b, c: c, d: d end my_method(1, 2, 3)
这会打印出
{:a=>1, :b=>2, :c=>nil, :d=>3}
你可以任意嵌套分解。
def my_method(((a, b), c)) # ... end
数组/哈希参数¶ ↑
在参数前加上 *
会导致任何剩余的参数被转换为一个数组。
def gather_arguments(*arguments) p arguments end gather_arguments 1, 2, 3 # prints [1, 2, 3]
数组参数必须出现在任何关键字参数之前。
可以在开头或中间收集参数。
def gather_arguments(first_arg, *middle_arguments, last_arg) p middle_arguments end gather_arguments 1, 2, 3, 4 # prints [2, 3]
如果调用者在所有位置参数之后提供了关键字,数组参数会将一个 Hash
作为最后一个条目捕获。
def gather_arguments(*arguments) p arguments end gather_arguments 1, a: 2 # prints [1, {:a=>2}]
然而,这只发生在方法没有声明任何关键字参数时。
def gather_arguments_keyword(*positional, keyword: nil) p positional: positional, keyword: keyword end gather_arguments_keyword 1, 2, three: 3 #=> raises: unknown keyword: three (ArgumentError)
另外,请注意,一个单独的 *
可以用来忽略参数。
def ignore_arguments(*) end
在调用方法时,你也可以使用单独的 *
将参数直接传递给另一个方法。
def delegate_arguments(*) other_method(*) end
关键字参数¶ ↑
关键字参数类似于带有默认值的位置参数。
def add_values(first: 1, second: 2) first + second end
可以使用 **
接受任意关键字参数。
def gather_arguments(first: nil, **rest) p first, rest end gather_arguments first: 1, second: 2, third: 3 # prints 1 then {:second=>2, :third=>3}
当使用关键字参数调用方法时,参数可以以任何顺序出现。如果调用者发送了一个未知的关键字参数,并且该方法不接受任意关键字参数,则会引发 ArgumentError
。
要要求特定的关键字参数,请不要为关键字参数包含默认值。
def add_values(first:, second:) first + second end add_values # ArgumentError (missing keywords: first, second) add_values(first: 1, second: 2) # => 3
当混合使用关键字参数和位置参数时,所有位置参数必须出现在任何关键字参数之前。
另外,请注意,**
可以用来忽略关键字参数。
def ignore_keywords(**) end
在调用方法时,你也可以使用 **
将关键字参数委托给另一个方法。
def delegate_keywords(**) other_method(**) end
要将一个方法标记为接受关键字,但不实际接受关键字,你可以使用 **nil
。
def no_keywords(**nil) end
使用关键字或非空的关键字 splat 调用这样的方法会导致 ArgumentError
。支持此语法是为了可以在以后向方法添加关键字,而不会影响向后兼容性。
如果方法定义不接受任何关键字,并且未使用 **nil
语法,则在调用该方法时提供的任何关键字都将转换为 Hash
位置参数。
def meth(arg) arg end meth(a: 1) # => {:a=>1}
块参数¶ ↑
块参数由 &
表示,并且必须放在最后。
def my_method(&my_block) my_block.call(self) end
最常见的情况是,块参数用于将块传递给另一个方法。
def each_item(&block) @items.each(&block) end
如果你只是将其传递给另一个方法,则不需要为块命名。
def each_item(&) @items.each(&) end
如果你只打算调用块,而不打算以其他方式操作它或将其发送到另一个方法,则最好使用不带显式块参数的 yield
。此方法等效于本节中的第一个方法。
def my_method yield self end
参数转发¶ ↑
自 Ruby 2.7 起,可以使用全参数转发语法。
def concrete_method(*positional_args, **keyword_args, &block) [positional_args, keyword_args, block] end def forwarding_method(...) concrete_method(...) end forwarding_method(1, b: 2) { puts 3 } #=> [[1], {:b=>2}, #<Proc:...skip...>]
仅在使用 ...
定义的方法中可以使用带转发 ...
的调用。
def regular_method(arg, **kwarg) concrete_method(...) # Syntax error end
自 Ruby 3.0 起,在定义和调用中,...
前面都可以有前导参数(但在定义中,它们只能是没有默认值的位置参数)。
def request(method, path, **headers) puts "#{method.upcase} #{path} #{headers}" end def get(...) request(:GET, ...) # leading argument in invoking end get('https://ruby-lang.org.cn', 'Accept' => 'text/html') # Prints: GET https://ruby-lang.org.cn {"Accept"=>"text/html"} def logged_get(msg, ...) # leading argument in definition puts "Invoking #get: #{msg}" get(...) end logged_get('Ruby site', 'https://ruby-lang.org.cn') # Prints: # Invoking #get: Ruby site # GET https://ruby-lang.org.cn {}
请注意,在转发调用中省略括号可能会导致意外的结果。
def log(...) puts ... # This would be treated as `puts()...', # i.e. endless range from puts result end log("test") # Prints: warning: ... at EOL, should be parenthesized? # ...and then empty line
Exception
处理¶ ↑
方法具有隐含的异常处理块,因此你不需要使用 begin
或 end
来处理异常。这个
def my_method begin # code that may raise an exception rescue # handle exception end end
可以写成
def my_method # code that may raise an exception rescue # handle exception end
类似地,如果你希望即使引发异常也始终运行代码,则可以使用不带 begin
和 end
的 ensure
。
def my_method # code that may raise an exception ensure # code that runs even if previous code raised an exception end
你还可以将 rescue
与 ensure
和/或 else
结合使用,而无需 begin
和 end
。
def my_method # code that may raise an exception rescue # handle exception else # only run if no exception raised above ensure # code that runs even if previous code raised an exception end
如果你希望仅针对方法的某一部分捕获异常,请使用 begin
和 end
。有关更多详细信息,请参阅有关异常处理的页面。