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?
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.
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__
.
method_missing
in Ruby or doesNotUnderstand
in Smalltalk. –
Clemence __getattr__
. I just don't understand why you "only want to intercept the method invocations". –
Lashonlashond __getattr__
is somewhere between method_missing
and overriding Hash#[]
to do something special for missing keys. –
Chrystel 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()
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'
defaultvalue = self.methods.get(attr)
–
Westberry © 2022 - 2024 — McMap. All rights reserved.