Я реализовал метакласс, который разбивает атрибуты класса для классов, созданных с его помощью, и создает методы из данных этих аргументов, а затем присоединяет эти динамически созданные методы непосредственно к объекту класса (рассматриваемый класс позволяет легко определять объекты веб-формы для использовать в рамках веб-тестирования). Он работал просто отлично, но теперь мне нужно добавить более сложный тип метода, который, чтобы поддерживать чистоту, я реализовал как вызываемый класс. К сожалению, когда я пытаюсь вызвать вызываемый класс в экземпляре, он обрабатывается как атрибут класса, а не как метод экземпляра, и при вызове получает только свой собственный 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, в основном декоратор, и это то, что я фактически добавляю к {{X4} }, но это кажется излишним и в основном просто более сложным способом сделать то же самое, что и (2)

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

5
Silas Ray 25 Янв 2013 в 20:58

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
5
kindall 25 Янв 2013 в 20:03

Проблема, с которой вы сталкиваетесь, заключается в том, что ваш объект не связан как метод класса 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
1
Blckknght 25 Янв 2013 в 17:49