Search code examples
python-3.xfunctools

Using `functools.partial` and `map` with built-in `getattr`?


I apologize if am completely missing something obvious or if I have not dug into the documentation hard enough, but after 30 mins or so I found a work around (without having understood the error I was getting) and ... hence the question here. Suppose I have a class:

class RGB(object):
    def __init__(self, r, g, b):
        super(RGB, self).__init__()
        self.red = r
        self.blue = b
        self.green = g

and I define a list of RGB instances as follows:

from random import random
rr, gg, bb = [[random() for _ in range(20)] for _ in range(3)]
list_of_rgbs = [RGB(*item) for item in zip(rr, gg, bb)]

why can't I extract a list of red values by doing:

from functools import partial
*reds, = map(partial(getattr, name="red"), list_of_rgbs)

or

*reds, = map(partial(getattr, "red"), list_of_rgbs)

I know I can make it do what I want by saying reds = [x.red for x in list_of_rbgs] but that would be difficult if the list of attributes to extract comes from elsewhere like: attribs_to_get = ['red', 'blue']. In this particular case I can still do what I want by:

reds, blues = [[getattr(x, attrib) for x in list_of_rgbs] for attrib in attribs_to_get]

but my question is about what causes the error. Can someone explain why, or how to make it work using partial and map? I have a hunch it has something to do with this behavior (and so maybe the partial function needs a reference to self?) but I can't quite tease it out.

For reference I was on Python 3.7.


Solution

  • Partial can only set positional arguments starting at the first argument. You can't set the second argument as positional, but only as a keyword argument. As the first one for getattr is the object, it won't work well together with map and partial.

    What you can use however is operator.attrgetter():

    from operator import attrgetter
    *reds, _ = map(attrgetter("red"), list_of_rgbs)