Python equivalent of Ruby's 'method_missing'

陌路散爱 提交于 2019-11-30 04:40:25

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.

senderle

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

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()

Although I don't recommend it!!!!!!!!!!!!!!!!!!!!!

this sort of comes closer to implementing the behavior of calling the special method for every name that does not correspond to a callable attribute/method. Of course they still don't really have separate namespaces so it may feel a bit weird. It works by overriding __getattribute__ which works at a lower level then __getattr__ it tries to fetch an attribute if it fails it returns a curried special method to call with the name you called it with, if it succeeds it passes it on if its callable otherwise it wraps the result with a proxy object which acts in almost exactly the same way afterwards except it implements call with your special method.

It doesn't allow you to access the calling object because I couldn't think of a good way to do that without sort of leaking memory(the calling object) if it's already a non-callable attribute which you store(the only think I can think of is to start a new thread that deletes it after a minute, by then you have presumably called it unless you are using it in a closure which wouldn't be supported in that case).

Edit: I forgot callable may have some false positives.

depends on the http://pypi.python.org/pypi/ProxyTypes library

from peak.util.proxies import ObjectWrapper
from functools import partial

def m(name, *args, **kwargs):
    print(name,repr(args),repr(kwargs))


class CallProxy(ObjectWrapper):
   def __init__(self, obj, m, method_name):
       ObjectWrapper.__init__(self, obj)
       object.__setattr__(self, "_method_name", method_name)
       object.__setattr__(self, "_m", m)

   def __call__(self, *args, **kwargs):
       return self._m(self._method_name, *args,**kwargs)


class Y(object):
   def __init__(self):
       self.x = [3]
   def __getattribute__(self, name):
       try:
           val = object.__getattribute__(self, name)
           if not callable(val):
               return CallProxy(val, m, name)
           else:
               return val
       except AttributeError:
           return partial(m, name)

In [2]: y=Y()

In [3]: y.x
Out[3]: [3]

In [4]: y.z
Out[4]: <functools.partial at 0x2667890>

In [5]: y.zz([12])
('zz', '([12],)', '{}')

In [6]: y.x.append(5)

In [7]: y.x
Out[7]: [3, 5]

In [8]: y.x(1,2,3,key="no")
('x', '(2, 3)', "{'key': 'no'}")
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!