在test1.rb中有这段代码
my_var = 42
def my_func()
42
end
class MyCLS
attr_accessor :prop
def initialize()
@prop = 42
end
end
然后在解释器中我需要它在irb
> require './test1.rb'
> MyCLS.new().prop
=> 42
> my_func()
=> 42
> my_var
NameError: undefined local variable or method `my_var' for main:Object
我很困惑,ruby似乎很高兴用类和函数污染全局命名空间,但拒绝对my_var做同样的事情?我想这是为了避免名称冲突和错误。但问题只是部分解决,因为它仍然存在于类和函数中。也许只是不太容易发生?
现在想象一下第二个文件test2.rb
def my_func()
43
end
class MyCLS
attr_accessor :prop
def initialize()
@prop = 43
end
end
然后执行它
> require './test1.rb'
> require './test2.rb'
> MyCLS.new().prop
=> 43
> my_func()
=> 43
以前的全局变量MyCLS和my_func被静默覆盖是否正常?这是不是很有可能破坏软件,因为宝石决定在某处添加/重命名一个类或一个函数?所有这一切似乎都非常脆弱和危险。
我知道模块,我尝试了它们但收效甚微(尴尬,再一次是全局变量)
有没有办法防止这种情况或减轻看起来像语言设计的缺陷?
编辑:另一个例子
# test1.rb
def my_func()
42
end
# test2.rb
puts my_func()
# test3.rb
class Example
require './test1.rb'
end
class AnotherExample
require './test2.rb'
end
# command line
$ ruby test3.rb
42
答案 0 :(得分:4)
Ruby中的常量(以大写字母开头的东西)总是在Object
上创建为常量,除非它们明确是另一个常量的成员(即module Foo::Bar
在Foo
下创建一个模块{1}}常量,它本身在Object
常数之下。
此外,还有一个名为" main"的特殊顶级对象实例。在顶级定义的任何方法都在Object上定义为私有方法,因此可以从main
访问。
当你require
文件时,机制是:
main
。始终遵守这些规则;您无法在文件中定义顶级方法,然后通过巧妙地放置require
语句将该文件包含到命名空间中。解析该文件后,Ruby会找到一个顶级方法,并使用它扩展main
,而不考虑调用require
的位置。
如果你想要一个混合到另一个类中的方法,那么你通常会将它放入一个模块中,然后将该模块混合到你的类中。
# test1.rb
module Bar
def my_func
42
end
end
# test2.rb
require 'test1'
class Foo
include Bar
end
my_func => # NameError: undefined local variable or method `my_func' for main:Object
Foo.new.my_func # => 42
在Ruby中,预计每个文件将完全命名它要公开的常量和方法。你几乎不会在大多数真正的Ruby项目中编写顶级方法;因为有人需要明确地进入你的命名空间来覆盖一些东西,所以不必担心会无意中覆盖这些东西。
如果要在不扩展主对象的情况下执行文件,则可以将Kernel#load与wrap
参数一起使用,该参数将负载包装在匿名模块中(但使其内部不可访问,除非你在那个文件中做了一些事情来暴露那个文件中的方法和常量):
load "test1", true
MyCLS # => NameError: uninitialized constant MyCLS
您可以通过自定义加载程序获得此范围内的加载:
# test1.rb
def foo
42
end
# test2.rb
def relative_load(file)
Module.new.tap {|m| m.module_eval open(file).read }
end
class Foo
include relative_load("test1.rb")
end
Foo.new.foo # => 42
foo # => NameError: undefined local variable or method `foo' for main:Object
顺便说一下,在你的第一个例子中,MyCLS类没有被覆盖;它与现有的MyCLS类合并。因为两者都声明initialize
,后者声明优先。例如:
# test1.rb
class MyCLS
attr_accessor :prop
# This definition will get thrown away when we overwrite it from test2.
# It is test2's responsibility to make sure that behavior is preserved;
# this can be done with reimplementation, or by saving a copy of this
# method with `alias` or similar and invoking it.
def initialize(prop)
@prop = prop
end
end
# test2.rb
class MyCLS
attr_accessor :another_prop
def initialize(prop, another_prop)
@prop = prop
@another_prop = prop
end
end
# test3.rb
require 'test1'
c = MyCLS.new(1, 2) # => ArgumentError: wrong number of arguments (2 for 1)
c = MyCLS.new(1)
c.prop => 1
c.another_prop => # => NoMethodError: undefined method `another_prop'
require 'test2'
c = MyCLS.new(1) # => ArgumentError: wrong number of arguments (1 for 2)
c = MyCLS.new(1, 2)
c.prop => 1
c.another_prop => 2
答案 1 :(得分:-3)
由于您正在使用全局命名空间,我将冒险尝试使用JavaScript背景深入Ruby,如果我错了,请纠正我。
Ruby不会污染全局命名空间,因为当您需要test_1.rb或test_2.rb等文件时,它们的范围将限制在您需要它们的位置。例如,如果您在名为Example的类中需要“test_1”,那么如果您要在名为AnotherExample的类中要求“test_2”,则该文件无法覆盖:
Class Example
require 'test_1'
end
Class AnotherExample
require 'test_2'
end
您的方法会被覆盖,因为您需要在同一范围内的两个文件,而这些文件在较大的应用程序的上下文中不会执行。命名空间可以阻止您覆盖类似命名的变量,方法等。
my_var是一个局部变量,其上下文绑定到test_1.rb。因此,其范围仅限于test_1.rb。