1 问题
你有一个列表,列表中的元素均为字典,你希望通过字典中的一个或多个键对列表进行排序。
2. 解决方案
针对上述需求,使用 operator 模块中的 itemgetter 类来实现非常简单。假设你从数据库里面查询出了以下信息:
>>> rows = [
... {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
... {'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
... {'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
... {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
...]
如果希望按照列表中每个字典的特定键进行排序,则利用 operator 模块的 itemgetter 类如下:
>>> from operator import itemgetter
>>> rows_by_fname = sorted(rows, key=itemgetter('fname'))
>>> rows_by_fname
[
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004},
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}
]
>>> rows_by_uid = sorted(rows, key=itemgetter('uid'))
>>> rows_by_uid
[
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
]
实际上,类 itemgetter 还接受多个对象初始化参数。例如:
- 先按照键
'lname' 在按照键 'fname' 进行排序:
>>> rows_by_lname_fname = sorted(rows, key=itemgetter('lname', 'fname'))
>>> rows_by_lname_fname
[
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004},
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}
]
3. 讨论
在上述案例中,元素为字典的列表 rows 被传递至函数 sorted() ,该函数还接受一个键值对参数 key ,该参数需要是一个可调用对象,该对象本身接受接受一个参数(在这里该参数是列表 rows 的每一个元素,即字典)作为输入,调用该对象会返回一个值用作排序,而 itemgetter 类创建的就恰恰是 sorted 函数通过 key 接受的可调用对象。例如:
>>> getter = itemgetter('lname', 'fname')
>>> getter(rows[0])
('Jones', 'Brian')
>>> getter = itemgetter('lname', 'fname', 'uid')
>>> getter(rows[1])
('Beazley', 'David', 1002)
在上述案例中,使用 operator.itemgetter() 类创建可调用对象时,初始化方法接受的参数会被用作查找的索引,后续在调用该对象时会返回根据索引查找得到的值。
实际上,该对象的初始化方法接受的参数除了可以是字典的键名以外,还可以是列表的索引,更进一步地,如果一个对象中实现了 __getitem__() 方法,这些参数还可以是实现 __getitem__() 方法时使用的任何类型对象。
如上所述,如果你在创建 itemgetter 实例时为初始化方法传入了多个后续用作查找的索引,那么在调用该对象后也将返回一个同样大小的元组,元组的每个元素都是使用对应索引查找出的值。
在实际中,类 itemgetter 的功能通常会被 lambda 表达式所代替。例如:
>>> rows_by_fname = sorted(rows, key=lambda row: row['fname'])
>>> rows_by_fname
[
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004},
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}
]
>>> rows_by_lname_fname = sorted(rows, key=lambda row: (row['lname'], row['fname']))
>>> rows_by_lname_fname
[
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004},
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}
]
上述两种方法的都可以实现需求,然而使用 itemgetter 类通常比较快,如果你对于性能要求较高可以使用对应的方法。
最后,不要忘了,类 itemgetter 的对象还可以用于 min() 或 max() 等函数中。例如:
>>> min(rows, key=itemgetter('uid'))
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}
>>> max(rows, key=itemgetter('uid'))
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
4. 拓展
下面是 itemgetter 类的源码,不长,花几分钟就能看懂。使用该类创建的对象会有两个实例属性:
self._items :用于以元组的形式保存创建对象时初始化方法接受的参数;self._call :一个函数的引用,该函数在 itemgetter 的初始化方法中定义,而且该函数返回的是根据索引 item 查找对象 obj 得到的元素。
在调用 itemgetter 的实例时,实例的 __call__() 方法会被调用,该方法会返回 self._call(obj) 的结果。
class itemgetter:
"""
Return a callable object that fetches the given item(s) from its operand.
After f = itemgetter(2), the call f(r) returns r[2].
After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3])
"""
__slots__ = ('_items', '_call')
def __init__(self, item, *items):
if not items:
self._items = (item,)
def func(obj):
return obj[item]
self._call = func
else:
self._items = items = (item,) + items
def func(obj):
return tuple(obj[i] for i in items)
self._call = func
def __call__(self, obj):
return self._call(obj)
def __repr__(self):
return '%s.%s(%s)' % (self.__class__.__module__,
self.__class__.__name__,
', '.join(map(repr, self._items)))
def __reduce__(self):
return self.__class__, self._items
|