Python中与Ruby的'method_missing'相当的是什么?

31

Python中类似于Ruby的method_missing方法的等效方法是什么?我尝试使用__getattr__,但此钩子也适用于字段。我只想拦截方法调用。Python的解决方案是什么?

4个回答

30

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

如果您想实现此操作,您的 __getattr__ 方法应该返回一个函数(一个lambda或一个常规的def,无论哪种都可以满足您的需求),并在调用后可能会检查一些内容。


1
谢谢。我在网上搜索时找到了这个 - missingfaktor
2
参考链接通过定义一组应被视为方法的属性名称来解决歧义问题(这似乎有点违背初衷,因为你可以直接定义这些方法并委托给一个存根)。 - Mu Mind

5

Python不像Ruby一样区分方法和属性(也称为“实例变量”)。在Python中,方法和其他对象属性的查找方式完全相同 - 即使是Python在查找阶段也无法区分它们。在找到属性之前,它只是一个字符串。

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


1
出于相同的原因,您会在Ruby中使用method_missing或者在Smalltalk中使用doesNotUnderstand - missingfaktor
我理解你为什么想使用__getattr__。我只是不明白为什么你“只想拦截方法调用”。 - senderle
我的设计不需要拦截字段访问,但这似乎并不是一个大的障碍。我认为@Emil提供的解决方案已经足够满足我的需求了。 - missingfaktor
2
Ruby在方法和属性方面没有任何区别 - 在Ruby中根本不存在属性这种东西。 - steenslag
1
@steenslag,这对我来说听起来像是一个非常奇怪的说法。当我说“属性”时,我的意思是“内部状态”。你是否声称Ruby中的对象没有内部状态?或者你是指Ruby对象的内部状态总是私有的?这是真的。我想在Ruby语言中,“属性”实际上是“实例变量”的访问器方法。但既然我们正在谈论Python,我使用Python语言。 - senderle
1
用 Ruby 的术语来说,Python 的 __getattr__ 介于 method_missing 和覆盖 Hash#[] 以为缺失的键执行特殊操作之间。 - Mark Reed

3
您可以通过以下方式实现类似于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()

0

这个解决方案创建了一个 Dummy 对象,其中:

  • 每个不存在的方法都可以工作并返回 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'

1
defaultvalue = self.methods.get(attr) - Kelly Bundy
你是对的,随意编辑 @KellyBundy! - Basj
注意,这并不是传统意义上的Dummy(可以参考https://tanzu.vmware.com/developer/guides/test-doubles/#dummy)。 - jonrsharpe
@jonrsharpe 我使用“虚拟”的术语是指它是一个什么也不做,只为每个方法调用回答默认值的类。 - Basj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接