I came across an interesting post Metaprogramming: Ruby vs. Javascript, which discusses and contrasts about how metaprogramming can be implemented in Ruby and Javascript. I thought it might be fun to document the same from a python perspective as well.
Here are the discussed samples. All the ruby code is quoted from from the blog post linked to above whereas the python code is what I wrote.
1. Initial class declaration and initialisation
We first declare a Ninja class and create two instances.
Ruby
1 2 3 4 5 6 7 8 9 10 | class Ninja attr_accessor :name def initialize(name) @name = name end end drew = Ninja.new("Drew") adam = Ninja.new("Adam") |
Python
1 2 3 4 5 6 | class Ninja(object): def __init__(self,name) : self.name = name drew = Ninja('drew') adam = Ninja('adam') |
2. Add a method to the class
In this step we add a method to the class dynamically. It is therefore available to all the instances of the class
Ruby
1 2 3 4 5 6 7 8 9 10 | class Ninja def battle_cry puts "#{name} says zing!!!" end end drew.battle_cry # => Drew says zing!!! adam.battle_cry # => Adam says zing!!! |
Python
1 2 3 4 5 6 7 8 | def battle_cry(self): print '%s says zing!!!' % self.name Ninja.battle_cry = battle_cry drew.battle_cry() # => Drew says zing!!! adam.battle_cry() # => Adam says zing!!! |
3. Add a method to an instance
In this case we add a method only to a particular instance. The same method will not be available to other instances of the same class.
Ruby
1 2 3 4 5 6 | def drew.throw_star puts "throwing a star" end drew.throw_star # => throwing a star |
Python
1 2 3 4 5 6 7 | import types def throw_star(self): print 'throwing a star' drew.throw_star = types.MethodType(throw_star,drew) drew.throw_star() # => throwing a star |
4. Invoke a method dynamically
In this case we supply the method name as a string and invoke it.
Ruby
1 2 | drew.send(:battle_cry) # => Drew says zing!!! |
Python
1 2 | drew.__getattribute__('battle_cry')() # => Drew says zing!!! |
5. Defining class level methods dynamically with closures
Here a class level method is defined which closes over some of the attributes in its context (in this case the method color is able to access the variable color_name as a closure).
Ruby
1 2 3 4 5 6 7 8 9 10 | color_name = 'black' Ninja.send(:define_method, 'color') do puts "#{name}'s color is #{color_name}" end drew.color # => Drew's color is black adam.color # => Adam's color is black |
Python
1 2 3 4 5 6 7 8 9 10 | color_name = 'black' def color(self): print "%s's color is %s" % (self.name, color_name) Ninja.color = color drew.color() # => Drew's color is black adam.color() # => Adam's color is black |
6. Defining a method dynamically on an instance that closes over local scope and accesses the instance’s state
Note that in this case the function is being defined on the instance using a dynamic name (‘swing’)
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Object def metaclass class << self; self; end end end sword_symbol = "*********" drew.metaclass.send(:define_method, 'swing') do |sound_effect| puts "#{name}: #{sword_symbol} #{sound_effect}" end drew.swing 'slash!!' # => Drew: ********* slash!! |
Python
1 2 3 4 5 6 7 8 | sword_symbol = "*********" def foo(self,sound_effect): print "%s : %s %s" % (self.name,sword_symbol,sound_effect) drew.__dict__['swing'] = types.MethodType(foo,drew) drew.swing('slash!!') # => Drew : ********* slash!! |

