What is Ruby's analog to Python Metaclasses?
Python has the idea of metaclasses that, if I understand correctly, allow you to modify an object of a class at the moment of construction. You are not modifying the class, but instead the object that is to be created then initialized.
Python (at least as of 3.0 I believe) also has the idea of class decorators. Again if I understand correctly, class decorators allow the modifying of the class definition at the moment it is being declared.
Now I believe there is an equivalent feature or features to the class decorator in Ruby, but I'm currently unaware of something equivalent to metaclasses. I'm sure you can easily pump开发者_如何学编程 any Ruby object through some functions and do what you will to it, but is there a feature in the language that sets that up like metaclasses do?
So again, Does Ruby have something similar to Python's metaclasses?
Edit I was off on the metaclasses for Python. A metaclass and a class decorator do very similar things it appears. They both modify the class when it is defined but in different manners. Hopefully a Python guru will come in and explain better on these features in Python.
But a class or the parent of a class can implement a __new__(cls[,..])
function that does customize the construction of the object before it is initialized with __init__(self[,..])
.
Edit This question is mostly for discussion and learning about how the two languages compare in these features. I'm familiar with Python but not Ruby and was curious. Hopefully anyone else who has the same question about the two languages will find this post helpful and enlightening.
Ruby doesn't have metaclasses. There are some constructs in Ruby which some people sometimes wrongly call metaclasses but they aren't (which is a source of endless confusion).
However, there's a lot of ways to achieve the same results in Ruby that you would do with metaclasses. But without telling us what exactly you want to do, there's no telling what those mechanisms might be.
In short:
- Ruby doesn't have metaclasses
- Ruby doesn't have any one construct that corresponds to Python's metaclasses
- Everything that Python can do with metaclasses can also be done in Ruby
- But there is no single construct, you will use different constructs depending on what exactly you want to do
- Any one of those constructs probably has other features as well that do not correspond to metaclasses (although they probably correspond to something else in Python)
- While you can do anything in Ruby that you can do with metaclasses in Python, it might not necessarily be straightforward
- Although often there will be a more Rubyish solution that is elegant
- Last but not least: while you can do anything in Ruby that you can do with metaclasses in Python, doing it might not necessarily be The Ruby Way
So, what are metaclasses exactly? Well, they are classes of classes. So, let's take a step back: what are classes exactly?
Classes …
- are factories for objects
- define the behavior of objects
- define on a metaphysical level what it means to be an instance of the class
For example, the Array
class produces array objects, defines the behavior of arrays and defines what "array-ness" means.
Back to metaclasses.
Metaclasses …
- are factories for classes
- define the behavior of classes
- define on a metaphysical level what it means to be a class
In Ruby, those three responsibilities are split across three different places:
- the
Class
class creates classes and defines a little bit of the behavior - the individual class's eigenclass defines a little bit of the behavior of the class
- the concept of "classness" is hardwired into the interpreter, which also implements the bulk of the behavior (for example, you cannot inherit from
Class
to create a new kind of class that looks up methods differently, or something like that – the method lookup algorithm is hardwired into the interpreter)
So, those three things together play the role of metaclasses, but neither one of those is a metaclass (each one only implements a small part of what a metaclass does), nor is the sum of those the metaclass (because they do much more than that).
Unfortunately, some people call eigenclasses of classes metaclasses. (Until recently, I was one of those misguided souls, until I finally saw the light.) Other people call all eigenclasses metaclasses. (Unfortunately, one of those people is the author of one the most popular tutorials on Ruby metaprogramming and the Ruby object model.) Some popular libraries add a metaclass
method to Object
that returns the object's eigenclass (e.g. ActiveSupport, Facets, metaid). Some people call all virtual classes (i.e. eigenclasses and include classes) metaclasses. Some people call Class
the metaclass. Even within the Ruby source code itself, the word "metaclass" is used to refer to things that are not metaclasses.
Your updated question looks quite different now. If I understand you correctly, you want to hook into object allocation and initialization, which has absolutely nothing whatsoever to do with metaclasses. (But you still don't write what it is that you actually want to do, so I might still be off.)
In some object-oriented languages, objects are created by constructors. However, Ruby doesn't have constructors. Constructors are just factory methods (with stupid restrictions); there is no reason to have them in a well-designed language, if you can just use a (more powerful) factory method instead.
Object construction in Ruby works like this: object construction is split into two phases, allocation and initialization. Allocation is done by a public class method called allocate
, which is defined as an instance method of class Class
and is generally never overriden. (In fact, I don't think you actually can override it.) It just allocates the memory space for the object and sets up a few pointers, however, the object is not really usable at this point.
That's where the initializer comes in: it is an instance method called initialize
, which sets up the object's internal state and brings it into a consistent, fully defined state which can be used by other objects.
So, in order to fully create a new object, what you need to do is this:
x = X.allocate
x.initialize
[Note: Objective-C programmers may recognize this.]
However, because it is too easy to forget to call initialize
and as a general rule an object should be fully valid after construction, there is a convenience factory method called Class#new
, which does all that work for you and looks something like this:
class Class
def new(*args, &block)
obj = allocate
obj.initialize(*args, &block)
return obj
end
end
[Note: actually, initialize
is private, so reflection has to be used to circumvent the access restrictions like this: obj.send(:initialize, *args, &block)
]
That, by the way, is the reason why to construct an object you call a public class method Foo.new
but you implement a private instance method Foo#initialize
, which seems to trip up a lot of newcomers.
However, none of this is in any way baked into the language. The fact that the primary factory method for any class is usually called new
is just a convention (and sometimes I wish it were different, because it looks similar to constructors in Java, but is completely different). In other languages, the constructor must have a specific name. In Java, it must have the same name as the class, which means that a) there can be only one constructor and b) anonymous classes can't have constructors because they don't have names. In Python, the factory method must be called __new__
, which again means there can be only one. (In both Java and Python, you can of course have different factory methods, but calling them looks different from calling the default, while in Ruby (and Smalltalk from whence this pattern originated) it looks just the same.)
In Ruby, there can be as many factory methods as you like, with any name you like, and a factory method can have many different names. (For collection classes, for example, the factory method is often aliased to []
, which allows you to write List[1, 2, 3]
instead of List.new(1, 2, 3)
which ends looking more like an array, thus emphasizing the collection-ish nature of lists.)
In short:
- the standardized factory method is
Foo.new
, but it can be anything Foo.new
callsallocate
to allocate memory for an empty objectfoo
Foo.new
then callsfoo.initialize
, i.e. theFoo#initialize
instance method- all three of those are just methods like any other, which you can undefine, redefine, override, wrap, alias and whatnot
- well, except
allocate
which needs to allocate memory inside the Ruby runtime which you can't really do from Ruby
In Python, __new__
roughly corresponds to both new
and allocate
in Ruby, and __init__
exactly corresponds to initialize
in Ruby. The main difference is that in Ruby, new
calls initialize
whereas in Python, the runtime automatically calls __init__
after __new__
.
For example, here is a class which only allows a maximum of 2 instances created:
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!
精彩评论