functools.partial on class method

Arjor picture Arjor · May 18, 2013 · Viewed 24.4k times · Source

I'm trying to define some class methods using another more generic class method as follows:

class RGB(object):
    def __init__(self, red, blue, green):
        super(RGB, self).__init__()
        self._red = red
        self._blue = blue
        self._green = green

    def _color(self, type):
        return getattr(self, type)

    red = functools.partial(_color, type='_red')
    blue = functools.partial(_color, type='_blue')
    green = functools.partial(_color, type='_green')

But when i attempt to invoke any of those methods i get:

rgb = RGB(100, 192, 240)
print rgb.red()
TypeError: _color() takes exactly 2 arguments (1 given)

I guess self is not passed to _color since rgb.red(rgb) works.

Answer

Martijn Pieters picture Martijn Pieters · May 18, 2013

You are creating partials on the function, not the method. functools.partial() objects are not descriptors, they will not themselves add the self argument and cannot act as methods themselves. You can only wrap bound methods or functions, they don't work at all with unbound methods. This is documented:

partial objects are like function objects in that they are callable, weak referencable, and can have attributes. There are some important differences. For instance, the __name__ and __doc__ attributes are not created automatically. Also, partial objects defined in classes behave like static methods and do not transform into bound methods during instance attribute look-up.

Use propertys instead; these are descriptors:

class RGB(object):
    def __init__(self, red, blue, green):
        super(RGB, self).__init__()
        self._red = red
        self._blue = blue
        self._green = green

    def _color(self, type):
        return getattr(self, type)

    @property
    def red(self): return self._color('_red')
    @property
    def blue(self): return self._color('_blue')
    @property
    def green(self): return self._color('_green')

As of Python 3.4, you can use the new functools.partialmethod() object here; it'll do the right thing when bound to an instance:

class RGB(object):
    def __init__(self, red, blue, green):
        super(RGB, self).__init__()
        self._red = red
        self._blue = blue
        self._green = green

    def _color(self, type):
        return getattr(self, type)

    red = functools.partialmethod(_color, type='_red')
    blue = functools.partialmethod(_color, type='_blue')
    green = functools.partialmethod(_color, type='_green')

but these'd have to be called, whilst the property objects can be used as simple attributes.