Python-эквивалент Ruby 'method_missing'
Что Python эквивалентно Ruby's method_missing
метод? Я пытался с помощью __getattr__
но этот хук относится и к полям. Я только хочу перехватить вызовы метода. Как Python способ сделать это?
5 ответов
В Python нет никакой разницы между свойствами и методами. Метод - это просто свойство, тип которого instancemethod
, что бывает вызываемым (поддерживает __call__
).
Если вы хотите реализовать это, ваш __getattr__
метод должен возвращать функцию (lambda
или обычный def
, что бы ни соответствовало вашим потребностям) и, возможно, что-то проверим после звонка.
Python не различает методы и атрибуты (так называемые "переменные экземпляра"), как это делает Ruby. Методы и другие атрибуты объекта в Python ищутся одинаково - даже Python не знает разницы на этапе поиска. Пока атрибут не найден, это просто строка.
Так что, если вы спрашиваете способ обеспечить __getattr__
вызывается только для методов, боюсь, вы, вероятно, не найдете элегантного решения. Но достаточно просто вернуть функцию (или даже совершенно новый динамически связанный метод) из __getattr__
,
Вы можете реализовать функцию, подобную пропущенному методу, следующим образом:
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__
он пытается извлечь атрибут, если ему это не удается, он возвращает специальный метод с карри для вызова с именем, с которым вы его вызвали, если он успешен, он передает его, если его вызывает, в противном случае он оборачивает результат прокси-объектом, который действует почти точно впоследствии то же самое, за исключением того, что он реализует вызов с вашим специальным методом.
Он не позволяет вам получить доступ к вызывающему объекту, потому что я не мог придумать хороший способ сделать это без какой-либо утечки памяти (вызывающего объекта), если это уже неотзываемый атрибут, который вы сохраняете (единственное, что я Можно подумать о том, чтобы запустить новый поток, который удаляет его через минуту, к тому времени вы, вероятно, уже вызвали его, если не используете его в закрытии, которое в этом случае не будет поддерживаться).
Редактировать: я забыл, что вызываемый может иметь некоторые ложные срабатывания.
зависит от библиотеки 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
объект, для которого:
- каждый несуществующий метод будет работать и возвращаться
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'