Every object has a class of its own¶
Level: 301 · a Ruby programmer who has written def self.build and wants to know where that method went
One line: A method defined on one object — by def obj.greet, class << obj, define_singleton_method or extend — lives in that object's singleton class, an anonymous class slotted between the object and its real class; a class method is exactly this done to a class, which is why Foo.singleton_class prints as #<Class:Foo>, why Foo.new.make fails, and why dup loses these methods while clone keeps them.
Ruby's method lookup starts one step before the object's class. Every object (except immediates like 1 and :sym) can own a hidden class that holds methods for that object alone; its superclass is the real class, and lookup starts there. That is the whole trick behind class methods: def self.make inside class Foo defines make in Foo's singleton class, so Foo responds to it and instances of Foo do not. The program below builds one such object step by step and prints where each method ends up. An instance's singleton class has no name — its inspect shows the object's address — so the program prints comparisons and class names for it, and the real thing only for a class, whose singleton class is named after it.
Verified output of singleton_classes_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
1. a method defined on one object
obj.greet hi from obj
other.greet NoMethodError
2. where that method lives
obj.singleton_methods [:greet]
Foo.instance_methods(false).sort [:inspect]
obj.singleton_class.superclass == Foo true
obj.singleton_class.singleton_class? true
obj.class Foo
obj.instance_of?(Foo) true
3. class << obj opens the singleton class
obj.name_length 3
obj.singleton_methods.sort [:greet, :name_length]
4. class methods are singleton methods of the class
Foo.singleton_methods.sort [:build, :make]
Foo.make #<Foo made>
Foo.build built by Foo
Foo.new("x").make NoMethodError
5. a class's singleton class has a name
Foo.singleton_class #<Class:Foo>
Foo.singleton_class.ancestors.first(3) [#<Class:Foo>, #<Class:Object>, #<Class:BasicObject>]
Foo.singleton_class.class Class
Foo.singleton_class.superclass #<Class:Object>
Class.class Class
Sub.singleton_class.superclass #<Class:Foo>
Sub.make (class methods are inherited) #<Sub made>
6. define_singleton_method takes a block
obj.wave wave from obj
7. extend is include into the singleton class
obj.singleton_class.include?(Loud) true
obj.singleton_class.ancestors (names) ["(singleton)", "Loud", "Foo", "Object", "Kernel", "BasicObject"]
obj.shout LOUD from obj
other.respond_to?(:shout) false
8. dup drops the singleton class, clone keeps it
obj.dup.singleton_methods []
obj.clone.singleton_methods.sort [:greet, :name_length, :shout, :wave]
clone.wave after renaming the clone wave from clone
9. immediates have no singleton class
1.singleton_class TypeError: can't define singleton
:sym.singleton_class TypeError: can't define singleton
nil.singleton_class NilClass
Reading the output¶
- 1–2.
def obj.greetgives one object a method;other, an instance of the same class, raisesNoMethodError. The method is not inFoo(Foo.instance_methods(false)still lists onlyinspect) but in a class whosesuperclassisFooand which answerssingleton_class?withtrue.obj.classstill saysFooandobj.instance_of?(Foo)is stilltrue: the singleton class is invisible toclassandinstance_of?. - 3.
class << obj … endopens that same class as a class body, so several methods can go in at once;singleton_methodslists both. - 4.
def self.makeandclass << self; def buildare the two spellings of the same thing, andFoo.singleton_methodslists both. A class method belongs to the class object alone:Foo.new("x").makeis aNoMethodError, because instances look inFoo, not inFoo's singleton class. - 5. A class's singleton class prints as
#<Class:Foo>, and its ancestors run in parallel to the class's own:#<Class:Foo><#<Class:Object><#<Class:BasicObject><Class. That parallel chain is why a subclass inherits class methods:Sub.singleton_class.superclassis#<Class:Foo>, soSub.makefindsmakethere and builds a#<Sub made>.Foo.singleton_class.classisClass— a singleton class is an ordinaryClassobject that answerssingleton_class?withtrue— andClass.classisClass, which closes the loop. - 6.
define_singleton_methoddoes whatdef obj.xdoes, from a block, so the name can be computed. - 7.
extend(Loud)isincludeperformed on the singleton class:Loudappears in the singleton class's ancestors right after the singleton class itself, and onlyobjgainedshout. - 8.
dupproduces an object whosesingleton_methodsis empty;clonecopies the singleton class, and the copied methods bind to the clone: after renaming the clone,wavereports the clone's name. dup and clone measures the rest of that difference. - 9.
1.singleton_classand:sym.singleton_classraiseTypeError: can't define singleton— Integers and Symbols are immediates without a per-object class.nil.singleton_classisNilClassitself.
Compared with Python¶
Python has no singleton class, so its answers for the same nine rows are workarounds, and two of them are traps. Storing a function on an instance (obj.greet = greet) stores a plain function, not a method: calling it passes no self, which is the TypeError in row 1; types.MethodType(greet, obj) builds the bound method by hand. The closest thing to class << obj is to give the object a class of its own by assigning obj.__class__ to a freshly made subclass (row 3), and extend becomes the same move with the mixin first in the bases (row 7). Class methods differ in a way that matters: a @classmethod is reachable through an instance (Foo("x").make() works), while Ruby's class methods are not; the true analogue of a Ruby class method is a method on the metaclass, which Bar.forge() finds and Bar().forge() does not. Row 5 is the other half of the picture: the class of a class is type (or a metaclass), where Ruby has #<Class:Foo>; a subclass inherits the metaclass (type(BarSub) is Meta, so BarSub.forge() works) the way Sub inherits make; and type(type) is type closes the loop just as Class.class is Class. Row 8 is the second trap: copy.copy keeps both the per-object class and the instance dictionary, but the bound method it copied still points at the original object — dup.wave() reports "obj" after the copy was renamed, where Ruby's clone rebinds. Row 9: an int or a str instance takes no attributes, the way Ruby's immediates take no singleton class.
Verified output of singleton_classes_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. a function stored on one object
obj.greet = greet; obj.greet() TypeError (self is not passed)
obj.greet = MethodType(greet, obj); obj.greet() hi from obj
other.greet() AttributeError
2. where that function lives
sorted(vars(obj)) ['greet', 'name']
'greet' in vars(Foo) False
type(obj) is Foo True
type(obj).__name__ Foo
isinstance(obj, Foo) True
3. the closest to class << obj: a per-object subclass
obj.name_length() 3
type(obj).__name__ / type(other).__name__ FooOnly / Foo
4. class methods live on the class or its metaclass
Foo.make() <Foo made>
Foo.build() built by Foo
Foo('x').make() (a classmethod is reachable) <Foo made>
Bar.forge() (a metaclass method) forged by Bar
Bar().forge() AttributeError
5. the class of a class is its metaclass
type(Foo).__name__ type
[c.__name__ for c in type(Foo).__mro__] ['type', 'object']
type(type) is type True
type(Bar).__name__ Meta
type(type).__name__ type
type(BarSub).__name__ (inherited metaclass) Meta
BarSub.forge() forged by BarSub
6. setattr with a bound MethodType
obj.wave() wave from obj
7. extend: a per-object subclass with the mixin first
isinstance(obj, Loud) True
[c.__name__ for c in type(obj).__mro__] ['FooLoud', 'Loud', 'FooOnly', 'Foo', 'object']
obj.shout() LOUD from obj
hasattr(other, 'shout') False
8. copy.copy keeps the class and the bound methods
type(copy.copy(obj)).__name__ FooLoud
sorted(vars(dup)) ['greet', 'name', 'wave']
dup.wave() after renaming the copy wave from obj (still bound to obj)
dup.wave.__self__ is obj True
9. built-in instances take no attributes
(1).greet = greet AttributeError
"sym".greet = greet AttributeError
type(None).__name__ NoneType
Try it¶
- Build a counter with no class of its own: an
Object.newgivenincrement,countandinspectas singleton methods. Print itssingleton_methods, show that a freshObject.newdoes not respond tocount, thendupandcloneit: which copy keeps the@countinstance variable, and which keeps the methods? - Write
module Greeterwith agreetmethod andextendone String with it. Print the names of that string'ssingleton_class.ancestors(map each toname, or"(singleton)"whensingleton_class?is true), then check"another".respond_to?(:greet). FinallyextendtheStringclass with the same module and explain whatString.greetnow does.
Solution to 1
singleton_classes_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.
# Exercise 1: an object with no class of its own. Build a counter from a bare
# Object.new using singleton methods only, then show what dup and clone keep.
counter = Object.new
counter.instance_variable_set(:@count, 0)
def counter.increment = @count += 1
def counter.count = @count
def counter.inspect = "#<counter #{@count}>"
3.times { counter.increment }
puts "counter #{counter.inspect}"
puts "counter.singleton_methods #{counter.singleton_methods.sort.inspect}"
puts "counter.class #{counter.class}"
puts "Object.new.respond_to?(:count) #{Object.new.respond_to?(:count)}"
copy = counter.dup
twin = counter.clone
puts "dup keeps @count? #{copy.instance_variable_get(:@count)}"
puts "dup.singleton_methods #{copy.singleton_methods.inspect}"
puts "dup.respond_to?(:increment) #{copy.respond_to?(:increment)}"
puts "clone.singleton_methods #{twin.singleton_methods.sort.inspect}"
twin.increment
puts "clone after one increment #{twin.inspect}"
puts "the original is untouched #{counter.inspect}"
Verified output of singleton_classes_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.
counter #<counter 3>
counter.singleton_methods [:count, :increment, :inspect]
counter.class Object
Object.new.respond_to?(:count) false
dup keeps @count? 3
dup.singleton_methods []
dup.respond_to?(:increment) false
clone.singleton_methods [:count, :increment, :inspect]
clone after one increment #<counter 4>
the original is untouched #<counter 3>
See also¶
newisallocatetheninitialize— the other half of the class object's job:newis a singleton method too, and can be overriddendupis a fresh copy,cloneis a faithful one — the full measurement of whatdupandclonecarry overObjectsits onKernelonBasicObject— the ancestor chain that the singleton class is inserted in front of- Mixins: include, extend, prepend —
extendseen from the module side - Class variables are shared — class-level instance variables live on the class object, next to its singleton methods
selfis implicit — whydef self.xin a class body means the classdefine_method— the instance-method counterpart ofdefine_singleton_methodinstance_evalandclass_eval—instance_evalwithdefdefines singleton methods;class_evaldefines instance methods- Ruby docs: singleton classes ↗ — the reference section
- Python docs: metaclasses ↗ — the class of a class, Python's side of row 5
- A module is a namespace, and cannot be instantiated —
module Shopis a named box for classes, constants and methods - Refinements are lexical, not global —
refine String do … endinside a module builds aRefinement, andusingthat module activates it from… Class.newmakes a class; a constant names it —Class.new(Base) { ... }returns a class object with no name,Foo = klassgives it the name of the…- Hooks fire when code is defined, not when it runs —
inherited,included,extended,prepended,method_added,singleton_method_addedand…