【问题标题】:How does ruby tell the difference between instance method and class method definition?ruby 如何区分实例方法和类方法定义?
【发布时间】:2019-01-08 02:48:17
【问题描述】:

在普通的 ruby​​ 代码中,我通常可以省略 self:

class User
  def greeting
    # these 2 are the same:
    puts "Hi, #{name}"
    puts "Hi, #{self.name}"
  end
end

但是为什么这两个不同:

# 1
class User
  def self.greeting
    # ...
  end
end

# 2
class User
  def greeting
    # ...
  end
end

为什么我不能省略self?从 ruby​​ 代码编译器的角度来看,这里有什么不同?

我知道#1 是类方法定义(单例方法定义),#2 是实例方法定义。但是 ruby​​ 是如何与 context 和 self 一起工作来实现方法定义的这种差异的呢?

【问题讨论】:

  • 您的问题不清楚。 #1 与 #2 有何不同?他们不是。
  • 抱歉,我忘记在 #2 中删除 self

标签: ruby


【解决方案1】:

方法体中,self 指的是接收者。在下面的第 3..4 行中,一旦接收者被确定为User 实例(通过def greeting 语法),self 将引用该实例。

class User
  def greeting
    puts "Hi, #{name}"      # method body
    puts "Hi, #{self.name}" # method body
  end
end

类主体中,self 指的是。在下面的第2、4、8、10行,类是User,所以def self.greetingdef User.greeting是一样的。

class User
  def self.greeting # class body
    # ...
  end               # class body
end

class User
  def greeting      # class body
    # ...
  end               # class body
end

但我实际上认为您真正的问题不是self 的含义,而是“省略的接收者”在不同情况下的含义。

在方法-调用语法中,省略的接收者代表self。所以下面两个是一样的:

name
self.name

在方法-定义语法中,省略的接收者代表“类的任何实例”。所以下面两个是不一样的:

def User.greeting; ... end
def greeting; ... end

当你定义一个实例方法时,没有明确的方式来表达“类的任何实例”,所以实际上省略是强制性的。

