Python-эквивалент Ruby 'method_missing' - PullRequest
22 голосов
/ 15 июля 2011

Что является эквивалентом Python метода method_missing Руби? Я попытался использовать __getattr__, но этот хук относится и к полям. Я только хочу перехватить вызовы метода. Как Python это делает?

Ответы [ 4 ]

27 голосов
/ 15 июля 2011

В Python нет разницы между свойствами и методами.Метод - это просто свойство, тип которого просто instancemethod, который может вызываться (поддерживает __call__).

Если вы хотите реализовать это, ваш метод __getattr__ должен вернуть функцию (lambda или обычный def, в зависимости от ваших потребностей) и, возможно, что-то проверите после звонка.

4 голосов
/ 15 июля 2011

Python не различает методы и атрибуты (a.k.a. «переменные экземпляра»), как это делает Ruby. Методы и другие атрибуты объекта ищутся точно так же в Python - даже Python не знает разницу на этапе поиска. Пока атрибут не найден, это просто строка.

Итак, если вы спрашиваете, как обеспечить, чтобы __getattr__ был только вызванным для методов, я боюсь, что вы, вероятно, не найдете элегантного решения. Но достаточно просто вернуть функцию (или даже совершенно новый динамически связанный метод ) из __getattr__.

3 голосов
/ 31 августа 2013

Вы можете реализовать функцию, подобную пропущенному методу, следующим образом:

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()
1 голос
/ 07 октября 2012

Хотя я не рекомендую это !!!!!!!!!!!!!!!!!!!!!

этосвоего рода приближается к реализации поведения вызова специального метода для каждого имени, которое не соответствует вызываемому атрибуту / методу.Конечно, они все еще не имеют отдельных пространств имен, так что это может показаться немного странным.Он работает путем переопределения __getattribute__, который работает на более низком уровне, чем __getattr__, он пытается извлечь атрибут в случае неудачи, возвращает специальный карри-метод для вызова с именем, с которым вы его вызвали, если он успешен, он передает его, еслив противном случае он вызывает результат с помощью прокси-объекта, который впоследствии действует почти таким же образом, за исключением того, что он реализует вызов с вашим специальным методом.

Он не позволяет вам получить доступ к вызывающему объекту, потому что я не мог 'Не подумайте о хорошем способе сделать это без утечки памяти (вызывающего объекта), если это уже не подлежащий вызову атрибут, который вы сохраняете (единственное, о чем я могу подумать, - это запустить новый поток, который удаляет его через минутук тому времени вы, вероятно, уже назвали его, если не используете его в замыкании, которое не будет поддерживаться в этом случае).

Редактировать: я забыл, что вызываемое может иметь некоторые ложные срабатывания.

зависит от 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'}")
...