Создайте метод экземпляра в метаклассе, используя частичный в Python 3 - PullRequest
7 голосов
/ 02 марта 2011

Используя метаклассы, я пытаюсь создать метод экземпляра, упростив существующий метод экземпляра.Проблема в том, что частичное не работает с методом экземпляра.Это простой пример того, чего я пытаюсь достичь:

from functools import partial

class Aclass(object):

    def __init__(self, value):
        self._value = value

    def complex(self, a, b):                                            
        return a + b + self._value

class Atype(type):

    def __new__(cls, name, bases, attrs):
        return super(Atype, cls).__new__(cls, name, (Aclass, ) + bases, attrs)

    def __init__(cls, name, bases, attrs):
        setattr(cls, 'simple', partial(cls.complex, b=1))

class B(metaclass=Atype):
    pass

b = B(10)

print(b.complex(1, 2))
print(b.simple(1))

и вывод:

13
Traceback (most recent call last):
  File "metatest.py", line 22, in <module>
    print(b.simple(1))
TypeError: complex() takes exactly 3 non-keyword positional arguments (1 given)

Я решил использовать лямбда-изменения:

    setattr(cls, 'simple', partial(cls.complex, b=1))

to:

    setattr(cls, 'simple', lambda self, x: cls.complex(self, x, b=1))

но он уродлив и имеет проблемы с необязательными параметрами.

Я мог бы создать этот метод в экземпляре __init__, но я думаю, что это имеет больше смысла, и этоболее эффективно делать это в классе __init__, используя метаклассы.

Есть идеи, как это сделать правильно?

Ответы [ 3 ]

5 голосов
/ 02 марта 2011

Ну, я немного не знаком с обработкой методов Python 3 - самое простое, о чем я мог подумать, это переписать partial, чтобы он сохранил первый аргумент из исходного вызова, а затем вставил «частичные» параметры.

Это работает с вашим примером, но требует тестирования с более сложными шаблонами.

from functools import wraps

class Aclass(object):
    def __init__(self, value):
        self._value = value

    def complex(self, a, b):                                            
        return a + b + self._value

def repartial(func, *parameters, **kparms):
    @wraps(func)
    def wrapped(self, *args, **kw):
        kw.update(kparms)
        return func(self, *(args + parameters), **kw)
    return wrapped

class Atype(type):
    def __new__(cls, name, bases, attrs):
        return super(Atype, cls).__new__(cls, name, (Aclass, ) + bases, attrs)

    def __init__(cls, name, bases, attrs):
        setattr(cls, 'simple', repartial(cls.complex, b=1))

class B(metaclass=Atype):
    pass

b = B(10)

print(b.complex(1, 2))
print(b.simple(1))
3 голосов
/ 02 марта 2011

Вместо использования partial я бы просто определил класс Atype следующим образом:

class Atype(type):

    def __new__(cls, name, bases, attrs):
        return super(Atype, cls).__new__(cls, name, (Aclass, ) + bases, attrs)

    def __init__(cls, name, bases, attrs):
        def simple(self, a):
            return cls.complex(self, a, 1)
        setattr(cls, 'simple', simple)

Метод __init__() также можно записать более компактно:

def __init__(cls, name, bases, attrs):
    setattr(cls, 'simple', lambda self, a: cls.complex(self, a, 1))
1 голос
/ 02 марта 2011

Проблема в том, что объект, возвращаемый functools.partial(), является вызываемым объектом, а не функцией. Таким образом, очевидно, что Python не заботится о том, чтобы не-функция пыталась вести себя как одна в этом контексте. Одним из решений является создание функции в качестве оболочки для объекта partial.

class Atype(type):

    def __init__(cls, name, bases, attrs):
        simple = partial(cls.complex, b=1)
        setattr(cls, 'simple', lambda cls, a: simple(cls, a))

Решение jsbueno (переопределение partial, которое возвращает реальную функцию) все же хорошо. Я действительно не знаю, почему functools.partial() не работает таким образом; неспособность использовать это в этом контексте - удивительная ловушка.

...