#ruby #yaml #psych
#ruby #yaml #psych
Вопрос:
Как мне десериализовать в Psych, чтобы вернуть существующий объект, такой как объект класса?
Чтобы выполнить сериализацию класса, я могу сделать
require "psych"
class Class
yaml_tag 'class'
def encode_with coder
coder.represent_scalar 'class', name
end
end
yaml_string = Psych.dump(String) # => "--- !<class> Stringn...n"
но если я попытаюсь сделать Psych.load
это, я получу анонимный класс, а не класс String.
Обычный метод десериализации — это Object#init_with(coder)
, но это только изменяет состояние существующего анонимного класса, тогда как я хочу класс String.
Psych::Visitors::ToRuby#visit_Psych_Nodes_Scalar(o)
есть случаи, когда вместо того, чтобы изменять существующие объекты с помощью init_with
, они в первую очередь удостоверяются, что создан правильный объект (например, вызывая Complex(o.value)
десериализацию комплексного числа), но я не думаю, что мне следует использовать этот метод monkeypatching.
Я обречен работать с низкоуровневым или среднеуровневым излучением, или я что-то упускаю?
Предыстория
Я опишу проект, зачем ему нужны классы и почему ему нужна (де)сериализация.
Проект
Малый собственный коллайдер предназначен для создания случайных задач для запуска Ruby. Первоначальной целью было посмотреть, возвращают ли разные реализации Ruby (например, Rubinius и JRuby) одинаковые результаты при выполнении одинаковых случайных задач, но я обнаружил, что это также полезно для обнаружения способов сегментирования Rubinius и YARV.
Каждая задача состоит из следующего:
receiver.send(method_name, *parameters, amp;block)
где receiver
— случайно выбранный объект, а method_name
—
имя случайно выбранного метода и *parameters
представляет собой массив
случайно выбранные объекты. amp;block
не очень случайный — это в основном
эквивалентно {|o| o.inspect}
.
Например, если бы получатель был «a», имя_метода было:casecmp, а параметры были [«b»], тогда вы бы вызывали
"a".send(:casecmp, "b") {|x| x.inspect}
что эквивалентно (поскольку блок не имеет значения)
"a".casecmp("b")
небольшой собственный коллайдер запускает этот код и регистрирует эти входные данные и
также возвращаемое значение. В этом примере большинство реализаций Ruby
возвращает -1, но на одном этапе Rubinius вернул 1. (Я подал это как
ошибка https://github.com/evanphx/rubinius/issues/518 и Rubinius
разработчики исправили ошибку)
Зачем ему нужны классы
Я хочу иметь возможность использовать объекты класса в моем маленьком собственном коллайдере. Обычно они являются получателем, но они также могут быть одним из параметров.
Например, я обнаружил, что один из способов segfault YARV — это выполнить
Thread.kill(nil)
В этом случае получателем является поток объектов класса, а параметрами являются
[ноль]. (Сообщение об ошибке:http://redmine.ruby-lang.org/issues/show/4367 )
Зачем ему нужна (де)сериализация
Небольшой собственный коллайдер нуждается в сериализации по нескольким причинам.
Во-первых, использование генератора случайных чисел для генерации серии случайных задач каждый раз непрактично. В JRuby есть другой встроенный генератор случайных чисел, поэтому даже при одинаковом начальном значении PRNG он будет давать разные задачи YARV. Вместо этого я создаю список случайных задач один раз (при первом запуске ruby bin / small_eigen_collider), при первоначальном запуске сериализую список задач в tasks.yml, а затем выполняю последующие запуски программы (с использованием разных реализаций Ruby) для чтения в этих задачах.yml-файл для получения списка задач.
Еще одна причина, по которой мне нужна сериализация, заключается в том, что я хочу иметь возможность редактировать список задач. Если у меня есть длинный список задач, которые приводят к ошибке сегментации, я хочу сократить список до минимума, необходимого для возникновения ошибки сегментации. Например, со следующей ошибкой https://github.com/evanphx/rubinius/issues/643 ,
ObjectSpace.undefine_finalizer(:symbol)
само по себе не вызывает ошибки сегментации, и также
Symbol.all_symbols.inspect
но если вы соедините их вместе, это сработает. Но я начал с
тысячи задач, и нужно было свести их обратно только к этим двум
задачи.
Имеет ли смысл в данном контексте десериализация, возвращающая существующие объекты класса, или вы думаете, что есть способ получше?
Комментарии:
1. Я не вижу в документах YAML (раздел yaml для ruby ), что (де) сериализация классов действительно возможна, только созданные экземпляры объектов и другие интересные вещи. Это означает: вы можете поместить объект в YAML, но не его определение класса, из которого был создан соответствующий объект. / КСТАТИ: я не могу понять идею, стоящую за вашей проблемой. Есть какие-нибудь примеры / кейсы, где это было бы полезно?
2. @asaaki: Когда я десериализуюstring, я просто хочу вернуть ссылку на существующий класс String. Мне не нужно сохранять и восстанавливать, какие методы есть в классе. Я отредактировал вопрос, чтобы дать (намного) больше информации.
3. Я не могу дать вам ответа, а только предложение: почему бы не использовать
Marshal.dump
/.load
? Поэтому попробуйте дополнительную упаковку, такую как:s = Psych.dump(Marshal.dump(String))
. Этап десериализации:Marshal.load(Psych.load(s))
— Я знаю, это выглядит дерьмово, но это работает!
Ответ №1:
Статус-кво моих текущих исследований:
Чтобы добиться желаемого поведения, вы можете использовать мой обходной путь, упомянутый выше.
Вот красиво оформленный пример кода:
string_yaml = Psych.dump(Marshal.dump(String))
# => "--- ! "\x04\bc\vString"n"
string_class = Marshal.load(Psych.load(string_yaml))
# => String
Ваш взлом с изменением класса, возможно, никогда не сработает, потому что реальная обработка классов не реализована в psych / yaml.
Вы можете воспользоваться этим репозиторием tenderlove / psych, который является автономной библиотекой.
(Gem: psych — чтобы загрузить его, используйте: gem 'psych'; require 'psych'
и выполните проверку с помощью Psych::VERSION
)
Как вы можете видеть в строке 249-251, обработка объектов с помощью анонимного класса Class не обрабатывается.
Вместо обезьяньего исправления класса class я рекомендую вам внести свой вклад в библиотеку Psych, расширив обработку этого класса.
Итак, на мой взгляд, конечный результат yaml должен быть чем-то вроде: "--- !ruby/class String"
После одной ночи размышлений об этом я могу сказать, что эта функция была бы действительно хороша!
Обновить
Найдено крошечное решение, которое, похоже, работает должным образом:
суть кода: gist.github.com/1012130 (с описательными комментариями)
Ответ №2:
Комментарии:
1. Приятно видеть — надеюсь, это был вопрос, который вызвал обновление. ; o)
2. К сожалению, нет, это не давало ему покоя в списке рассылки Seattle.rb. 😛