class_eval против instance_eval

Есть ли разница в том, как class_eval & instance_eval работают, кроме def? Внутри class_eval блока def определяется метод самого класса (то есть метод экземпляра), а внутри instance_eval def метод определяет собственный класс класса (то есть метод класса). AFAIK все остальные функции работают одинаково в обоих случаях (например, define_method, attr_accessor, class << self; end, определение констант). Это правда?

Ответ: def, undef и alias имеют разные контексты для class_eval и instance_eval.


person Alexey    schedule 24.04.2012    source источник


Ответы (2)


Короче:

  • Object.instance_eval &block sets:
  • Object.class_eval &block sets:
    • self to Object
    • «Текущий класс» на Object

«Текущий класс» используется для def, undef и alias, а также для поиска констант и переменных класса.


Теперь давайте посмотрим на детали реализации.

Вот как module_eval и _ 13_ реализованы на C:

VALUE rb_mod_module_eval(int argc, VALUE *argv, VALUE mod) {
    return specific_eval(argc, argv, mod, mod);
}

VALUE rb_obj_instance_eval(int argc, VALUE *argv, VALUE self) {
    VALUE klass;
    if (SPECIAL_CONST_P(self)) { klass = Qnil; }
    else { klass = rb_singleton_class(self); }
    return specific_eval(argc, argv, klass, self);
}

Оба вызывают specific_eval, который принимает следующие аргументы: int argc, VALUE *argv, VALUE klass и VALUE self.

Обратите внимание, что:

  • module_eval передает экземпляр Module или Class как klass и self
  • instance_eval передает одноэлементный класс объекта как klass

Если задан блок, specific_eval вызовет yield_under, что займет следующие аргументы: VALUE under, VALUE self и VALUE values.

if (rb_block_given_p()) {
    rb_check_arity(argc, 0, 0);
    return yield_under(klass, self, Qundef);
}

В yield_under есть две важные строки:

  1. block.self = self;

    Это устанавливает self блока на приемник.

  2. cref = vm_cref_push(th, under, NOEX_PUBLIC, blockptr);

    cref - это связанный список, в котором указывается "текущий class ", который используется для def, undef и alias, а также для поиска констант и переменных класса.

    Эта строка в основном устанавливает cref в under.

    Наконец-то:

    • При вызове из module_eval under будет экземпляром Class или Module.

    • При вызове из instance_eval under будет одноэлементным классом для self.

person Matheus Moreira    schedule 24.04.2012
comment
Есть одна вещь: внутри class_eval присвоение констант и переменных класса не работает так, как это работает при определении / повторном открытии класса: оно использует внешнюю область видимости. - person Alexey; 25.04.2012
comment
@ Алексей, ты прав. Бьюсь об заклад, это как-то связано с константой NODE_FL_CREF_PUSHED_BY_EVAL. Многие методы, например Module::nesting, похоже, игнорирует узел cref, если установлен флаг. - person Matheus Moreira; 25.04.2012
comment
Разве первая версия не должна быть Object.class_eval &block? В противном случае вы не показываете разницу между instance_eval в классе и class_eval в классе, вы показываете разницу между instance eval в экземпляре класса и class_eval в классе. - person Michael Hewson; 02.06.2019
comment
@MichaelHewson, я использовал класс для class_eval, потому что метод определен только для экземпляров Module. Классы являются объектами, поэтому instance_eval ведет себя аналогичным образом, даже если object = Object. self будет Object, но методы, определенные в блоке, станут методами класса Object, потому что они фактически были определены на Object.singleton_class. - person Matheus Moreira; 03.06.2019
comment
Извините, я хотел сказать, что вторая версия, использующая instance_eval, также должна использовать Object вместо Object.new, чтобы обе вызывались в классе Object. Я просто думаю, что наличие одного и того же приемника метода лучше проиллюстрирует разницу между методами. - person Michael Hewson; 05.06.2019
comment
О нет, это тоже неправильно: facepalm: это первая версия, о которой я говорю, та, что с instance_eval. Тем не менее, то, что я сказал об использовании того же приемника метода, все еще применимо. (Извините за все комментарии, жаль, что я не понял все правильно с первого раза.) - person Michael Hewson; 05.06.2019
comment
@ Майкл, теперь я понимаю, что ты имеешь в виду, и согласен. Я отредактирую свой ответ, чтобы отразить это. - person Matheus Moreira; 05.06.2019

instance_eval позволяет вам напрямую обращаться к переменным экземпляра и использовать self в качестве ссылки на экземпляр.

person rcrogers    schedule 24.04.2012
comment
class_eval и instance_eval работают одинаково в этом отношении - person Alexey; 24.04.2012