J.Spiral
J.Spiral

Reputation: 475

What's the most pythonic way to write this filtered list comprehension?

Maybe i'm just up too late.

I have an object that is a thin wrapper around a dictionary. It will pretend to have a property for any key in the dictionary, and return None if a nonexistent key is referenced.

I want to get back only the unique, "truthy" values for three possible keys. (not None). the object may not have one or more of the keys. Or, it may have the same value in two or three of the keys.

This code does what I want:

set(getattr(obj, field) for field in ['field1', 'field2', 'field3'] if getattr(obj, field))

I just don't like the look of repeating getattr() twice. I feel like i'm overlooking an obviously better way to do this.

Upvotes: 2

Views: 669

Answers (4)

Inbar Rose
Inbar Rose

Reputation: 43467

another take on using the getattr default:

set(x for x in getattr(obj, field, None) for field in ['field1', 'field2', 'field3'] if x)

edit:

here is to show you the logic behind this function - and to show why i think it might be better than some of the other solutions (i could be wrong - but hey - life is all about learning from mistakes)

obj = Your_Object
fields = ['field1', 'field2', 'field3']

def get_set(obj, fields):
    result = []
    for field in fields:
        x = getattr(obj, field, None)
        if x:
            result.append(x)
    return set(result)

as you can see, there is only 1 loop, and getattr() is only called once for each field. and only "truthy" values are added to the result. i think this is a bit more efficient than getting all the results, and then removing the "non-truthy" values later. but please correct me if i am wrong. cheers.

Upvotes: 3

Jon Clements
Jon Clements

Reputation: 142206

Something like:

from operator import attrgetter

class DictWrapper(object):
    def __init__(self, d):
        self.d = d

    def __repr__(self):
        return repr(self.d)

    def __getattr__(self, name):
        return self.d.get(name)

    def truthy(self, *keys):
        values = attrgetter(*keys)(self)
        if not isinstance(values, tuple):
            values = (values,)
        return filter(None, set(values))

dw = DictWrapper({'x': 3, 'y': None, 'z': 'zebra'})
dw.truthy('x', 'y' ,'z', 'bob')
# [3, 'zebra']
dw.truthy('x')
# [3]
dw.truthy('y')
# []

Upvotes: 1

eumiro
eumiro

Reputation: 213005

If your thin wrapper returns default None:

s = set(getattr(obj, field) for field in ['field1', 'field2', 'field3']) - {None}

another possibility:

s = set(filter(None, (getattr(obj, field) for field in ['field1', 'field2', 'field3']))

Upvotes: 3

grc
grc

Reputation: 23575

You could filter out the None values afterwards:

set(filter(bool, [getattr(obj, field) for field in ['field1', 'field2', 'field3']]))

Or you could just forget about the object. This is probably the way I would do it:

a_dict = {'key1': 1, 'key2': 2, 'key3missing': 3}

print set([a_dict[key] for key in ['key1','key2','key3'] if key in a_dict])

# prints: set([1, 2])

Upvotes: 4

Related Questions