2015-02-05 45 views
0

在解釋有這段代碼在test1.rbRuby爲什麼會用類和函數污染全局名稱空間,但不能變量?

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 

我很困惑,紅寶石好像很樂意與污染類全局命名空間和函數,但拒絕對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 
+1

這不是一個缺陷,它是一個功能。 :)您正在查看「打開類」,這是您修改類定義(即「猴子修補」)的機制。定義被合併;但在你的情況下,你已經重新定義了一個方法,所以後者會覆蓋第一個方法。 – 2015-06-29 19:59:42

回答

-2

由於您使用的全局命名空間,我要創業,你是潛入紅寶石在JavaScript的背景下,糾正我,如果我錯了。

Ruby不會污染全局名稱空間,因爲當您需要諸如test_1.rb或test_2.rb之類的文件時,它們的作用域僅限於您需要它們的地方。舉例來說,如果你需要在一個名爲示例類「TEST_1」,即沒有達到,如果你需要在一個叫做另一個實例類「test_2」:因爲你需要內這兩個文件

Class Example 
    require 'test_1' 
end 

Class AnotherExample 
    require 'test_2' 
end 

你的方法會被覆蓋相同的範圍,您不會在較大的應用程序範圍內執行此操作。命名空間可以防止覆蓋類似命名的變量,方法等。

my_var是一個局部變量,其上下文綁定到test_1.rb。因此,其範圍僅限於test_1.rb內。在紅寶石

+0

@PeterSmith我想你例子(類沒有被資本化的方式) '類示例 需要「./test1.rb」 結束 類另一個實例 需要「./test2.rb」 結束 $ ruby test3.rb 42' 我仍然得到42,這意味着test1中的函數在test2中泄露並執行?我在這裏錯過了什麼嗎? – 2015-02-06 09:40:31

+0

這是錯誤的。無論您需要什麼地方,都需要將要求納入全球範圍。 – PSkocik 2015-06-29 18:27:37

3

常量(東西以大寫字母開頭)作爲常量總是創建上Object,除非它們被明確一個另一恆定(即,module Foo::Bar創建Foo恆定下一個模塊,它本身就是下的構件Object常數)。

此外,還有一個名爲「main」的特殊頂級Object實例。在頂層定義的任何方法都在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#loadwrap參數,它包裝的匿名模塊中的負載(但使得它的內部交通不便,除非你是做做的東西文件以公開該文件中的方法和常量):

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 
+0

請注意,將文件加載到包裝模塊中不會改變1.使用頂級範圍定義(例如, ':: Foo = 1'; 2.全局變量,例如'$ global = 1'。無論是否包裝,這2個構造都將被添加到頂級全局命名空間中。 – Kelvin 2016-07-08 17:18:29

相關問題