【问题标题】:Why are symbols in Ruby not thought of as a type of variable?为什么 Ruby 中的符号不​​被视为一种变量?
【发布时间】:2012-11-30 19:39:20
【问题描述】:

编程和 Ruby 新手,我希望这个关于符号的问题符合要求。我知道 Ruby 中的符号(例如,:book:price)作为哈希键特别有用,并且可以全方位地做一个轻量级的、特定的字符串可以做的事情的子集。

但是,我在一方面对符号感到困惑。具体来说,当它们用于attr_accessor 类型的方法时,它们的行为似乎更像一个变量。例如,attr_reader :book, :price

如果它们在该用法中是变量,这有点令人费解,因为它们通常不在变量类型中列出(如 $global、@instance、local、@@class,有时还有 CONSTANT,变量类型)何时描述变量类型。

如果以这种方式使用符号是变量,那么它们的作用域应该是什么?还是在这种情况下它们仍然以某种方式轻量级字符串? (或者更广泛地说,符号、字符串和变量是否都具有类似鸭子的基本性质?)提前感谢您的见解和建议。

【问题讨论】:

标签: ruby variables symbols accessor


【解决方案1】:

Ruby 的attr_accessorattr_readerattr_writer 只是避免编写一些重复代码的简写方式。以下问题扩展了这些工作原理:Why use Ruby's attr_accessor, attr_reader and attr_writer?

不要将attr_reader :book 视为一个变量,只需将其视为使用符号指定的属性的名称

【讨论】:

  • 谢谢!根据我看到的链接和其他示例,通常使用访问器,符号指向实例变量。 (帮助解决我上面的“范围”问题,即使符号不是变量而只是指向变量。)
  • 我不知道我会走那么远——符号本身并不“指向”任何特别的东西。它们只是一种指定某物名称的方法,而不使用字符串。在这种情况下,由 attr_accessor 等类方法将符号解释为表示实例变量的名称。
【解决方案2】:

它们不是变量,因为它们不保存值,它们是不可变的。事情就是价值本身。它类似于数字。您不能设置1 的值。 1 = 2 不起作用。

【讨论】:

  • 有道理,谢谢!伟大的,简单的例子。它们拥有一个价值,一成不变,听起来像。
【解决方案3】:

符号不是变量,而是一种文字值,如数字和带引号的字符串。值得注意的是,符号用于表示 Ruby 运行时中的变量和其他命名值。因此,当 Ruby 解释器看到名称 foo 用作变量或方法名称时,它在运行时值的哈希中查找的是符号 :foo,而不是字符串 "foo"。事实上,这是编程语言术语中“符号”一词的原始用法。变量、函数、常量、方法等被称为存储在编译器或解释器的“符号表”中。

几乎任何时候你在 Ruby 中传递一些东西的名字,你都会使用一个符号。如果你使用method_missing 作为一个包罗万象的方法来在你的对象类上实现任意方法,一个符号就是它作为参数接收的,告诉它实际调用的方法的名称。如果你用.methods.instance_variables 检查一个对象,你得到的是一个符号数组。以此类推。

【讨论】:

  • 谢谢!了解编译器在符号中的思考方式会有所帮助!
【解决方案4】:

attr_accessor等都是属于类Class的方法。他们期望符号作为参数。如果需要,您可以编写自己的使用字符串的attr_ 版本。它只是一个红宝石成语。这是attr_acessor 的示例,它存储了我为家庭作业所做的attr_accessor 的所有先前值。

class Class
  def attr_accessor_with_history(attr_name)
    attr_name = attr_name.to_s   # make sure it's a string
    attr_reader attr_name        # create the attribute's getter
    attr_reader attr_name+"_history" # create bar_history getter
    class_eval %Q"
      def #{attr_name}=(value)
        if !defined? @#{attr_name}_history 
          @#{attr_name}_history = [nil]
        end
        @#{attr_name} = value
        @#{attr_name}_history << value
      end
    "
  end
end

【讨论】:

  • 谢谢!很高兴知道您可以更改班级,尽管听起来很危险!我看到你的头像好像在给猴子打补丁……
  • 事实上,attr_reader 及其同类已经支持字符串参数。不过,符号看起来更酷 :-)
【解决方案5】:

访问器方法中使用的符号不是变量。它们只是代表变量的名称。变量包含一些引用,因此您不能在定义访问器方法时使用变量本身。例如,假设您想在变量@foo 的值为"bar" 的上下文中定义一个访问器方法。如果 Ruby 的语法是这样的会发生什么:

attr_accessor @foo

这与写作没有什么不同:

attr_accessor "bar"

您无法访问您感兴趣的名称@foo。因此,必须将此类构造设计为在元级别引用变量名称。符号就是因为这个原因。它们本身不是变量。它们代表变量的名称。

与访问器方法相关的变量是实例变量。

