tags:

views:

283

answers:

2

I have some custom objects and dictionaries that I want to sort. I want to sort both the objects the dictionaries together. I want to sort the objects by an attribute and the dictionaries by a key.

object.name = 'Jack'
d = {'name':'Jill'}

sort_me =[object, d]

How do I sort this list using the object's name attribute and the dictionary's 'name' key?

+8  A: 

What you are almost certainly looking for is to use the key= option for sorted(), which provides a function which returns an arbitrary sort key for each element. This function can check the type of its argument and take various actions. For instance:

import types

class obj(object):
    def __init__(self, arg):
        self.name = arg

def extract_name(obj):
    if type(obj) is types.DictType:
        return obj['name']
    else:
        return obj.__dict__['name']

d = { 'name': 'Jill'}    
print sorted([obj('Jack'), d], key=extract_name)

More information can be found on the Python wiki

RichieHindle's suggestion of using isinstance is a good one. And while I was at it I thought it might be nice to support arbitrary element names instead of hardcoding 'name':

def extract_elem_v2(elem_name):
    def key_extractor(obj):
        dct = obj if isinstance(obj, dict) else obj.__dict__
        return dct[elem_name]
    return key_extractor

Which you can use like so:

print sorted(list_of_stuff, key=extract_elem_v2('name'))
Jack Lloyd
+1. Minor suggestion: `isinstance(obj, dict)` would be neater, and would allow for classes derived from `dict`.
RichieHindle
You're right, isinstance is a better choice there, not sure why I didn't think of that. Updated version appended to the answer. Thanks!
Jack Lloyd
Thanks a lot Jack! This answer is beautiful.
hekevintran
`__dict__` attribute is not available for all objects.
J.F. Sebastian
A) Corrections inline, that's what people read. The `type(..) is` thing has 0 merit to stay. B) is `vars(obj)` preferred over obj.__dict__? (vars is a less known builtin.)
kaizer.se
+1  A: 
sort_me.sort(key=attr_or_itemgetter('name'))

Where attr_or_itemgetter():

class attr_or_itemgetter(object):
    def __init__(self, name):
        self.name = name
    def __call__(self, obj):
        try: return getattr(obj, name)
        except AttributeError:
            return obj[name]

NOTE: It intentionally doesn't check for dictionary type, therefore attr_or_itemgetter('items') applied to a dictionary will return dict.items method.

J.F. Sebastian
I find this answer more Pythonic than the one based on type-checking (may be a little slower if there are plenty of dicts in the sequence being sorted, but all it takes to optimize it for that use is flipping what's the try body -0).
Alex Martelli