2016-09-05 12 views
1

Я хотел был бы использовать functools.partialmethod на classmethod. Однако поведение, которое я нахожу, не то, что я ожидал бы (и хотел бы иметь). Вот пример:Комбинация functools.partialmethod и classmethod

class A(object): 
    @classmethod 
    def h(cls, x, y): 
     print(cls, x, y) 

class B(A): 
    h = functools.partialmethod(A.h, "fixed") 

Когда я

>>> b = B() 
>>> b.h(3) 

Я получаю сообщение об ошибке:

... 
TypeError: h() takes 3 positional arguments but 4 were given 

Это согласуется с

>>> b.h() 
<class '__main__.A'> <__main__.B object at 0x1034739e8> fixed 

Однако я ожидать (и li ка иметь) следующее поведение:

>>> b.h(4) 
<class '__main__.B'> fixed 4 

Я думаю, что functools.partialmethod лечит B.h как обычный метод экземпляра и автоматически передает фактический экземпляр в качестве первого аргумента. Но это поведение делает functools.partialmethod бесполезным для замораживания аргументов в классах наследующих классов.

+1

'' 'b.h()' '' или '' 'b.c()' ''? – wwii

+0

Вы хотите переопределить метод класса в родительском классе посредством * fix * * одного из своих аргументов? – wwii

+0

Будут ли все параметры позиционными или ключевыми словами или миксами? – wwii

ответ

2

Не вдаваясь в подробности, объект partial не очень хорошо сочетается с протоколом дескриптора, который использует @classmethod для создания экземпляра класса. Простое исправление, это просто определить ваш перекрытый метод обычного способа:

class B(A): 
    @classmethod 
    def h(cls, y): 
     return A.h("fixed", y) 

Это может можно делать то, что вы хотите с каким-то вызовом partial, но я не смог найти его. Вот некоторые из моих попыток и почему они потерпели неудачу.

A.h вызывает метод объектного объекта __get__, возвращая функцию, в которой первый аргумент уже привязан к вызывающему классу. partial затем применяет эту функцию к "fixed", но тогда получаемый вызываемый еще имеет метод __get__, который пытается вставить вызывающий класс в результирующий вызов. Вы могли бы попытаться обойти это, определив h на самом деле быть метод статический:

class B(A): 
    h = staticmethod(partial(A.h, "fixed")) 

>>> B.h(4) 
<class '__main__.A'> fixed 4 

Но, как вы видите, вы уже заморозили класс аргумент при вызове partial. Еще одна попытка, чтобы избежать протокола дескриптора путем доступа аргумент непосредственно:

class B(A): 
    h = staticmethod(partial(A.__dict__["h"], "fixed")) 

но classmethod объекты являются на самом деле не отозваны; только возвращаемое значение их методов __get__, так что вызов partial терпит неудачу.

+0

Явное вызов 'A.c' в перегрузке' B' - это плохая форма, так как вы потеряете информацию об «истинном» классе (и методы класса обычно являются альтернативными конструкторами, поэтому потеря этой информации является проблемой). Измените его на 'return super(). C (" fixed ", y)', чтобы он прошел правильный класс (и правильно обрабатывает множественное наследование). – ShadowRanger

+0

'super' подразумевает, что вы * всегда * собираетесь использовать' super' в родительском классе, и вы всегда будете использовать 'super' в любых дочерних классах. Это не так просто, как «эй, мне не нужно жестко закодировать имя моего родительского класса». Вы не можете просто сказать, что одно правильно, другое неверно; оба имеют свои преимущества, преимущества и недостатки. – chepner

+0

Это действительно так просто в 99% случаев (другие 1% обычно являются злоупотреблениями «classmethod'). 'A.c' передает' A' как 'cls',' super(). C' передает 'B' как' cls'. Если вы используете первое, то украшение '@ classmethod' не имеет никакой цели, кроме как позволить вам сказать' cls' вместо 'A' в методе; в противном случае это просто '@ staticmethod', потому что вы выбросили один кусок уникальной информации, которую предоставляет' @ classmethod'. Вы больше не используете какую-либо полезную функцию 'B', чтобы простая функция, вызывающая' A', не обеспечивала бы одинаково хорошо. – ShadowRanger

1

Использование super (v2.7)

class A(object): 
    @classmethod 
    def c(cls, x, y): 
     print('cls:{}, x:{}, y:{}'.format(cls, x, y)) 

class B(A): 
    def c(self, y): 
     super(B, self).c(x = 'fixed', y = y) 

class C(A): 
    def c(self, x): 
     super(C, self).c(x = x, y = 'fixed') 

b = B() 
c = C() 

>>> 
>>> b.c(4) 
cls:<class '__main__.B'>, x:fixed, y:4 
>>> c.c(4) 
cls:<class '__main__.C'>, x:4, y:fixed 
>>> 

super is super

+0

FYI, Python 3 (в этом вопросе отмечен) не требует явных аргументов для 'super' в большинстве случаев. Это намного проще (и [нарушение DRY] (https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)), чтобы просто вызвать 'super(). C'. – ShadowRanger

+0

Я действительно хочу избежать явного переопределения. В моем случае есть гораздо больше аргументов на месте y и набирать их все очень склонно к ошибкам и слишком много работы. Я мог бы использовать синтаксис 'h (x, * args)', но тогда эта информация отсутствует в документах. – Thomasillo

+0

@ShadowRanger ... У меня здесь всего 2,7, поэтому я не мог тестировать/играть в 3.x, вот почему я выбрал версию. – wwii