74

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.

6
  • This is a useful question, and I don't want to detract from the main point, but as a side-issue question on your code I was wondering why you wrote super(RGB, self).__init__(), when RGB inherits from object? Commented Mar 20, 2019 at 10:23
  • @CaptainLepton It's good practice, in case someday somebody adds a superclass. Commented Jun 14, 2019 at 2:02
  • @michaelb958--GoFundMonica you meant making RGB superclass to a newer written subclass? Commented Mar 16, 2021 at 8:54
  • @deadvoid he means if someone edits the line class RGB(object) to instead say something like class RGB(Other class), then the __init__ is still correct. Of course the tradeoff is that super(RGB, self) breaks if you instead edit it to class Other name(object). Of course Python 3 fixed that by letting you just call super(). Commented May 25, 2022 at 17:34
  • @CaptainLepton the biggest reason why it's good practice is that it makes your class actually work right if someone wants to multiply inherit from both your class and another class. See this answer. Commented May 25, 2022 at 17:42

2 Answers 2

82

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.

Sign up to request clarification or add additional context in comments.

What about self.red = functools.partial(RGB._color, self, 'red') in __init__? It is Python2 compatible too.
@dashesy: sure, but that puts those objects on each instance (a memory cost), also making it harder for a subclass to replace them.
It seems to work if I create a partial from the _color() method from an instance of RGB?
@VictorCui: then you created a partial for a bound method, and it'll remain bound to the specific instance. That's not the same thing as creating a partialmethod that then re-binds self to the current instance.
@VictorCui exactly
10

The issue with partialmethod is that it is not compatible with inspect.signature, functools.wraps,...

Weirdly enough, if you re-implement functools.partial yourself using the partial documentation implementation example, it will work:

# Implementation from:
# https://docs.python.org/3/library/functools.html#functools.partial
def partial(func, /, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = {**keywords, **fkeywords}
        return func(*args, *fargs, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc
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 = partial(_color, type='_red')
    blue = partial(_color, type='_blue')
    green = partial(_color, type='_green')

rgb = RGB(100, 192, 240)
print(rgb.red())  # Print red

The reason is that newfunc is a true function which implement the descriptor protocol with newfunc.__get__. While type(functools.partial) is a custom class with __call__ overwritten. Class won't add the self parameter automatically.

lmao nice find, that means functools.partial kinda breaks the zen of python lol. if it just respected ducktyping it would all work fine anyways

Your Answer

Draft saved
Draft discarded

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.