【问题标题】:Class instance with names defined by string名称由字符串定义的类实例
【发布时间】:2019-10-16 03:22:51
【问题描述】:

在'pure ruby​​'(不是rails)中,给定一个类:

class Person
end

...和一个字符串数组:

people_names = ['John', 'Jane', 'Barbara', 'Bob']

如何实例化 Person 类,每个实例变量都命名为数组中的元素之一?

John = Person.new
Jane = Person.new
Barbara = Person.new
Bob = Person.new

【问题讨论】:

  • 值得一提的是,使用大写字母作为变量名会使它们成为常量。不确定这是否是您的目标
  • 那个 dup 目标是关于局部变量的。这个问题实际上是关于常量的——它的措辞真的很糟糕。
  • 我没有看到任何迹象表明这个问题是关于示例代码以外的常量,它从来没有明确说明它们是常量,我假设作者可能不知道在 Ruby 中如果第一个字母是大写,则变量是常量。
  • 他也在询问实例变量,但没有@sigil。其实我不知道该怎么做。 @BrandonBuck
  • 是的,这就是我的意思,确实没有足够的信息来确定他想要什么。

标签: ruby


【解决方案1】:

很不清楚你在这里真正想要什么,因为在 Ruby 中以大写字母开头的标识符是一个常量。

John = Person.new
Jane = Person.new
Barbara = Person.new
Bob = Person.new

您可以使用Module#const_set 动态分配常量。

module MyModule
  ['John', 'Jane', 'Barbara', 'Bob'].each do |name|
    const_set(name, Person.new)
  end
end

# this imports the constants into Main which is the global scope
include MyModule

John
=> #<Person:0x007f973586a618>

另一方面,实例变量使用@sigil。您可以使用instance_variable_set 动态分配实例变量:

['John', 'Jane', 'Barbara', 'Bob'].map(&:downcase).each do |name|
  instance_variable_set("@#{name}", Person.new)
end

@john       
# => #<Person:0x007f9734089530>

虽然您可以声明一个名为 @John 的实例变量,但它违反了语言的约定。

局部变量实际上不能动态定义。您只能通过evalbinding.local_variable_set 更改现有变量。

def foo
  a = 1
  bind = binding
  bind.local_variable_set(:a, 2) # set existing local variable `a'
  bind.local_variable_set(:b, 3) # create new local variable `b'
                                 # `b' exists only in binding

  p bind.local_variable_get(:a)  #=> 2
  p bind.local_variable_get(:b)  #=> 3
  p a                            #=> 2
  p b                            #=> NameError
end

