Что такое Ruby-аналог метаклассов Python?

В Python есть идея метаклассов, которые, если я правильно понимаю, позволяют вам изменять объект класса в момент создания. Вы изменяете не класс, а объект, который должен быть создан и инициализирован.

Python (по крайней мере, с версии 3.0, я полагаю) также имеет идею декораторов классов. Опять же, если я правильно понимаю, декораторы классов позволяют изменять определение класса в момент его объявления.

Теперь я считаю, что в Ruby есть функция, эквивалентная декоратору классов, но в настоящее время мне неизвестно что-то эквивалентное метаклассам. Я уверен, что вы можете легко прокачать любой объект Ruby с помощью некоторых функций и делать с ним все, что захотите, но есть ли в языке функция, которая настраивает это, как это делают метаклассы?

Итак, еще раз: есть ли у Ruby что-то похожее на метаклассы Python?

Изменить. Я отключил метаклассы для Python. Кажется, что метакласс и декоратор класса делают очень похожие вещи. Оба они модифицируют класс, когда он определен, но по-разному. Надеюсь, гуру Python придет и лучше расскажет об этих функциях Python.

Но класс или родительский класс могут реализовать функцию __new__(cls[,..]), которая настраивает конструкцию объекта до его инициализации с помощью __init__(self[,..]).

Изменить. Этот вопрос в основном предназначен для обсуждения и изучения того, как эти два языка сравниваются в этих функциях. Я знаком с Python, но не с Ruby, и мне было любопытно. Надеюсь, любой, у кого есть такой же вопрос о двух языках, найдет этот пост полезным и поучительным.


person Sean Copenhaver    schedule 20.04.2010    source источник
comment
Хм ... Возможно, я все еще что-то упускаю, но после прочтения пары ссылок это начинает звучать так, как будто метакласс Python и метакласс Ruby разные. Надеюсь, у меня будет возможность провести больше исследований и опубликовать информацию о том, что я узнал. Конечно, если кто-то не знает. Ссылки: Кац на метаклассах, Metaclass и Ruby изнутри   -  person Sean Copenhaver    schedule 20.04.2010


Ответы (2)


У Ruby нет метаклассов. В Ruby есть некоторые конструкции, которые некоторые люди иногда ошибочно называют метаклассами, но это не так (что является источником бесконечной путаницы).

Однако есть много способов добиться в Ruby тех же результатов, что и с метаклассами. Но не сообщая нам, что именно вы хотите сделать, невозможно сказать, что это за механизмы.

Суммируя:

  • У Ruby нет метаклассов
  • Ruby не имеет ни одной конструкции, соответствующей метаклассам Python.
  • Все, что Python может делать с метаклассами, также можно сделать в Ruby.
  • Но не существует одиночной конструкции, вы будете использовать разные конструкции в зависимости от того, что именно вы хотите сделать.
  • Любая из этих конструкций, вероятно, также имеет другие функции, которые не соответствуют метаклассам (хотя они, вероятно, соответствуют чему-то else в Python)
  • Хотя в Ruby можно делать все, что можно делать с метаклассами в Python, это не всегда просто.
  • Хотя часто бывает более рубиновое решение, которое является элегантным.
  • И последнее, но не менее важное: хотя вы можете делать в Ruby все, что вы можете делать с метаклассами в Python, это не обязательно может быть Ruby Way.

Итак, что именно такое метаклассы? Ну, это классы классов. Итак, давайте сделаем шаг назад: что такое классы?

Классы

  • фабрики для объектов
  • определять поведение объектов
  • определить на метафизическом уровне, что значит быть экземпляром класса

Например, класс Array создает объекты массива, определяет поведение массивов и определяет, что означает «массивность».

Вернемся к метаклассам.

Метаклассы

  • фабрики для занятий
  • определить поведение классов
  • определить на метафизическом уровне, что значит быть классом

В Ruby эти три обязанности разделены на три разных места:

  • класс Class создает классы и определяет немного поведения
  • собственный класс отдельного класса определяет немного поведения класса
  • концепция "классности" встроена в интерпретатор, который также реализует основную часть поведения (например, вы не можете наследовать от Class, чтобы создать новый тип класса, который ищет методы по-другому, или что-то в этом роде, алгоритм поиска метода встроен в интерпретатор)

Итак, эти три вещи вместе играют роль метаклассов, но ни один из них не является метаклассом (каждый реализует лишь небольшую часть того, что делает метакласс), ни сумма из них не является метаклассом. (потому что они делают гораздо больше).

К сожалению, некоторые люди называют собственные классы классов метаклассами. (До недавнего времени я был одним из тех заблудших душ, пока наконец не увидел свет.) Другие люди называют метаклассами все собственные классы. (К сожалению, один из этих людей является автором одного из самых популярных руководств по метапрограммированию Ruby и объектной модели Ruby.) Некоторые популярные библиотеки добавляют метод metaclass к Object, который возвращает собственный класс объекта (например, ActiveSupport, Facets, metaid). Некоторые люди называют все виртуальные классы (т.е. собственные классы и классы включения) метаклассами. Некоторые люди называют Class метаклассом. Даже в самом исходном коде Ruby слово «метакласс» используется для обозначения вещей, которые не являются метаклассами.

person Jörg W Mittag    schedule 20.04.2010
comment
Спасибо за сообщение. Это в основном именно то, что я искал. Теперь, надеюсь, кто-то с большим знанием Python сможет комментировать или публиковать сообщения о различных функциях Python. - person Sean Copenhaver; 21.04.2010
comment
Вопрос изменился с тех пор, как я изначально ответил, но я решил создать новый ответ вместо того, чтобы редактировать этот, и оставить его, в основном в надежде, что он будет полезен кому-то, кто окажется здесь после поиска ruby ​​python метакласс или что-то в этом роде. - person Jörg W Mittag; 21.04.2010
comment
Спасибо за ответ. Задача вопроса заключалась в том, чтобы узнать о метаклассах на обоих языках. Я добавил первую правку, когда понял, что мое понимание их в Python было неправильным. Извините, если это вас смутило. Оба ваших ответа очень помогли узнать о возможностях Ruby. - person Sean Copenhaver; 21.04.2010