【讨论】:

  • 谢谢,很有帮助!使用实例变量过于具体,Ruby 会将其解释为该变量的当前值,因此通常使用符号来表示该变量。
  • 所有答案都很有帮助,但你的答案对我来说是最清楚的,并且在解决“为什么”方面做得最好(即对符号的需求——不仅仅是什么或如何)。非常感谢!
  • 请注意也有我的问题的读者,请务必在下面查看 BernardK 的回答,并附上有用的声明:“解决您的‘如果它们是变量’和‘范围’问题,更简单的回答是访问器符号与实例变量无关,即使这听起来很反传统。它们不指向实例变量。访问器只定义 getter 和 setter 方法。实际上,访问方法符号定义了方法,如下面的海报输出 2) 所示,它是 nil 而不是 NoMethodError。
【解决方案6】:

要解决您的“如果它们是变量是真的”和“范围”问题,回答访问器符号与实例变量无关,即使这听起来是反传统的,也会更简单。它们不指向实例变量。访问器只定义 getter 和 setter 方法。在 Object#instance_variables 下,Pickaxe(*) 表示:请注意,简单地定义访问器不会创建相应的实例变量。

在 Ruby 中,变量在您为其赋值之前并不存在。下面的代码演示了这一点。

class MyClass
    attr_accessor :name
    attr_reader   :book
end

obj = MyClass.new # line 6
print '1) obj.instance_variables : '; p obj.instance_variables
print '2) obj.name : '; p obj.name

obj.name = 'xyz'
print '3) obj.instance_variables : '; p obj.instance_variables
print '4) obj.name : '; p obj.name
print '5) obj.book : '; p obj.book

class MyClass
    def initialize(p_book)
        @book = p_book
    end
end

obj = MyClass.new('The Pickaxe') # line 21
print '6) [new] obj.book : '; p obj.book

class MyClass
    method_name = 'title'
    attr_accessor method_name # line 26
end

obj.title = 'Programming Ruby'
print '7) obj.instance_variables : '; p obj.instance_variables
print '8) obj.title : '; p obj.title

输出:

$ ruby -w t.rb 
1) obj.instance_variables : []
2) obj.name : nil
3) obj.instance_variables : ["@name"]
4) obj.name : "xyz"
5) obj.book : nil
6) [new] obj.book : "The Pickaxe"
7) obj.instance_variables : ["@title", "@book"]
8) obj.title : "Programming Ruby"

1) 空数组:访问器没有定义实例变量
2) 询问实例变量 @name 回答 nil :它不存在
3) 赋值已创建实例变量。
请注意,name = 是一种语法糖,用于将 setter 用作带有参数的普通方法:obj.name=('xyz')
4)getter方法name回答@name的值
5) getter 方法book 回答nil,因为实例变量@book 不存在。定义访问器attr_reader :book 没有定义对应的实例变量
6)getter方法book回答initialize中赋值的值,由new在第21行调用。实例变量@book已由@book = p_book创建
第 26 行)我一直认为访问器只接受符号。我发现变量是可能的,但兴趣有限。
7)setter方法title=创建了@title。这也表明实例变量属于单个对象。我们经常相信它们属于类的所有实例,就像在其他语言中一样。在这种情况下,@name 仅属于在第 6 行创建的对象。
8)getter方法title回答@title的值

class MyClass
    def title # line 34
        @book + ' (cheating !)'
    end
end

print '9) obj.title : '; p obj.title  

输出:

t.rb:34: warning: method redefined; discarding old title  
9) obj.title : "The Pickaxe (cheating !)"  

9) 当然,访问器符号和相应的实例变量之间存在紧密的关联,因为在幕后,Ruby 创建了引用同名实例变量的方法。您可以定义自己的吸气剂和作弊方法。


请注意,除了类变量(@@var,有些人不喜欢它们像全局变量一样丑陋),类也可以有实例变量。我称它们为类实例变量 :)。
class MyClass :Ruby 分配类 Class 的一个新实例,定义一个常量 MyClass,并将新实例分配给该常量。因此 MyClass 是一个普通对象(Class 的实例),因此可以有实例变量。

if RUBY_VERSION[0..2] == '1.8'
    class Object
        def singleton_class
            class << self
                self
            end
        end
    end
end

class MyClass
    singleton_class.instance_eval do
        attr_accessor :counter
    end
    @counter = 0

    def initialize(p_book)
        @book = p_book
        self.class.counter += 1
    end
end

print '10) MyClass.singleton_methods  : '; p MyClass.singleton_methods
print '11) MyClass.instance_variables : '; p MyClass.instance_variables
obj = MyClass.new('Ruby')
print '12) [new] obj.book ', MyClass.counter, ': '; p obj.book
obj = MyClass.new('Metaprogramming')
print '13) [new] obj.book ', MyClass.counter, ': '; p obj.book  

输出:

t.rb:55: warning: method redefined; discarding old initialize
10) MyClass.singleton_methods  : ["counter", "counter="]
11) MyClass.instance_variables : ["@counter"]
12) [new] obj.book 1: "Ruby"
13) [new] obj.book 2: "Metaprogramming"  

更多关于单例方法的信息:What does def `self.function` name mean?