【讨论】:

    【解决方案2】:

    我确信 Ruby 有一些方法可以让您动态定义常量,但我不会费心去查找它,因为这几乎 100% 感觉像是您不想做的事情。似乎您想要某种方式将“名称”与类实例相关联。这正是Hash 的用途。

    people_names = ['John', 'Jane', 'Barbara', 'Bob']
    people = people_names.each_with_object({}) do |name, ppl|
      ppl[name] = Person.new(name)
    end
    people['John'].name # => 'John'
    people['Jane'].name # => 'Jane'
    

    为什么我说你要求的可能不是你想要的?因为在专业开发中通常不赞成使用元编程来动态创建和动态读取局部变量/常量/实例变量。对于您自己的项目,对于实验,当然可以。但是,对于作为团队一部分的任何项目,当您开始使用元编程功能来动态添加这些值并引用它们(可能是直接的,也可能是稍后间接引用)时,一切都很好,但是当您尝试弄清楚发生了什么时除非具有动态名称的数组是硬编码的,否则几乎永远无法弄清楚这些东西是从哪里定义/来自哪里的。如果它是硬编码的,为什么不能直接在代码中构建常量/变量/目标?这比动态执行要好得多。

    # this is a fake API to demonstrate
    # let's assume this is what you want
    PEOPLE_NAMES = ['John', 'Jane']
    PEOPLE_NAMES.each do |name|
      const_def(name, Person.new)
    end
    
    get_person('Jane').do_something # maps to const_get('Jane').do_something
    get_person(PEOPLE_NAMES[0]).do_something
    John.do_something
    

    如果你想要以上,你为什么不能这样做:

    John = Person.new
    Jane = Person.new
    
    John.do_something
    

    后者加载更清晰,仍然可以动态查找,但有一个硬​​编码的定义,可以在调试时轻松定位。

    这是我的建议和回答。我很确定你不想做你要求做的事情。 Hash 完全符合您的需求,它被大量用于此类目的并与之密切相关,我建议您尝试使其满足您的需求,然后尝试找出如何解决您特别希望得到的问题也是一个答案。

    编辑

    作为一个非常有趣的插件,您可以在这里使用Hash 做一些非常酷的动态内容,除非您碰巧隐藏了哈希的来源,否则不会导致大量混乱。但你可以这样做:

    people = Hash.new { |h, k| h[k] = Person.new(k) }
    # right now, people contains no actual people
    people['John'].name # => 'John'
    # now people contains one Person instance
    

    这很酷有两个原因 1)您不必有一个列表来生成散列,所以如果您在创建散列之后得到名称,那很好,您可以通过访问该用户名称来添加它们 2)懒惰,它只会使用你需要的内存。如果您使用所有四个人预加载哈希,然后仅从两个人访问数据,那么您浪费了未使用的 2 个 Person 实例所需的空间,所以这让您只使用所需的空间,否则为您提供所有相同的好处。

    【讨论】:

    • 作为一个插件,你使用的哈希变量可以是常量/实例变量/局部变量,基本上可以赋值给任何你想要的变量。它可以传递给函数、复制等等......它比一些定义动态数据的范围本地方法更通用。
    • 呃,Rails 使用了大量的动态实例/常量分配。但我想那是所谓的专业开发人员不赞成的。
    【解决方案3】:

    您当然可以这样做,尽管正如 Brandon 所说,这可能不是一个好主意。操作方法如下:

    people_names.each { |name| eval("#{name} = Person.new")}
    

    eval 接受一个作为参数传递的字符串并将其作为一行代码执行。因此,您使用 each 为数组的每个成员执行一次。

    您可能想在 Google 上搜索“eval”以查看任意数量的关于它为何邪恶的文章。其中许多都涉及元编程(eval 是元编程的一个例子),并提出了一些好的观点。我对元编程有一种比这更温和的方法(attr_accessor,毕竟也是元编程的一个例子,人们一直都在使用它),但你当然可以使用eval 编写一些非常复杂的代码。

    请注意,正如几位发帖人所观察到的,通过将字符串大写,您将它们定义为常量。您可以在 Ruby 中更改常量的值,但每次这样做都会收到警告。

    【讨论】:

    • eval 实际上不会定义新的局部变量。 eval('John = Person.new'); John 有效。但是eval('john = Person.new'); john 给出了一个 NameError。
    • @max 确实如此。那时到处都是坏主意。 :) 实际上,它会定义新的局部变量:eval("a = 7") 也可以。似乎它只是不允许您定义类实例?
    • 不,我很确定它没有声明新的实例变量。我猜你已经声明了a。这在binding.local_variable_get 的文档中进行了解释,在这方面它的工作原理与 eval 完全相同。
    • 我个人认为语言允许重新分配常量是错误的。如果它不是常量,那么“常量”的意义何在?我可以使用 const_set 之类的东西来支持常量的重新分配(带有警告),但我认为本机硬编码的重新分配应该是一个错误。
    • @max 你是对的!这就是我所做的。我对eval 有了更多了解。来自 Matz 的书中:“请注意,eval 在临时范围内评估其代码。eval 可以更改已经存在的实例变量的值。但它定义的任何新实例变量都是 eval 调用的本地变量并且不再存在当它返回时。(就好像被评估的代码在块的主体中​​运行 - 块的本地变量不存在于块之外。)“所以,不是一个好的解决方案。
    猜你喜欢
    • 2016-06-05
    • 2014-09-28
    • 1970-01-01
    • 2011-04-09
    • 2014-06-04
    • 1970-01-01
    • 2018-02-03
    • 1970-01-01
    相关资源
    最近更新 更多