繁体   English   中英

Python 相当于 Ruby 的“method_missing”

[英]Python equivalent of Ruby's 'method_missing'

Python 与 Ruby 的method_missing方法等价的是什么? 我尝试使用__getattr__但这个钩子也适用于字段。 我只想拦截方法调用。 Python 的方法是什么?

Python 在属性和方法之间没有区别。 方法只是一个属性,其类型只是instancemethod ,恰好是可调用的(支持__call__ )。

如果你想实现这个,你的__getattr__方法应该返回一个 function (一个lambda或一个常规的def ,无论你需要什么),并可能在通话后检查一些东西。

Python 不像 Ruby 那样区分方法和属性(也称为“实例变量”)。 在 Python 中以完全相同的方式查找方法和其他 object 属性——甚至 Python 都不知道查找阶段的区别。 在找到属性之前,它只是一个字符串。

因此,如果您要寻求一种方法来确保__getattr__被方法调用,恐怕您可能找不到优雅的解决方案。 但是很容易从__getattr__返回一个 function (甚至是一个全新的动态绑定方法)。

您可以通过以下方式实现类似 missing_method 的功能:

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

虽然不推荐!!!!!!!!!!!!!!!!!!

这种更接近于实现为每个不对应于可调用属性/方法的名称调用特殊方法的行为。 当然,它们仍然没有真正的独立命名空间,所以可能感觉有点奇怪。 它通过覆盖__getattribute__来工作,后者在较低级别上工作,然后__getattr__如果失败,它会尝试获取属性用代理 object 包装结果,它之后的行为几乎完全相同,只是它使用您的特殊方法实现调用。

它不允许您访问调用 object 因为如果它已经是您存储的不可调用属性(我唯一认为可以想到的是启动一个新线程,在一分钟后将其删除,到那时您可能已经调用了它,除非您在这种情况下不支持的闭包中使用它)。

编辑:我忘了 callable 可能有一些误报。

取决于http://pypi.python.org/pypi/ProxyTypes

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'}")

此解决方案创建了一个Dummy object,其:

  • 每个不存在的方法都将起作用并返回None (即使您传递参数)
  • 您还可以提供带有方法名的字典methodname: defaultvalue应该返回特定默认值的方法的默认值
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'

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM