new is allocate then initialize¶
Level: 201 · anyone who has written def initialize and wondered who calls it
One line: Point.new(1, 2) is a class method that does two things — Class#allocate makes an empty object, then the automatically private instance method initialize(1, 2) fills it in — so allocate alone gives you an object with no instance variables, initialize's return value is ignored, and overriding self.new lets a class cache or dispatch its instances before any of that happens.
There is no constructor syntax in Ruby. new is an ordinary method on Class, which every class object inherits, and initialize is an ordinary instance method that new calls for you — it has to, because Ruby makes initialize private without being asked (along with initialize_copy). Everything the program below prints follows from that: what you can call on a freshly allocated object, why pt.initialize raises, why an ArgumentError from new names initialize's arity, and why super inside a self.new override is the only thing that ever runs initialize.
Verified output of new_allocate_and_initialize_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
1. new is allocate, then initialize
(initialize(1, 2) runs; self is a Point)
Point.new(1, 2) #<Point 1,2>
Point.method(:new).owner Class
Point.method(:allocate).owner Class
2. initialize is private automatically
Point.private_instance_methods(false).sort [:initialize, :initialize_copy]
(initialize(1, 2) runs; self is a Point)
pt.initialize(3, 4) NoMethodError: private method 'initialize' called for an instance of Point
(initialize(3, 4) runs; self is a Point)
pt.send(:initialize, 3, 4); pt #<Point 3,4>
3. allocate skips initialize
Point.allocate #<Point nil,nil>
blank.instance_variables []
blank.x nil
(initialize(5, 6) runs; self is a Point)
blank.send(:initialize, 5, 6); blank #<Point 5,6>
4. initialize's return value is ignored
(initialize(0, 0) runs; self is a Point)
Point.new(0, 0).class Point
5. arguments go straight to initialize
Point.new(1) ArgumentError: wrong number of arguments (given 1, expected 2)
6. overriding self.new: a cache
(Color#initialize("red") runs)
(Color#initialize("blue") runs)
Color.new("red").equal?(Color.new("red")) true
red.equal?(blue) false
7. overriding self.new: a factory
Shape.new(:circle, 3) #<Circle size=3>
Shape.new(:square, 4) #<Square size=4>
Circle.new(5) #<Circle size=5>
8. initialize_copy runs on dup and clone
(initialize(7, 8) runs; self is a Point)
(initialize_copy runs, copying #<Point 7,8>)
original.dup #<Point 7,8>
(initialize_copy runs, copying #<Point 7,8>)
original.clone #<Point 7,8>
9. classes with no allocator or no new
Integer.allocate TypeError: allocator undefined for Integer
Integer.new NoMethodError: undefined method 'new' for class Integer
NilClass.new NoMethodError: undefined method 'new' for class NilClass
10. a subclass calls super inside initialize
(initialize(1, 2) runs; self is a Point3)
Point3.new(1, 2, 3) #<Point3 1,2,3>
Point3.instance_method(:initialize).arity 3
Reading the output¶
- 1.
Point.new(1, 2)prints the trace from insideinitializeand returns the finished object. Bothnewandallocateare owned byClass, not byPoint. - 2.
private_instance_methods(false)listsinitializeandinitialize_copy, neither of which was declared private: Ruby makes them so. Callingpt.initialize(3, 4)from outside is therefore aNoMethodError, whilesendreaches it and re-initialises the object in place — seesendandpublic_send. - 3.
Point.allocatereturns aPointwith no instance variables, so its readerxisnil. A secondsend(:initialize, 5, 6)turns it into a normal point. This is whatMarshal.loadand object-relational mappers do: allocate, then fill in the state from elsewhere. (Not machine-checked here.) - 4.
initializereturns:the_return_value_is_ignored;newstill returns the object, soPoint.new(0, 0).classisPoint. - 5.
newpasses its arguments straight through, so theArgumentErrornamesinitialize's expectation:given 1, expected 2. - 6.
Color.newis overridden to look in a cache first.superis what reachesClass#new, soinitializeruns once per colour: the trace shows"red"initialised once even thoughColor.new("red")was called twice, and the two calls return the same object (equal?istrue). - 7.
Shape.new(:circle, 3)dispatches to a subclass'snew; the subclasses fall through tosuper, soCircle.new(5)still works directly. - 8.
dupandclonedo not callinitialize. They callinitialize_copyon the new object with the original as the argument — the hook to deep-copy a field. dup, clone and frozen state measures the rest. - 9. Some classes opt out.
Integer.allocateraisesTypeError: allocator undefined for Integer, andInteger.newandNilClass.newareNoMethodErrors: those classes undefinenew, because integers are immediates andnilis the one instance of its class. - 10. A subclass's
initializecallssuper(x, y)explicitly;arityis3, the subclass's own signature.
Compared with Python¶
Python splits the same two steps into two dunder methods and runs them from the metaclass: Point(1, 2) is type.__call__(Point, 1, 2), which calls Point.__new__(Point, 1, 2) — inherited from object here — and then __init__(1, 2) on the result. Row 2 is the first difference: __init__ is public, so pt.__init__(3, 4) simply re-initialises the object, and Python has no private methods for new to need send for. Row 3 is allocate by another name: object.__new__(Point) skips __init__, leaves vars(blank) empty, and a later blank.__init__(5, 6) fills it. Row 4 is a difference in the other direction — __init__ must return None, and returning 5 is a TypeError, where Ruby ignores initialize's value. Row 6 is the trap: a cache written in __new__ returns the cached object, but type.__call__ then runs __init__ on it again — the trace shows Color.__init__('red') twice — because __init__ runs whenever __new__ returns an instance of the class; Ruby's self.new override never calls initialize unless super does. Row 7's factory works in both, and for the same reason: __new__ returning a Circle (an instance of Shape) still gets __init__. Row 8: copy.copy calls __copy__ when the class defines it, and never __init__ — Ruby's initialize_copy is the same hook by another name, and copy.deepcopy falls back to a state copy when there is no __deepcopy__. Row 9: object(1) and int.__new__(str) are TypeErrors, and type(None)() hands back the one None rather than building a second one.
Verified output of new_allocate_and_initialize_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. calling the class is __new__, then __init__
(__init__(1, 2) runs; self is a Point)
Point(1, 2) Point(1, 2)
type(Point).__call__ is type.__call__ True
Point.__new__ is object.__new__ True
2. __init__ is public
'__init__' in vars(Point) True
(__init__(1, 2) runs; self is a Point)
(__init__(3, 4) runs; self is a Point)
pt.__init__(3, 4); pt Point(3, 4)
private methods none; a leading _ is a convention
3. object.__new__(Point) skips __init__
object.__new__(Point) Point(None, None)
vars(blank) {}
getattr(blank, 'x', None) None
(__init__(5, 6) runs; self is a Point)
blank.__init__(5, 6); blank Point(5, 6)
4. __init__ must return None
BadInit() (its __init__ returns 5) TypeError
5. arguments go straight to __init__
Point(1) TypeError
6. overriding __new__: a cache (and a trap)
(Color.__init__('red') runs)
(Color.__init__('red') runs)
(Color.__init__('blue') runs)
Color('red') is Color('red') True
red is blue False
7. overriding __new__: a factory
Shape('circle', 3) <Circle size=3>
Shape('square', 4) <Square size=4>
Circle(5) <Circle size=5>
8. __copy__ runs on copy.copy; __init__ does not
(__init__(7, 8) runs; self is a Point)
(__copy__ runs, copying Point(7, 8))
copy.copy(original) Point(7, 8)
copy.deepcopy(original) Point(7, 8)
9. classes that refuse arguments or new instances
object(1) TypeError
int.__new__(str) TypeError
type(None)() is None True
10. a subclass calls super().__init__
(__init__(1, 2) runs; self is a Point3)
Point3(1, 2, 3) Point3(1, 2, 3)
Point3.__init__.__code__.co_argcount - 1 3
Try it¶
- Write
class Moneywithinitialize(cents)and overrideself.newso thatMoney.new(0)always returns one shared zero object (built the first time it is asked for) while any other amount builds a fresh object. PrintMoney.new(0).equal?(Money.new(0)),Money.new(5).equal?(Money.new(5))and how many timesinitializeran; then callMoney.allocateand show the count did not move. - Give
Pointa class methodfrom_hash(h)that builds a point withallocateandinstance_variable_set, never callinginitialize. Show that it prints the same asPoint.new(h[:x], h[:y]), then add aputsinsideinitializeto prove it was skipped.
Solution to 1
new_allocate_and_initialize_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.
# Exercise 1: a shared zero. Money.new(0) always returns one object, built once;
# every other amount is a fresh object. Count how often initialize runs, then
# show that allocate walks around the override entirely.
class Money
@built = 0
class << self
attr_reader :built
def new(cents)
return @zero ||= super if cents.zero?
super
end
def count_one = @built += 1
end
attr_reader :cents
def initialize(cents)
Money.count_one
@cents = cents
end
def inspect = "#<Money #{@cents}c>"
end
zero_a = Money.new(0)
zero_b = Money.new(0)
five_a = Money.new(5)
five_b = Money.new(5)
puts "Money.new(0) #{zero_a.inspect}"
puts "Money.new(0).equal?(Money.new(0)) #{zero_a.equal?(zero_b)}"
puts "Money.new(5).equal?(Money.new(5)) #{five_a.equal?(five_b)}"
puts "times initialize ran #{Money.built}"
puts "Money.allocate.cents #{Money.allocate.cents.inspect}"
puts "times initialize ran after allocate #{Money.built}"
Verified output of new_allocate_and_initialize_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
See also¶
- Every object has a class of its own —
newis a singleton method of the class, which is whydef self.newcan override it dupis a fresh copy,cloneis a faithful one —initialize_copyand what the two copies carry overto_s,inspectandp— theinspectevery class in this chapter defines to print without an address- Private means no receiver — why
pt.initializefails andsend(:initialize)does not - Classes at runtime —
Class.newandStruct.newas class factories, the level aboveFoo.new sendandpublic_send— the call that reaches a private method- Struct and Data — classes that write
initializefor you - Ruby docs:
Class#newandClass#allocate↗ — the two methods this page takes apart - Python docs:
__new__and__init__↗ — the data model's account of instance creation - Twenty-three Python traps for a Ruby programmer — The twenty-three places where a Ruby reflex gives the wrong answer in Python