31

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?

dreftymac
  • 31,404
  • 26
  • 119
  • 182
missingfaktor
  • 90,905
  • 62
  • 285
  • 365

4 Answers4

30

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.

Emil Ivanov
  • 37,300
  • 12
  • 75
  • 90
  • 1
    Thanks. I found [this](http://venodesigns.net/2010/11/02/emulating-rubys-method_missing-in-python/) on a little googling. – missingfaktor Jul 15 '11 at 08:39
  • 2
    For reference, the link in question gets around the ambiguity by defining a list of attribute names that should be considered methods (which sounds like it kinda defeats the purpose, since you could just _define_ each of those methods and delegate to a stub). – Mu Mind Oct 07 '12 at 16:23
5

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__.

Community
  • 1
  • 1
senderle
  • 145,869
  • 36
  • 209
  • 233
  • 1
    For the same reason you would use `method_missing` in Ruby or `doesNotUnderstand` in Smalltalk. – missingfaktor Jul 15 '11 at 08:37
  • I understand why you would want to use `__getattr__`. I just don't understand why you "only want to intercept the method invocations". – senderle Jul 15 '11 at 08:44
  • My design didn't require intercepting field access, but it doesn't seem to be a big stumbling block. I find the solution provided by @Emil good enough for my requirements. – missingfaktor Jul 15 '11 at 08:53
  • 2
    Ruby does not distinguish between methods and attributes at all - there is no such thing as an attribute in Ruby. – steenslag Jul 15 '11 at 17:00
  • 1
    @steenslag, that sounds to me like a really bizarre claim. When I say "attribute" I mean "internal state". Are you claiming that objects in Ruby have no internal state? Or do you mean that the internal state of objects in Ruby is always private? That's true. I suppose in Ruby-speak an attribute is really an accessor method of an "instance variable". But since we're talking about Python, I'm using Python-speak. – senderle Jul 15 '11 at 17:19
  • 1
    In Ruby terms, Python's `__getattr__` is somewhere between `method_missing` and overriding `Hash#[]` to do something special for missing keys. – Mark Reed Aug 31 '13 at 20:18
3

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()
gterzian
  • 535
  • 6
  • 5
0

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'
Basj
  • 41,386
  • 99
  • 383
  • 673