Ваш обновленный вопрос теперь выглядит совсем иначе. Если я вас правильно понял, вы хотите подключиться к распределению и инициализации объектов, что не имеет абсолютно ничего общего с метаклассами. (Но вы по-прежнему не пишете, что именно хотите делать, так что я все равно могу уйти.)

В некоторых объектно-ориентированных языках объекты создаются конструкторами. Однако в Ruby нет конструкторов. Конструкторы - это всего лишь фабричные методы (с дурацкими ограничениями); нет причин использовать их на хорошо разработанном языке, если вместо этого вы можете просто использовать (более мощный) фабричный метод.

Построение объекта в Ruby работает следующим образом: построение объекта делится на две фазы: выделение и инициализация. Распределение выполняется общедоступным методом класса с именем allocate, который определяется как метод экземпляра класса Class и обычно никогда не переопределяется. (На самом деле, я не думаю, что вы действительно можете переопределить его.) Он просто выделяет пространство памяти для объекта и устанавливает несколько указателей, однако объект на данный момент не может использоваться .

Здесь на помощь приходит инициализатор: это метод экземпляра с именем initialize, который устанавливает внутреннее состояние объекта и переводит его в согласованное, полностью определенное состояние, которое может использоваться другими объектами.

Итак, чтобы полностью создать новый объект, вам нужно сделать следующее:

x = X.allocate
x.initialize

[Примечание: программисты Objective-C могут это понять.]

Однако, поскольку слишком легко забыть вызвать initialize и, как правило, объект должен быть полностью действительным после построения, существует удобный фабричный метод под названием Class#new, который делает все, что работает за вас, и выглядит примерно так:

class Class
  def new(*args, &block)
    obj = allocate
    obj.initialize(*args, &block)

    return obj
  end
end

[Примечание: на самом деле initialize является частным, поэтому необходимо использовать отражение, чтобы обойти такие ограничения доступа: obj.send(:initialize, *args, &block)]

Это, кстати, является причиной того, почему для создания объекта вы вызываете метод открытого класса Foo.new, но реализуете метод частного экземпляра Foo#initialize, который, кажется, сбивает много новичков.

Однако ничего из этого никоим образом не встроено в язык. Тот факт, что основной фабричный метод для любого класса обычно называется new, - это просто соглашение (и иногда мне хотелось бы, чтобы он был другим, потому что он похож на конструкторы в Java, но полностью отличается). В других языках у конструктора должно быть определенное имя. В Java он должен иметь то же имя, что и класс, что означает, что а) может быть только один конструктор и б) анонимные классы не могут иметь конструкторов, потому что у них нет имен. В Python фабричный метод должен называться __new__, что опять же означает, что может быть только один. (И в Java, и в Python вы, конечно, можете иметь разные фабричные методы, но их вызов выглядит иначе, чем вызов по умолчанию, тогда как в Ruby (и Smalltalk, откуда возник этот шаблон) он выглядит точно так же.)

В Ruby может быть сколько угодно фабричных методов с любым именем, а фабричный метод может иметь множество разных имен. (Для классов коллекций, например, фабричный метод часто имеет псевдоним [], что позволяет писать List[1, 2, 3] вместо List.new(1, 2, 3), что в конце больше похоже на массив, тем самым подчеркивая коллекционный характер списков.)

Суммируя:

  • стандартный заводской метод Foo.new, но это может быть что угодно
  • Foo.new вызывает allocate, чтобы выделить память для пустого объекта foo
  • Foo.new затем вызывает foo.initialize, т.е. метод экземпляра Foo#initialize
  • все три из них - это такие же методы, как и любые другие, которые вы можете отменить, переопределить, переопределить, обернуть, псевдоним и еще много чего.
  • ну, кроме allocate, которому необходимо выделить память внутри среды выполнения Ruby, чего вы не можете сделать из Ruby

В Python __new__ примерно соответствует new и allocate в Ruby, а __init__ точно соответствует initialize в Ruby. Основное отличие состоит в том, что в Ruby new вызывает initialize, тогда как в Python среда выполнения автоматически вызывает __init__ после __new__.

Например, вот класс, который позволяет создавать максимум 2 экземпляра:

class Foo
  def self.new(*args, &block)
    @instances ||= 0
    raise 'Too many instances!' if @instances >= 2

    obj = allocate
    obj.send(:initialize, *args, &block)

    @instances += 1

    return obj
  end

  attr_reader :name

  def initialize(name)
    @name = name
  end
end

one = Foo.new('#1')
two = Foo.new('#2')
puts two.name         # => #2
three = Foo.new('#3') # => RuntimeError: Too many instances!
person Jörg W Mittag    schedule 20.04.2010
comment
тебе не нужен obj = super? :) - person horseyguy; 05.05.2010
comment
@banister: Да, ты прав. Я переключил его в последнюю минуту, чтобы убедиться, что я не увеличиваю @instances до того, как объект действительно будет успешно построен, и забыл его протестировать. Я просто переключил его обратно, так как это, вероятно, перебор для такого небольшого фрагмента кода. В реальной системе вы, вероятно, поместите его в else блок исключений. - person Jörg W Mittag; 05.05.2010
comment
@banister: На самом деле, все было плохо продумано. - person Jörg W Mittag; 05.05.2010