вызываемый как метод экземпляра?
Вопрос
Допустим, у нас есть метакласс CallableWrappingMeta
который проходит тело нового класса, обертывая его методы классом, InstanceMethodWrapper
:
import types
class CallableWrappingMeta(type):
def __new__(mcls, name, bases, cls_dict):
for k, v in cls_dict.iteritems():
if isinstance(v, types.FunctionType):
cls_dict[k] = InstanceMethodWrapper(v)
return type.__new__(mcls, name, bases, cls_dict)
class InstanceMethodWrapper(object):
def __init__(self, method):
self.method = method
def __call__(self, *args, **kw):
print "InstanceMethodWrapper.__call__( %s, *%r, **%r )" % (self, args, kw)
return self.method(*args, **kw)
class Bar(object):
__metaclass__ = CallableWrappingMeta
def __init__(self):
print 'bar!'
Наша фиктивная оболочка просто печатает аргументы по мере их поступления.Но вы заметите кое-что примечательное:метод не передается получателю объекта-экземпляра, потому что, хотя InstanceMethodWrapper
является вызываемым, он не рассматривается как функция с целью преобразования в метод экземпляра во время создания класса (после того, как наш метакласс закончил с ним работу).
Потенциальное решение — использовать декоратор вместо класса для обертывания методов — эта функция станет методом экземпляра.Но в реальном мире InstanceMethodWrapper
гораздо сложнее:он предоставляет API и публикует события вызова методов.Класс более удобен (и более производительен, но это не имеет большого значения).
Я также попробовал несколько тупиков.Подклассы types.MethodType
и types.UnboundMethodType
никуда не пошел.Небольшой самоанализ, и оказывается, что они произошли от type
.Поэтому я попробовал использовать оба в качестве метакласса, но и здесь безуспешно.Возможно, у них как у метакласса есть особые требования, но, похоже, на данный момент мы находимся на недокументированной территории.
Есть идеи?
Решение
Просто обогащаю тебя InstanceMethodWrapper
класс с __get__
(что вполне может просто return self
) - то есть превратить этот класс в дескриптор type, так что его экземпляры являются объектами дескриптора.Видеть http://users.rcn.com/python/download/Descriptor.htm для фона и деталей.
Кстати, если вы используете Python 2.6 или более позднюю версию, рассмотрите возможность использования декоратора класса вместо этого метакласса — мы добавили декораторы классов именно потому, что так много метаклассов использовалось именно для таких целей оформления, а декораторы действительно намного проще использовать. .
Другие советы
Редактировать:Я в очередной раз лгу.А __?attr__
атрибуты функций доступны только для чтения, но, видимо, не всегда выдают AttributeException
исключение, когда вы назначаете?Я не знаю.В исходную точку!
Редактировать:На самом деле это не решает проблему, поскольку функция упаковки не передает запросы атрибутов InstanceMethodWrapper
.Я мог бы, конечно, ударить __?attr__
атрибуты в декораторе — и это то, что я сейчас делаю — но это некрасиво.Лучшие идеи приветствуются.
Конечно, я сразу понял, что объединение простого декоратора с нашими классами даст результат:
def methodize(method, callable):
"Circumvents the fact that callables are not converted to instance methods."
@wraps(method)
def wrapper(*args, **kw):
return wrapper._callable(*args, **kw)
wrapper._callable = callable
return wrapper
Затем вы добавляете декоратор к вызову InstanceMethodWrapper
в метаклассе:
cls_dict[k] = methodize(v, InstanceMethodWrapper(v))
Пуф.Немного косо, но работает.
Я предполагаю, что вы пытаетесь создать метакласс, который обертывает каждый метод класса специальной функцией.
Вот моя версия, которая, на мой взгляд, немного менее уклончива.
import types
class CallableWrappingMeta(type):
def __new__(mcls, name, bases, cls_dict):
instance = type.__new__(mcls, name, bases, cls_dict)
for k in dir(instance):
v = getattr(instance, k)
if isinstance(v, types.MethodType):
setattr(instance, k, instanceMethodWrapper(v))
return instance
def instanceMethodWrapper(function):
def customfunc(*args, **kw):
print "instanceMethodWrapper(*%r, **%r )" % (args, kw)
return function(*args, **kw)
return customfunc
class Bar(object):
__metaclass__ = CallableWrappingMeta
def method(self, a, b):
print a,b
a = Bar()
a.method("foo","bar")
Я думаю, вам нужно более конкретно рассказать о своей проблеме.В исходном вопросе говорится об обертке функции, но ваш последующий ответ, похоже, говорит о сохранении атрибутов функции, что, по-видимому, является новым фактором.Если вы более четко сформулируете цели своего дизайна, возможно, вам будет легче ответить на ваш вопрос.