【讨论】:

    【解决方案2】:

    有时需要self(否则根本不存在)。您给出的第一个示例是冗余的情况。这两个方法调用都引用同一个方法。

    有时需要区分两种不同的行为。

    在您的示例 #1 和 #2 中,self 用于确保方法是在类上定义的,而不是在类的实例上。在这种情况下,self 不是多余的。这是 Ruby 解释器知道你想在哪里定义方法的方式。将第一个示例中的 self 视为与示例 #1 和 #2 中的 self 不同的事物可能会有所帮助。将#1 和#2 中的self 视为“class”可能会有所帮助?它们是相同的关键字,但它们的使用方式不能直接互换。

    另一个不冗余的例子:

    # user.rb
    
    def method1
      name = "Henry" # Sets a local variable called `name`
    end
    
    def method2
      self.name = "Henry" # Sets the user's `name` attribute
    end
    

    【讨论】:

    • 我已根据您的其他 cmets 和标题更改更新了我的答案。特别是这部分:将第一个示例中的self 与示例#1 和#2 中的self 不同可能会有所帮助。将#1 和#2 中的self 视为class 可能会有所帮助?它们是同一个词,但它们的用途和变化不能直接互换。
    【解决方案3】:

    类上定义的方法是实例方法

    class Animal
      def dog
        "woof"
      end
    
      def cat
        "meow"
      end
    end
    

    它们之所以如此命名是因为它们响应类的任何实例,这意味着它们的接收者必须是类的实例:

    Animal.instance_methods(false)
      #=> [:dog, :cat] 
    animal = Animal.new
      #=> #<Animal:0x00005bfb0c55ae98>  
    animal.dog
      #=> "woof"
    Animal.dog
      #=> NoMethodError (undefined method `dog' for Animal:Class)
    

    要定义一个接收者是一个类的方法(一个类方法),我们在类的单例类上定义该方法。对于 Animal 类,我们可以编写以下任一代码。

    class Animal
      class << Animal
        def pig(n)
          "#{n} little pigs"
        end
      end
    end
    
    Animal.methods(false)
      #=> [:pig] 
    Animal.pig(3)
      #=> "3 little pigs"
    

    Animal.define_singleton_method(:pig) do |n|
      "#{n} little pigs"
    end
    
    Animal.methods(false)
      #=> [:pig] 
    Animal.pig(3)
      #=> "3 little pigs"
    

    class &lt; Animal1 行将范围更改为 Animal 的单例类,导致 self 的值也更改为该类。

    那么这和问题有什么关系,也就是定义方法def self.my_method ...呢?简短的回答是没有必要以这种方式定义方法。请耐心等待——我会解决的。

    注意Animal的单例类上定义的方法pigAnimal的子类的单例类继承:

    class Swine < Animal
    end
    
    Swine.instance_methods & [:dog, :cat]
      #=> [:dog, :cat]
    Swine.methods & [:pig]
      #=> [:pig] 
    

    我们还可以在许多独特的对象上定义方法。考虑animalAnimal 的一个实例:

    animal.define_singleton_method(:rodent) do |n|
      "I'm rodent ##{n}"
    end
    
    animal.rodent(3241)
      #=> "I'm rodent #3241"
    

    animal 是此方法将响应的唯一接收者:

    Animal.new.rodent(55)
      #=> #NoMethodError (undefined method `rodent' for
      #     #<Animal:0x00005bfb0c530670>)
    

    实际上,我们可以在每个具有单例类的对象上定义方法,也就是大多数对象:

    str = "cow"
    str.define_singleton_method(:greeting) { "moo" }
    str.greeting
      #=> "moo"
    
    arr = [1,2]
    arr.define_singleton_method(:greeting) { "I'm an array" }
    arr.greeting
      #=> "I'm an array"
    
    module M; end
    M.define_singleton_method(:greeting) { "I'm a module" }
    M.greeting
      #=> "I'm a module"
    
    piggy = Animal.method(:pig)
      #=> #<Method: Animal.pig> 
    piggy.define_singleton_method(:greeting) { 
      "I'm a singleton method" }
    piggy.greeting
      #=> "I'm a singleton method"
    

    我们可以对所有具有单例类的 Ruby 对象执行此操作。这包括除了具有立即值(按值传递的对象)之外的所有对象,包括niltruefalse、整数、符号和一些浮点数。此外,已冻结的对象(例如,"Hi".freeze)没有单例类。

    假设现在我们写

    class Animal
      def Animal.pig(n)
        "#{n} little pigs"
      end
    end
    

    或(相同的东西)

    class Animal
      def self.pig(n)
        "#{n} little pigs"
      end
    end
    

    (我们终于到了!)

    这种定义方法的新方法是什么?实际上,这只是在Animal 的单例类上定义方法的一种简写方式。仅将其视为syntactic sugar。就像在方法定义的第一行写2 + 2 指示Ruby 执行2.+(2)Animal.self. 只是指示Ruby 执行以下操作。

    class Animal
      class << self
        def pig(n)
          "#{n} little pigs"
        end
      end
    end
    

    也就是说,完全不需要写def Animal.my_method...def self.my_method...来创建类方法;它只是为了方便 Ruby 编码人员。

    1 该行一般写成class &lt;&lt; self,当行执行时self 等于Animal 是可以接受的。如果类被重命名,使用&lt;&lt; self 只是一种方便。

    【讨论】:

      【解决方案4】:

      在一个类中,如果你在 self 上定义一个方法,它会在类上定义它。例如

      class User
        def self.greeting
          "Hello User"
        end
      end
      

      然后就可以调用了

      User.greeting
      # => "Hello User"
      

      如果你定义了一个没有 self 的方法,那么这个方法就是为那个类的对象定义的。例如:

      class User
        def greeting
          "Hello user"
        end
      end
      

      然后你必须创建一个新的用户对象来调用greeting

      user = User.new
      user.greeting
      # => "Hello user"
      

      【讨论】:

      • 我知道这一点,但我不知道为什么会这样。我想了解 ruby​​ 如何处理上下文和自我并得出这种差异
      【解决方案5】:

      self 在定义类方法时是必需的,而没有 self 关键字它将被视为实例方法。 class &lt;&lt; self; end; 块也用于定义类方法的数量。

      这些方法是在你的类加载时加载的,以上将有助于区分它们。

      这就像与现实生活中的例子相关。

      您的电脑上有多个账户提供了认证,有正确密码的有效人可以进入里面的账户。进入内部帐户后,您无需提及您的身份!它始终将您视为进入的人。

      【讨论】:

        【解决方案6】:

        首先,重要的是要了解在Ruby 中没有类方法单例方法 这样的东西。方法只有一种:实例方法。

        真正的问题是:module中定义的方法是什么?

        [“单例方法”和“类方法”是我们用于“在单例类中定义的方法”和“在类为Class的对象的单例类中定义的方法”的简写名称"。]

        Ruby 中有两种形式的方法定义:

        def some_expression.some_name; end
        

        def some_name; end
        

        第一个将首先评估表达式some_expression,然后在结果对象的单例类中定义一个名为some_name的方法。

        第二个将在所谓的默认定义中定义一个名为some_name 的方法。默认的definee是通常最接近词法封闭的模块定义。例如,在这种情况下:

        module Foo
          module Bar
            def foo
              def bar; end
            end
          end
        end
        
        include Foo::Bar
        
        foo
        # At this point, `bar` will be defined, but where?
        

        这里,bar 将被定义为Foo::Bar 的实例方法,因为bar 是最接近的词法封闭模块定义。还有一个更接近的词法定义,即foo的方法定义,但这不是模块定义。

        有点令人费解:

        Foo = Class.new do
          def foo; end
        end
        

        这会将foo 定义为Foo 的实例方法,尽管技术上这不是一个模块定义,它“只是”一个块。但是,这就是我在上面写“通常”的原因:某些方法可以更改默认定义,Class::new 是其中一种方法(类似于Object#instance_eval 如何更改self 的值)。

        另一个例子:

        def foo; end
        

        在这种情况下,没有词法封闭模块定义。在顶层,默认定义是Object,但有一点不同:默认可见性也是private

        所有这些都在 yugui 的精彩博文 Three implicit contexts in Ruby 中有更详细的解释。

        现在,唯一缺少的是:表达式self 在模块定义中的计算结果是什么?好吧,如果你愿意,你可以测试一下:

        class Foo
          p self
        end
        

        答案是在模块定义中,self 计算为正在定义的模块。 (有点道理,是吗?)

        因此,在

        class Foo
          def self.foo; end
        end
        

        foo 定义在 Foo 的单例类中。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-05-27
          • 1970-01-01
          • 2013-06-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-07-04
          相关资源
          最近更新 更多