Что такое аналог Ruby для метаклассов Python? - PullRequest
10 голосов
/ 20 апреля 2010

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

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

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

Итак, опять же, есть ли в Ruby что-то похожее на метаклассы Python?

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

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

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

Ответы [ 2 ]

23 голосов
/ 21 апреля 2010

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

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

Короче говоря:

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

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

Классы & hellip;

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

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

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

Метаклассы & hellip;

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

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

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

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

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

12 голосов
/ 21 апреля 2010

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

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

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

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

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

x = X.allocate
x.initialize

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

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

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 runtime автоматически вызывает __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!
...