Dynamically adding methods with metaprogramming : Ruby and Python

Posted by – January 13, 2010

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!!
4 Comments on Dynamically adding methods with metaprogramming : Ruby and Python

Respond | Trackback

  1. David Bacher says:

    That’s an interesting comparison. Could you contrast the ruby and python approaches in more detail? One thing that strikes me is namespace issues. It looks like your python code ends up defining functions in the global namespace and then assigning them to a class or instance, while the ruby code scopes the definitions more explicitly.

    • Dhananjay Nene says:

      David,

      The difference actually is due to the languages per se. python does not allow reopening of a class to add methods to it the way ruby allows.

      The entry can be simply removed by using the del command as follows :

      1
      2
      3
      4
      5
      def battle_cry(self):
          print '%s says zing!!!' % self.name
      Ninja.battle_cry = battle_cry
      # Remove the battle_cry entry
      del battle_cry
  2. Vitaly Babiy says:

    Awesome post. Metaprogramming doesn’t seem to be used as much in the python world as it is in the ruby world, but it nice to see how to implement these patterns if need be.

  3. [...] all the examples (in Perl 5 / Moose, Ruby, Javascript & Python) I think this one is the most clean and intuitive. Perl 6 has a good future if it keeps this [...]

Respond

Note: To embed code wrap code with the tags <code lang="_language_">..Your code..</code>. Instead of _language_ use the specific language. Supported languages are "python","ruby","php","java","scala" and many many others.
Comments

Comments