通过传递构造函数的 Ruby YAML 解析器 [英] Ruby YAML parser by passing constructor

查看:31
本文介绍了通过传递构造函数的 Ruby YAML 解析器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个应用程序,它从 YAML 文件中获取输入,将它们解析为对象,然后让它们做自己的事情.我现在遇到的唯一问题是 YAML 解析器似乎忽略了对象的初始化"方法.我指望构造函数来填充 YAML 文件缺少的任何实例变量以及默认值,以及在类变量中存储一些东西.下面是一个例子:

I am working on an application that takes input from a YAML file, parses them into objects, and let's them do their thing. The only problem I'm having now, is that the YAML parser seems to ignore the objects "initialize" method. I was counting on the constructor to fill in any instance variables the YAML file was lacking with defaults, as well as store some things in class variables. Here is an example:

class Test

    @@counter = 0

    def initialize(a,b)
        @a = a
        @b = b

        @a = 29 if @b == 3

        @@counter += 1
    end

    def self.how_many
        p @@counter
    end

    attr_accessor :a,:b

end

require 'YAML'

a = Test.new(2,3)
s = a.to_yaml
puts s
b = YAML::load(s)
puts b.a
puts b.b
Test.how_many

puts ""

c = Test.new(4,4)
c.b = 3
t = c.to_yaml
puts t
d = YAML::load(t)
puts d.a
puts d.b
Test.how_many

我希望上面的输出:

--- !ruby/object:Test
a: 29
b: 3
29
3
2

--- !ruby/object:Test
a: 4
b: 3
29
3
4

相反,我得到了:

--- !ruby/object:Test
a: 29
b: 3
29
3
1

--- !ruby/object:Test
a: 4
b: 3
4
3
2

我不明白它是如何在不使用它们定义的初始化方法的情况下制作这些对象的.我也想知道是否有任何方式强制解析器使用初始化方法.

I don't understand how it makes these objects without using their defined initialize method. I'm also wondering if there is anyway to force the parser to use the initialize method.

推荐答案

从 Yaml 反序列化一个对象不使用 initialize 方法,因为通常对象的实例变量(它是默认的 Yaml 序列化存储的内容)和 initialize 的参数.

Deserializing an object from Yaml doesn’t use the initialize method because in general there is no correspondance between the object’s instance variables (which is what the default Yaml serialization stores) and the parameters to initialize.

举个例子,考虑一个带有 initialize 的对象,它看起来像这样(没有其他实例变量):

As an example, consider an object with an initialize that looks like this (with no other instance variables):

def initialize(param_one, param_two)
  @a_variable = some_calculation(param_one, param_two)
end

现在当这个实例被反序列化时,Yaml 处理器有一个 @a_variable 的值,但是 initialize 方法需要 两个 参数,所以它不能调用它.即使实例变量的数量与 initialize 的参数数量相匹配,它们也不一定是对应的,即使它们做到了,处理器也不知道它们应该被传递给 代码>初始化.

Now when an instance of this is deserialized, the Yaml processor has a value for @a_variable, but the initialize method requires two parameters, so it can’t call it. Even if the number of instance variables matches the number of parameters to initialize it is not necessarily the case that they correspond, and even if they did the processor doesn’t know the order they shoud be passed to initialize.

将 Ruby 对象序列化和反序列化到 Yaml 的默认过程是在序列化期间写出所有实例变量(及其名称),然后在反序列化时分配类的新实例,并简单地在这个新实例上设置相同的实例变量实例.

The default process for serializing and deserializing a Ruby object to Yaml is to write out all instance variables (with their names) during serialization, then when deserializing allocate a new instance of the class and simply set the same instance variables on this new instance.

当然,有时您需要对这个过程进行更多控制.如果您使用的是 Psych Yaml 处理器(这是 Ruby 1.9.3 中的默认处理器),那么您应该实现 encode_with(用于序列化)或 init_with(用于反序列化)适当的方法.

Of course sometimes you need more control of this process. If you are using the Psych Yaml processor (which is the default in Ruby 1.9.3) then you should implement the encode_with (for serialisation) or or init_with (for deserialization) methods as appropriate.

对于序列化,Psych 将调用对象的 encode_with 方法(如果存在),传递一个 coder 对象.这个对象允许你指定对象在 Yaml 中的表示方式——通常你只是把它当作散列来对待.

For serialization, Psych will call the encode_with method of an object if it is present, passing a coder object. This object allows you to specify how the object should be represented in Yaml – normally you just treat it like a hash.

对于反序列化,Psych 将调用 init_with 方法(如果它存在于您的对象上),而不是使用上述默认过程,再次传递一个 coder 对象.这次 coder 将包含有关 Yaml 中对象表示的信息.

For deserialization, Psych will call the init_with method if it is present on your object instead of using the default procedure described above, again passing a coder object. This time the coder will contain the information about the objects representation in Yaml.

请注意,您不需要同时提供这两种方法,如果需要,您可以只提供其中一种.如果您同时提供两者,则您在 init_with 中传递的 coder 对象将与在该方法执行后传递给 encode_with 的对象基本相同运行.

Note you don’t need to provide both methods, you can just provide either one if you want. If you do provide both, the coder object you get passed in init_with will essentially be the same as the one passed to encode_with after that method has run.

举个例子,考虑一个对象,它有一些实例变量是从其他实例变量计算出来的(可能是为了避免大量计算的优化),但不应该被序列化到 Yaml.

As an example, consider an object that has some instance variables that are calculated from others (perhaps as an optimisation to avoid a large calculation), but shouldn’t be serialized to the Yaml.

class Foo

  def initialize(first, second)
    @first = first
    @second = second
    @calculated = expensive_calculation(@first, @second)
  end

  def encode_with(coder)
    # @calculated shouldn’t be serialized, so we just add the other two.
    # We could provide different names to use in the Yaml here if we
    # wanted (as long as the same names are used in init_with).
    coder['first'] = @first
    coder['second'] = @second
  end

  def init_with(coder)
    # The Yaml only contains values for @first and @second, we need to
    # recalculate @calculated so the object is valid.
    @first = coder['first']
    @second = coder['second']
    @calculated = expensive_calculation(@first, @second)
  end

  # The expensive calculation
  def expensive_calculation(a, b)
    ...
  end
end

当你将这个类的一个实例转储到 Yaml 时,它看起来像这样,没有 calculated 值:

When you dump an instance of this class to Yaml, it will look something like this, without the calculated value:

--- !ruby/object:Foo
first: 1
second: 2

当您将此 Yaml 加载回 Ruby 时,创建的对象将设置 @calculated 实例变量.

When you load this Yaml back into Ruby, the created object will have the @calculated instance variable set.

如果你希望你可以init_with 中调用 initialize,但我认为最好在初始化之间保持清晰的分离一个 new 类的实例,并从 Yaml 反序列化一个 现有 实例.我建议将公共逻辑提取到可以从两者调用的方法中,

If you wanted you could call initialize from within init_with, but I think it would be better to keep the a clear separation between initializing a new instance of the class, and deserializing an existing instance from Yaml. I would recommend extracting the common logic into methods that can be called from both instead,

这篇关于通过传递构造函数的 Ruby YAML 解析器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