I would like to use functools.partialmethod
on a classmethod. However the behavior I find is not what I would expect (and like to have).
Here is an example:
class A(object):
@classmethod
def h(cls, x, y):
print(cls, x, y)
class B(A):
h = functools.partialmethod(A.h, "fixed")
When I do
>>> b = B()
>>> b.h(3)
I get an error:
...
TypeError: h() takes 3 positional arguments but 4 were given
This is consistent with
>>> b.h()
<class '__main__.A'> <__main__.B object at 0x1034739e8> fixed
However, I would expect (and like to have) the following behavior:
>>> b.h(4)
<class '__main__.B'> fixed 4
I think that functools.partialmethod
treats B.h
as a normal instance method and passes the actual instance as first argument automatically.
But this behavior renders functools.partialmethod
useless for freezing arguments in classmethods of inheriting classes.
Without going into too much detail, the partial
object doesn't mix well with the descriptor protocol that @classmethod
utilizes to create a class instance. The simple fix is to just define your overridden method in the usual fashion:
class B(A):
@classmethod
def h(cls, y):
return A.h("fixed", y)
It might be possible to do what you want with some invocation of partial
, but I was unable to find it. Here are some of my attempts, and why they failed.
A.h
invokes the __get__
method of the function object, returning a function where the first argument is already bound to the calling class. partial
then applies that function to "fixed"
, but then the resulting callable still has a __get__
method that tries to insert the calling class into the resulting call. You might try to work around that by defining h
to actually be a static method:
class B(A):
h = staticmethod(partial(A.h, "fixed"))
>>> B.h(4)
<class '__main__.A'> fixed 4
But as you can see, you already froze the class argument when you call partial
. Another attempt is to avoid the descriptor protocol by accessing the argument directly:
class B(A):
h = staticmethod(partial(A.__dict__["h"], "fixed"))
but classmethod
objects aren't actually callable; only the return value of their __get__
methods is, so the call to partial
fails.