【问题标题】:How to access global method from object?如何从对象访问全局方法?
【发布时间】:2026-01-12 11:05:01
【问题描述】:

我有这个代码:

def login(user,pass)
end

class Bob
  def login(pass)
    login('bob',pass) #ERROR#
  end
end

login('hello','world')
bob = Bob.new
bob.login('world')

当我尝试从命令行执行代码时,我在注释为 #ERROR# 的行上收到错误数量的参数错误。我猜这是因为我没有成功访问全局 login() 函数吗?如何引用?

【问题讨论】:

标签: ruby


【解决方案1】:

您可以为此使用super。在顶层定义的方法神奇地变成了所有对象的私有方法。

class Bob def login(pass) super('Bob', pass) end end

【讨论】:

    【解决方案2】:

    如果你能解释你到底想做什么,也许有更好的方法。但是,如果你必须这样做,那么:

    def login(user, pass)
      puts 'global login'
      puts "user: #{user}, pass: #{pass}"
    end
    
    class Bob
      def login(pass)
        self.class.send(:login, 'bob',pass) #ERROR#
      end
    end
    
    login('hello','world')
    bob = Bob.new
    bob.login('world')
    
    #=> global login
    #=> user: hello, pass: world
    #=> global login
    #=> user: bob, pass: world
    

    【讨论】:

      【解决方案3】:

      在 Ruby 中,我们有一个层次结构,其中首先调用类方法而不是全局范围方法。

      这些全局范围的方法属于Object 类,但它们被声明为私有。

      直接访问可以使用send方法,但不推荐

      Object.send(:login, param1, param2)
      

      解决此问题的更好方法是使用模块。

      创建一个模块:

      login.rb

      module Login
        def login(user, pass)
        end
      end
      

      并将其包含在您的课程中:

      bob.rb

      require 'login'
      
      include Login
      
      class Bob
        login(pass)
          Login::login('bob', pass)
        end
      end
      
      bob = Bob.new
      bob.login('test')
      

      【讨论】:

        【解决方案4】:

        我真的是 ruby​​ 新手,所以这是一个入门级问题。

        简短的回答是:Bob 类中的 login() 实例方法隐藏了顶层 login() 方法。简单的解决方案是:更改其中一种方法的名称。

        以下是一些你应该尝试学习的东西:

        1) 在 ruby​​ 中,每个方法都使用左侧的对象调用,例如

        some_obj.login
        

        左侧的对象称为接收者

        2) 如果您没有明确指定接收者,例如

        login('bob',pass)  #No receiver is specified on the left hand side
        

        ...ruby 在左侧使用一个名为 self 的变量,例如:

        self.login('bob', pass)
        

        3) 在类内部定义的方法内,例如:

        class Bob
          def login(pass)
            #IN HERE
          end
        end
        

        ...self 等于调用该方法的对象。在您的情况下,您有以下代码:

        bob = Bob.new
        bob.login('world')
        

        所以 bob 是调用 login() 实例方法的对象,因此你有这个:

        class Bob
          def login(pass)
            #IN HERE, self is equal to bob
          end
        end
        

        因此,ruby 会这样做:

        class Bob
          def login(pass)
            #login('bob', pass) =>This line gets converted to this:
            self.login('bob',pass) #ERROR#
            #IN HERE, self is equal to bob
            #So ruby executes this:
            #bob.login('bob', pass)  #ERROR: too many arguments#
          end
        end
        

        像 Guilherme Carlos 建议的那样,解决您的问题的一种方法是使用模块——但您可以通过更简单的方式做到这一点:

        module MyAuthenticationMethods
          def login(user, pass)
            puts "user: #{user}, pass: #{pass}"
          end
        end
        
        class Bob
          def login(pass)
            MyAuthenticationMethods::login('bob',pass)
          end
        end
        

        然而,通常你把一个模块放在它自己的文件中,然后require它。模块解决您的问题的原因是模块名称以大写字母开头,这意味着它是一个常量——您可以从代码中的任何位置访问一个常量。

        4) 所有 def 都将自己附加到 当前类。当前类由self变量的值决定:如果self是一个类,那么当前类就是self的值,但是当self不是一个类时,那么当前类就是self的类。好的,让我们看看这些原则的实际应用:

        class Bob
          puts self  #=>Bob
        
          def login(pass)
            ...
          end
        end
        

        因为self是一个类,所以当前类等于self,def将自己附加到Bob类。

        顶层会发生什么?

        puts self  #=> main
        
        def login(user,pass)
        end
        

        经验丰富的红宝石师熟悉main;它是 ruby​​ 在顶层分配给 self 的对象,即在任何类或方法定义之外——你称之为 global。重要的一点是main 不是一个类。结果,顶层 login() def 将自己附加到 main 的类,即:

        puts self  #=>main
        puts self.class #=>Object
        
        def login(user,pass)
        end
        

        Brian Driscoll 提到 ruby​​ 没有全局作用域——但这并不重要,因为 def 创建了一个新的作用域来关闭外部作用域,因此在 def 之外存在的任何内容在def(常量除外)。

        您尝试做的事情通常是在 ruby​​ 中使用所谓的 blocks 完成的。块允许您将第二种方法传递给第一种方法,然后在第一种方法中您可以调用第二种方法。这是一个例子:

        class Bob
          def login(pass)
            yield('bob', pass)  #yield calls the block with the specified arguments
          end
        end
        
        
        bob = Bob.new
        
        bob.login('my password') do |username, pword|
          puts username, pword
        end
        

        该代码中的块是这部分:

        do |username, pword|
            puts username, pword
        end
        

        ...看起来有点像方法定义——但没有名称。那是您的* login() 方法的替代品。 Ruby 自动将块传递给块之前指定的方法:

          This method!
             |
             V
        bob.login('my password')
        

        在 login() 方法中,您使用单词yield 调用块——就好像yield 是方法的名称。

        注意实际上是ruby的sytnax,即在方法调用之后写一个块,导致第二个方法被传递给第一个方法,在第一个方法中你可以通过简单地写@987654340来调用传入的方法@。

        【讨论】:

          【解决方案5】:

          我们来看看:

          def login(user, pass)
            puts "#{user}'s #{pass}"
          end
          
          class Bob
            def login(pass)
              greeting
              login('Bob',pass) #ERROR#
            end
            def greeting
              puts "hi"
            end
          end
          

          当我们运行时:

          bob = Bob.new
          bob.login('world')
          

          我们得到:

          hi
          ArgumentError: wrong number of arguments (2 for 1)
          

          并且您知道引发异常的原因。

          我们通过将方法连同任何参数一起发送给接收者来执行方法。最初,我们将带有参数'world' 的方法login 发送给接收者bob。但是等等,在login 中,没有指定接收者。接收者要么是明确的(例如,在类之外,bob.greeting),要么是未指定的,在这种情况下,它们被假定为self。这里selfbob,所以login方法中的greeting等价于方法内的self.greeting或类外的bob.greeting

          greetinglogin执行后,我们要执行类外的方法login。因此,我们必须使用显式接收器。但它的类是什么? (我们知道它有一个!)加载此代码后,在 IRB 中尝试:

          method(:login).owner #=> Object
          

          我们在“*”运行它:

          self       #=> main
          self.class #=> Object
          

          因此可以在我们程序的任何地方调用它。唯一的复杂情况是当我们在一个具有同名实例方法的类中时。

          好的,所以 login 在类 Bob 之外是类 Object 的方法。是类方法还是实例方法?

          Object.methods.include?(:login)          #=> false
          Object.instance_methods.include?(:login) #=> false
          

          没有!嗯。那么它必须是一个私有方法:

          Object.private_methods.include?(:login)          #=> true
          Object.private_instance_methods.include?(:login) #=> true
          

          是的,事实上,它既是私有类方法,又是私有实例方法(属于Object 类)。这有点令人困惑,但为什么两者兼而有之以及为什么它是私有的,答案在于 Ruby 的对象模型,这不是几句话就能解释的,所以必须再等一天。

          我们可以使用Object#send的方法 调用私有方法,这就是我们要做的。让我们使用私有类方法,所以接收者将是Object

          def login(user,pass)
            puts "#{user}'s #{pass}"
          end
          
          class Bob
            def login(pass)
              greeting
              Object.send(:login, "Bob", pass)
            end
            def greeting
              puts "hi"
            end
          end
          
          bob = Bob.new
          bob.login('world')
            # hi
            # Bob's world
          

          万岁!

          额外功劳:由于login既是(私有)类方法又是实例方法,我们应该可以在操作行中插入new

          Object.new.send(:login, "Bob", pass)
          

          并得到相同的结果。我们要不要?如果你有兴趣,我会告诉你。

          【讨论】:

          • 嗨,Cary,如果它不是私有方法,我们可以调用它:Object.login('Bob',pass) 这有点令人困惑。您使用 private_instance_methods() 来表明 login() 是 Object 的实例方法,但随后您调用 login() 就像 Object 的类方法一样。你真的想进入继承结构顶部的循环吗?
          • 7stud,好点。谢谢!我所拥有的不仅令人困惑,而且是错误的。我已经修复了。如果仍有任何错误或遗漏,请告诉我。