вызывается как метод экземпляра? - PullRequest
2 голосов
/ 03 мая 2009

Допустим, у нас есть метакласс 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. Поэтому я попытался использовать оба в качестве метакласса, но и там мне не повезло. Возможно, они имеют особые требования в качестве метакласса, но, похоже, на данный момент мы находимся на недокументированной территории.

Есть идеи?

Ответы [ 4 ]

3 голосов
/ 03 мая 2009

Просто обогатите свой класс InstanceMethodWrapper с помощью __get__ (который вполне может просто return self) - то есть превратите этот класс в тип дескриптор , чтобы его экземпляры были дескриптором объекты. См. http://users.rcn.com/python/download/Descriptor.htm для справки и деталей.

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

0 голосов
/ 03 мая 2009

Я предполагаю, что вы пытаетесь создать метакласс, который обертывает каждый метод в классе с помощью пользовательской функции.

Вот моя версия, которая, на мой взгляд, немного менее косна.

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")
0 голосов
/ 03 мая 2009

Я думаю, вам нужно быть более конкретным в своей проблеме. Первоначальный вопрос говорит о переносе функции, но ваш последующий ответ говорит о сохранении атрибутов функции, что представляется новым фактором. Если вы изложите свои цели в области дизайна более четко, возможно, будет проще ответить на ваш вопрос.

0 голосов
/ 03 мая 2009

Редактировать : Я снова вру. Атрибуты __?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))

Пуф. Немного косо, но это работает.

...