34

在 python 中,引用一个函数相当简单:

>>> def foo():
...     print "foo called"
...     return 1
... 
>>> x = foo
>>> foo()
foo called
1
>>> x()
foo called
1
>>> x
<function foo at 0x1004ba5f0>
>>> foo
<function foo at 0x1004ba5f0>

然而,它在 Ruby 中似乎有所不同,因为一个裸foo实际上调用了 foo:

ruby-1.9.2-p0 > def foo
ruby-1.9.2-p0 ?>  print "foo called"
ruby-1.9.2-p0 ?>  1
ruby-1.9.2-p0 ?>  end
 => nil 
ruby-1.9.2-p0 > x = foo
foo called => 1 
ruby-1.9.2-p0 > foo
foo called => 1 
ruby-1.9.2-p0 > x
 => 1 

我如何实际将函数foo 分配给 x 然后调用它?还是有更惯用的方法来做到这一点?

4

4 回答 4

59

Ruby 没有函数。它只有方法(不是一流的)和Proc一流的,但不与任何对象相关联。

所以,这是一种方法:

def foo(bar) puts bar end

foo('Hello')
# Hello

哦,是的,这一个真正的方法,而不是顶级函数或过程之类的东西。在顶层定义的方法最终成为Object类中的私有(!)实例方法:

Object.private_instance_methods(false) # => [:foo]

这是一个Proc

foo = -> bar { puts bar }

foo.('Hello')
# Hello

请注意,Procs 的调用方式与方法不同:

foo('Hello')  # method
foo.('Hello') # Proc

foo.(bar)语法只是语法糖foo.call(bar)(对于sProcMethods 也别名为foo[bar])。在对象上实现一个call方法然后调用它.()是最接近 Python 能力的事情__call__

请注意,Ruby 和 Python lambda 之间的一个重要区别Proc是没有限制:在 Python 中,一个 lambda 只能包含一条语句,但 Ruby 没有语句和表达式之间的区别(一切都是表达式),并且所以这个限制根本不存在,因此在很多情况下,您需要在 Python 中将命名函数作为参数传递,因为您无法在单个语句中表达逻辑,您可以在 Ruby 中简单地传递 aProc或块,因此甚至不会出现引用方法的难看语法问题。

您可以通过调用对象上的方法(这将为您提供绑定到该特定对象的对象)来将方法包装Method对象中(本质上是鸭子类型):ProcObject#methodMethodself

foo_bound = method(:foo)

foo_bound.('Hello')
# Hello

您还可以使用该Module#instance_method系列中的一种方法UnboundMethod从模块(或类,显然,因为一个类是一个模块)中获取一个,然后您可以UnboundMethod#bind对一个特定的对象进行调用。(我认为 Python 具有相同的概念,尽管实现方式不同:未绑定的方法只是显式地接受 self 参数,就像它被声明的方式一样。)

foo_unbound = Object.instance_method(:foo) # this is an UnboundMethod

foo_unbound.('Hello')
# NoMethodError: undefined method `call' for #<UnboundMethod: Object#foo>

foo_rebound = foo_unbound.bind(self)       # this is a Method

foo_rebound.('Hello')
# Hello

请注意,您只能将 an 绑定UnboundMethod到一个对象,该对象是您从中获取方法的模块的实例。您不能用于UnboundMethods在不相关的模块之间“移植”行为:

bar = module Foo; def bar; puts 'Bye' end; self end.instance_method(:bar)
module Foo; def bar; puts 'Hello' end end

obj = Object.new
bar.bind(obj)
# TypeError: bind argument must be an instance of Foo

obj.extend(Foo)
bar.bind(obj).()
# Bye
obj.bar
# Hello

但是请注意,theMethod和 theUnboundMethod都是方法的包装器而不是方法本身。方法不是Ruby 中的对象。(与我在其他答案中写的相反,顺便说一句。我真的需要回去修复这些。)您可以将它们包装在对象中,但它们不是对象,您可以看到这一点,因为您基本上得到了所有相同的东西使用包装器时经常遇到的问题:身份和状态。如果你method多次调用同一个方法,你Method每次都会得到一个不同的对象。如果您尝试在该Method对象上存储一些状态(例如 Python 风格__doc__字符串,例如),该状态将是该特定实例的私有状态,如果您尝试再次通过 检索您的文档字符串method,您会发现它已经消失了。

方法引用运算符.:的形式还有语法糖:

bound_method = obj.:foo

这与

bound_method = obj.method(:foo)
于 2010-11-28T00:22:36.040 回答
10

您可以使用method继承自的实例方法Object来检索Method对象,该对象本质上是Proc您可以调用的对象call

在控制台中,您可以这样做:

fooMethod = self.method(:foo) #fooMethod is a Method object

fooMethod.call #invokes fooMethod

替代文字

于 2010-11-27T23:36:15.777 回答
1

从https://stackoverflow.com/a/26620095/226255复制的函数和方法之间的(主要)区别

函数是在类之外定义的,而方法是在类内部和类的一部分中定义的。

Ruby 没有函数,您def foo最终成为Object该类的方法。

如果你坚持foo像上面那样定义,你可以通过这样做来提取它的“功能”:

def foo(a,b)
 a+b
end

x = method(:foo).to_proc
x.call(1,2)
=> 3

解释:

> method(:foo) # this is Object.method(:foo), returns a Method object bound to 
# object of type 'Class(Object)'
=> #<Method: Class(Object)#foo>

method(:foo).to_proc
# a Proc that can be called without the original object the method was bound to
=> #<Proc:0x007f97845f35e8 (lambda)>

重要的提示:

to_proc“复制”方法的对象的关联实例变量(如果有)。考虑一下:

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

  def greet
    puts "hello #{@name}"
  end
end

greet = Person.new('Abdo').method(:greet) 
# note that Person.method(:greet) returns an UnboundMethod and cannot be called 
# unless you bind it to an object

> greet.call
hello Abdo
=> nil

从概念上讲,如果您想要一个适用于某种类型对象的“函数”,它应该是一种方法,并且您应该这样组织您的代码。如果您只需要在特定上下文中使用“函数”并希望传递它,请使用 lambdas:

greet = lambda { |person| "hello #{person}" }
yell_at = lambda { |person| "HELLO #{person.upcase}" }

def do_to_person(person, m)
  m.call(person)
end

do_to_person('Abdo', greet)
于 2014-11-19T17:05:40.117 回答
1

Ruby 支持proc并且lambda在其他语言中可能称为匿名函数或闭包,具体取决于它们的使用方式。它们可能更接近您正在寻找的东西。

于 2010-11-28T00:09:14.417 回答