(*)http://pragprog.com/book/ruby3/programming-ruby-1-9

【讨论】:

  • 谢谢!啊哈,访问器方法定义了方法,而不是实例变量,正如您在第一个代码块中很好地演示的那样。 (并且,您可能会导致重新定义/重命名方法。)我从您的最后两个代码块中看到,您可以制作一次性的单对象方法(也称为单例方法),并且这些方法也可以采用访问器方法。如果大声朗读,您将如何阅读class &lt;&lt; self?可能是这样的:“来自class,使用以下一次性/特殊指令实例化单例对象self”? Anyhoo,再次感谢您提供清晰的示例代码!
【解决方案7】:

(回答您的评论)

dog = 'dog'String.new("dog")

dog = String.new之后,实例dog的字段类指向String类。

class << dog
    puts "inside #{self}" #=> inside #<Class:#<String:0x007fb38a83a820>>
    def bark
        puts 'woof'
    end
end
dog.bark #=> "woof" 
p dog.singleton_methods #=> ["bark"]

使用class &lt;&lt; dogdef dog.bark,Ruby 创建了一个匿名类,实例dog 的字段类现在指向这个匿名类,并从那里指向String。在此上下文中使用 def 或 define_method 定义的方法进入匿名类的方法表。

Ruby 1.9.2 引入了 Object#singleton_class。 [The Pickaxe] 返回 obj 的单例类,必要时创建一个。 (我加)相当于class &lt;&lt; self; self end

Ruby 编程语言(O'Reiily)简单地说:要打开对象 o 的 eigenclass [singleton class],使用 class

所以我不知道如何大声朗读。我读到有些人更喜欢o &gt;&gt; class。直到最近我才发现如何弄清楚这个奇怪的表达是什么意思。我发音:从 o 到它的匿名类。

class << MyClass
    def dog
        puts 'dog as class method'
    end
end
MyClass.dog #=> dog as class method

对于一个类也是如此。对于class MyClass,MyClass 作为 Class 的实例,是一个带有指向其类 Class 的指针的对象。使用def MyClass.some_methodclass &lt;&lt; MyClass,Ruby 创建了一个匿名类,插入到 MyClass 和 Class 之间,类方法进入其中。

可能类似于:“从类中实例化单例对象自身

“从类/对象”到匿名单例类/特征类/元类是可以的。 但是我们不实例化 self. Self(在 Smaltalk 中,在 C++/Java 中为 this)是一种保留字,用于指定消息的接收者。 dog.bark:在OO语言中我们说消息吠叫发送给对象狗。在方法bark 内部,self 将被设置为dog,这样我们就可以引用dog。这在

中更明显
o1 = MyClass.new; o2 = MyClass.new
o1.some_method; o2.some_method

some_method 必须能够以通用方式引用接收者,是 o1 还是 o2,这就是 self 的用途。

【讨论】:

  • 谢谢!单例类有趣的东西。我也喜欢这个主题的链接:devalot.com/articles/2008/09/ruby-singleton.
  • 另外添加:我喜欢你为class &lt;&lt; MyClass 的符号建议的简单语言表达——“从课堂,转到它的匿名课堂”。 (我发现发现给定符号的发音方法非​​常有帮助!)
  • @allenemilyh 谢谢你的链接,很好的解释,我已经收藏了。
【解决方案8】:

酷,我想你现在已经理解它们了。 但是为什么它们如此重要?

Ruby 中的符号是不可变的,而字符串是可变的。 你觉得不错不错,那又怎样?

假设您有一个字符串数组,如下所示:

    [ "a", "b", "a", "b", "a", "b", "c" ]

对于您创建的每个新字符串,ruby 将创建一个包含“a”值的字符串/对象,并且由于字符串是可变的,ruby 会为每个字符串分配不同的 id。 如果您要改用符号:

[ :a, :b, :a, :b, :a, :b, :c ]

Ruby 现在将指向这些符号,并且只会创建一次。

让我们做一些基准测试:

require 'benchmark'

Benchmark.bm do |x|
  x.report("Symbols") do
    a = :a
    1000_000.times do
      b = :a
    end
  end

  x.report("Strings") do
    a = "a"
    1000_000.times do
      b = "a"
    end
  end

end

ruby -w symbols.rb

Symbols  0.220000   0.000000   0.220000 (  0.215795)
Strings  0.460000   0.000000   0.460000 (  0.452653)

如果您想查看您已经创建的所有符号,您可以这样做:

Symbol.all_symbols

您也可以向他们发送消息询问他们的身份:

:a.object_id #=> 123
:a.object_id #=> 123

"a".id #=> 23323232
"a".id #=> some_blob_number

这又是因为 Ruby 中的字符串是可变的,而符号则不是。 Ruby 符号表示 Ruby 解释器中的名称。

这个视频真的帮助了我: Ruby's Symbols Explained

希望对大家有帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-10
    • 2019-03-05
    • 2020-07-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-19
    相关资源
    最近更新 更多