динамическое добавление вызываемого объекта в класс как метод экземпляра

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

class Foo(object):
    def __init__(self, name, val):
        self.name = name
        self.val = val
        self.__name__ = name + '_foo'
        self.name = name
    # This doesn't work as I'd wish
    def __call__(self, instance):
        return self.name + str(self.val + instance.val)

def get_methods(name, foo_val):
    foo = Foo(name, foo_val)
    def bar(self):
        return name + str(self.val + 2)
    bar.__name__ = name + '_bar'
    return foo, bar

class Baz(object):
    def __init__(self, val):
        self.val = val

for method in get_methods('biff', 1):
    setattr(Baz, method.__name__, method)
baz = Baz(10)
# baz.val == 10
# baz.biff_foo() == 'biff11'
# baz.biff_bar() == 'biff12'

Я подумал о:

  1. Использование дескриптора, но это кажется более сложным, чем здесь необходимо
  2. Использование замыкания внутри фабрики для foo, но вложенные замыкания в большинстве случаев являются уродливыми и грязными заменами объектов, imo
  3. Оборачивая экземпляр Foo в метод, который передает его self вниз экземпляру Foo как instance, в основном декоратор, это то, что я на самом деле добавляю к Baz, но это кажется излишним и в основном просто более сложным способом сделать то же самое, что и ( 2)

Есть ли лучший способ, чем любой из них, чтобы попытаться выполнить то, что я хочу, или я должен просто укусить пулю и использовать какой-то шаблон типа фабрики закрытия?


person Silas Ray    schedule 25.01.2013    source источник


Ответы (2)


Один из способов сделать это — присоединить вызываемые объекты к классу как несвязанные методы. Конструктор метода будет работать с произвольными вызываемыми объектами (т. е. с экземплярами классов с методом __call__()), а не только с функциями.

from types import MethodType

class Foo(object):
    def __init__(self, name, val):
        self.name = name
        self.val = val
        self.__name__ = name + '_foo'
        self.name = name
    def __call__(self, instance):
        return self.name + str(self.val + instance.val)

class Baz(object):
    def __init__(self, val):
        self.val = val

Baz.biff = MethodType(Foo("biff", 42), None, Baz)

b = Baz(13)
print b.biff()
>>> biff55

В Python 3 нет такой вещи, как несвязанный экземпляр (к классам просто присоединены обычные функции), поэтому вы можете вместо этого сделать свой класс Foo дескриптором, который возвращает связанный метод экземпляра, предоставив ему метод __get__(). (На самом деле этот подход будет работать и в Python 2.x, но приведенный выше вариант будет работать немного лучше.)

from types import MethodType

class Foo(object):
    def __init__(self, name, val):
        self.name = name
        self.val = val
        self.__name__ = name + '_foo'
        self.name = name
    def __call__(self, instance):
        return self.name + str(self.val + instance.val)
    def __get__(self, instance, owner):
        return MethodType(self, instance) if instance else self
        # Python 2: MethodType(self, instance, owner)

class Baz(object):
    def __init__(self, val):
        self.val = val

Baz.biff = Foo("biff", 42)

b = Baz(13)
print b.biff()
>>> biff55
person kindall    schedule 25.01.2013
comment
Это не работает в Python 3, поскольку несвязанных методов больше не существует (MethodType принимает только два аргумента, а второй не должен быть None). - person Blckknght; 25.01.2013
comment
Я думаю, что обновлял это, пока вы писали этот комментарий. :-) - person kindall; 25.01.2013
comment
Я уже писал дескрипторы раньше, но, думаю, в данном случае дескриптор не такой уж и беспорядочный, на самом деле... Я раньше тыкал в MethodType, но, честно говоря, меня это всегда немного пугало, потому что это казалось таким простым. сломать, что с необходимостью в основном инициализировать его пространство имен и еще много чего. В итоге я реализовал другое закрытие, но я мог бы вернуться и посмотреть на замену его этим. Спасибо. - person Silas Ray; 25.01.2013
comment
MethodType на самом деле не так уж и сложно. Вам нужна функция (или другой вызываемый объект), экземпляр (если вы создаете метод экземпляра; в Python 3 это единственный тип, который вы можете создать) и тип (в Python 2.x; в Python 3 он получает тип из экземпляра). Вот и все. - person kindall; 31.01.2013

Проблема, с которой вы сталкиваетесь, заключается в том, что ваш объект не связан как метод класса Baz, в который вы его помещаете. Это потому, что это не дескриптор, что такое обычные функции!

Вы можете исправить это, добавив простой метод __get__ в ваш класс Foo, который превращает его в метод, когда к нему обращаются как к дескриптору:

import types

class Foo(object):
    # your other stuff here

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self # unbound
        else:
            return types.MethodType(self, obj) # bound to obj
person Blckknght    schedule 25.01.2013
comment
Я не думаю, что вам нужен if/else в этом, так как __get__() не будет вызываться, когда экземпляр Foo все равно будет доступен через класс. - person kindall; 25.01.2013
comment
@kindall: если у меня нет блока if, я получаю исключение, когда обращаюсь к экземпляру Foo через класс: TypeError: self must not be None. Это привязка классов, которая все еще существует в Python 3, хотя функции больше не используют ее для создания несвязанных методов. - person Blckknght; 25.01.2013