Python equivalent of Ruby's 'method_missing'

Question:

What is Python’s equivalent of Ruby’s method_missing method? I tried using __getattr__ but this hook applies to fields too. I only want to intercept the method invocations. What is the Python way to do it?

Asked By: missingfaktor

||

Answers:

Python doesn’t distinguish between methods and attributes (a.k.a. “instance variables”) the way Ruby does. Methods and other object attributes are looked up in exactly the same way in Python — not even Python knows the difference at the look-up stage. Until the attribute is found, it’s just a string.

So if you’re asking for a way to ensure that __getattr__ is only called for methods, I’m afraid you probably won’t find an elegant solution. But it’s easy enough to simply return a function (or even a brand-new dynamically bound method) from __getattr__.

Answered By: senderle

There is no difference in Python between properties and methods. A method is just a property, whose type is just instancemethod, that happens to be callable (supports __call__).

If you want to implement this, your __getattr__ method should return a function (a lambda or a regular def, whatever suite your needs) and maybe check something after the call.

Answered By: Emil Ivanov

You could implement a missing_method like feature in the below way:

https://gist.github.com/gterzian/6400170

import unittest
from functools import partial

class MethodMissing:
    def method_missing(self, name, *args, **kwargs):
        '''please implement'''
        raise NotImplementedError('please implement a "method_missing" method')

    def __getattr__(self, name):
        return partial(self.method_missing, name)


class Wrapper(object, MethodMissing):
    def __init__(self, item):
        self.item = item

    def method_missing(self, name, *args, **kwargs):
        if name in dir(self.item):
            method = getattr(self.item, name)
            if callable(method):
                return method(*args, **kwargs)
            else:
                raise AttributeError(' %s has not method named "%s" ' % (self.item, name))


class Item(object):
    def __init__(self, name):
        self.name = name

    def test(self, string):
        return string + ' was passed on'


class EmptyWrapper(object, MethodMissing):
    '''not implementing a missing_method'''
    pass


class TestWrapper(unittest.TestCase):
    def setUp(self):
        self.item = Item('test')
        self.wrapper = Wrapper(self.item)
        self.empty_wrapper = EmptyWrapper()

    def test_proxy_method_call(self):
        string = self.wrapper.test('message')
        self.assertEqual(string, 'message was passed on')

    def test_normal_attribute_not_proxied(self, ):
        with self.assertRaises(AttributeError):
            self.wrapper.name
            self.wrapper.name()

    def test_empty_wrapper_raises_error(self, ):
        with self.assertRaises(NotImplementedError):
            self.empty_wrapper.test('message')


if __name__ == '__main__':
    unittest.main()
Answered By: gterzian

This solution creates a Dummy object for which:

  • every non-existing method will work and return None (even if you pass parameters)
  • you can also provide a dictionary with methodname: defaultvalue for methods that should return a specific default value
class Dummy:
    def __init__(self, methods):
        self.methods = methods

    def __getattr__(self, attr):
        defaultvalue = self.methods[attr] if attr in self.methods else None
        return lambda *args, **kwargs: defaultvalue

    def baz(self):
        return 'custom'

d = Dummy({'foo': 1234})

print(d.foo())                        # 1234
print(d.bar(1, 2, x=123, y=456))      # None
print(d.kjfdhgjf())                   # None
print(d.baz())                        # 'custom'
Answered By: Basj