How to implement Ruby's equivalent method_missing in Python?
Takeaways
- Handling method_missing in Ruby and Python.
NoMethodError
Irrespective of the language, when we call a method on an object which is not defined in a class, the program will exit with an exception saying there is no such method defined.
Eg: Ruby raises an Undefined method exception. Python raises an AttributeError exception.
# Ruby
class Animal
def initialize(name)
@name = name
end
end
a = Animal.new('Parrot')
a.get_my_name()
# When we run this, it throws the following exception
no_method.rb:8:in `<main>': undefined method `get_my_name' for #<Animal:0x007fa707064d60 @name="Parrot"> (NoMethodError)
# Python
class Animal(object):
def __init__(self, name):
self.name = name
a = Animal('Parrot')
a.get_my_name()
# When we run this, it will throw the following exception
Traceback (most recent call last):
File "no_method.py", line 6, in <module>
a.get_my_name()
AttributeError: 'Animal' object has no attribute 'get_my_name'
Is there a way to handle NoMethodError exception gracefully? YES
Let's look at how to handle NoMethodError in Ruby and Python in the following sections.
method_missing in ruby
Ruby provides a special method called method_missing to intercept the control when the called method doesn’t exist. Once you have the control it is up to you what you want to do. method_missing is used frequently when there the functionality is the same for different methods.
The method_missing method takes 3 arguments:
- Name of the method you were trying to call. Passed as the symbol.
- Arguments to the method (*args)
- Block (&block)
I am writing a class to generate HTML tags, for each tag I just want to return open and closing tags except for few tags which have only open tag and no close tag (eg: img). There are different ways to do it, let's look at how to do it without method_missing
class HtmlTag
OPEN_TAGS = ['img', 'input']
def get_tag(ele)
if OPEN_TAGS.include?(ele)
return "<#{ele}/>"
else
return "<#{ele}></#{ele}>"
end
end
end
html = HtmlTag.new()
puts(html.get_tag('p'))
puts(html.get_tag('img'))
Implemented using without method_missing
class HtmlTag
OPEN_TAGS = ['img', 'input']
def method_missing(name, *args, &block)
if OPEN_TAGS.include?(name.to_s)
return "<#{name}/>"
else
return "<#{name}></#{name}>"
end
end
end
html = HtmlTag.new()
puts(html.p)
puts(html.img)
Implemented using with method_missing
method_missing in Python
In Python, when we call a method on an object the execution is a two-step operation. Getting the method (attribute) and calling the method with arguments.
Eg: If we are calling a method get_name on an object animal with parameter animal_id i.e animal.get_name(animal_id)
- First, it gets the get_name attribute from the object animal
- Then it calls it with the parameter animal_id.
When the method called doesn’t exist, Python fails at the first step when trying to get the attribute. In Python, when an attribute (in this case it is a method) is not found, it calls the python magic method __getattr__ (do not get confused with __getattribute__). To intercept the control we need to override __getattr__ within the class.
Let's implement the same HTML tag example in Python.
class HtmlTag(object):
OPEN_TAGS = ['img', 'input']
def __getattr__(self, name):
def _missing(*args, **kwargs):
if name in self.OPEN_TAGS:
return "<{}/>".format(name)
else:
return "<{0}> </{0}>".format(name)
return _missing
html = HtmlTag()
print(html.p())
print(html.img())
References:
- https://www.leighhalliday.com/ruby-metaprogramming-method-missing
- Python __getattr__ and __getattribute__