10 — Metaprogramming¶
One line: In Ruby a class body is code that runs, a method call is a message, and a class is an object — so the program can write, inspect and reroute its own methods at run time, and every one of those moves has a Python spelling that is close enough to be useful and different enough to be measured.
This chapter is the eight moves that Ruby libraries are built from: calling a method by a name computed at run time (send), making methods in a loop (define_method), answering messages nobody defined (method_missing), running a block as somebody else (instance_eval, class_eval), being told when code is defined (inherited, included, method_added), turning those moves into a readable DSL, making classes without a class keyword (Class.new), and asking any object what it is (methods, parameters, defined?). Each lesson is one Ruby program and one Python twin that print the same numbered rows, so the comparison is a diff you can read.
For a Python programmer the map is mostly one-to-one and worth learning precisely, because this is where the two object models differ most. send is getattr(...)() — but Ruby has a private to bypass and Python has only a naming convention and compile-time mangling. define_method is setattr(Cls, name, func) — but a Ruby block gets a fresh loop variable per call and a Python lambda binds late, so Python needs the c=c default. method_missing is __getattr__ — but Python's hasattr sees ghosts for free and Ruby needs respond_to_missing?. instance_eval has no twin at all, because Python cannot move self; exec, setattr, classmethod and types.MethodType do its jobs one at a time. Ruby's seven definition hooks are Python's single __init_subclass__ plus a metaclass, and where Python lets you subclass type — that is what a metaclass is — Ruby refuses Class.new(Class) and uses singleton classes and hooks instead. Class.new is type(name, bases, ns), except that a Ruby class is anonymous until a constant names it, and a Python class is named at birth.
| Lesson | Level | The one thing |
|---|---|---|
send reaches private methods; public_send does not |
301 | send ignores private, public_send honours it, __send__ survives an overridden send; getattr has no visibility to ignore |
define_method turns a block into a method |
301 | the block is a closure with a fresh loop variable per each call; Python's setattr + lambda binds late and needs c=c |
method_missing needs respond_to_missing? |
301 | the hook runs only after lookup fails and must call super; respond_to? and method stay blind without the second hook, while hasattr sees __getattr__ through |
instance_eval moves self, class_eval moves def |
301 | both change self; only the default definee differs, so class_eval { def } makes instance methods and instance_eval { def } singleton ones; Python cannot move self |
| Hooks fire when code is defined, not when it runs | 301 | inherited, included, extended, prepended, method_added, const_added are private callbacks; Python has __init_subclass__ and a metaclass's __prepare__/__new__ |
A DSL is instance_eval plus stored blocks |
301 | bare words are methods of the object being built, blocks are filed for later, and the block loses the caller's @ivars; Python uses class bodies, decorators, with and **kwargs |
Class.new makes a class; a constant names it |
301 | a class is anonymous until first assigned to a constant, and Class.new(Class) is refused; type() names at birth and subclassing type is a metaclass |
| An object can list its methods and variables | 301 | methods, instance_methods(false), parameters, source_location, ancestors, defined?, __method__; Python's dir, vars, inspect and the frame |
Read more¶
Object#send↗ — the Ruby docs entry, withpublic_sendand__send__beside itModule#define_method↗ — the three things it accepts: a block, aMethod, aProcBasicObject#method_missing↗ — andinstance_eval,instance_exec,__send__on the same pageClass#inherited↗ — withsubclassesandattached_object; theModulepage hasincluded,method_addedandconst_added- Python data model: customizing class creation ↗ —
__init_subclass__,__set_name__, metaclasses and__prepare__ - Python
getattr,setattr,type↗ — the builtins that do whatsend,define_methodandClass.newdo - Python
inspect↗ —signature,getsourcefile,getmembers: the introspection twin - Python
types.new_class↗ —type()with keyword options for__init_subclass__