
本文旨在深入探讨如何在python中对自定义对象列表进行动态排序。我们将详细解析当使用lambda表达式作为排序键时,如何正确访问对象属性,避免常见的typeerror: 'person object is not subscriptable`错误。此外,文章还将介绍更优雅的实现方式,如通过公共getter方法增强封装性,或利用operator.attrgetter简化代码,以提高代码的可读性和维护性。
1. 理解Python中的对象排序机制
在Python中,对列表进行排序通常使用内置的 sorted() 函数或列表自身的 sort() 方法。它们都支持一个可选的 key 参数,该参数接收一个函数,用于从列表中的每个元素提取一个比较键。这个键将用于实际的排序比较。
例如,如果我们有一个字符串列表,并想根据字符串的长度进行排序,可以使用 key=len:
words = ["apple", "banana", "kiwi", "grape"] sorted_by_length = sorted(words, key=len) print(sorted_by_length) # 输出: ['kiwi', 'grape', 'apple', 'banana']
当需要更复杂的自定义排序逻辑时,通常会使用匿名函数 lambda 来定义 key。
2. 初始问题:Person类与排序尝试
假设我们有一个 Person 类,包含姓名、年龄、身高和体重等属性。我们希望能够根据这些不同的属性动态地对 Person 对象列表进行排序。
立即学习“Python免费学习笔记(深入)”;
import numpy as np
import enum
# 假设 merge_sort 模块已实现归并排序
# from merge_sort import mergesort
# 模拟一个简单的归并排序函数,实际项目中可使用内置 sorted
def mergesort(data, key=None):
if key is None:
key = lambda x: x
# 这是一个简化版,实际归并排序逻辑会更复杂
return sorted(data, key=key)
NAMES = ["Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Grace", "Hank", "Ivy", "Jack"]
class Person:
def __init__(self, name, age, height, weight):
self._name = name
self._age = age
self._height = height
self._weight = weight
def __repr__(self):
return f"Person({self._name}, {self._age} years, {self._height} cm, {self._weight} kg)"
# 为简化示例,省略其他比较方法 __eq__, __lt__ 等
def create_persons_list(n=10, sort_key='weight'):
person_objects = [
Person(np.random.choice(NAMES), np.random.randint(18, 101),
np.random.randint(150, 201), np.random.randint(45, 101))
for _ in range(n)
]
# 错误尝试:试图将Person对象当作元组进行下标访问
if sort_key == 'name':
return mergesort(person_objects, key=lambda x: x[0]) # 错误点
elif sort_key == 'age':
return mergesort(person_objects, key=lambda x: x[1]) # 错误点
elif sort_key == 'height':
return mergesort(person_objects, key=lambda x: x[2]) # 错误点
elif sort_key == 'weight':
return mergesort(person_objects, key=lambda x: x[3]) # 错误点
else:
raise ValueError("Invalid sort_key.")
# 尝试执行会导致 TypeError
# sorted_persons_by_name = create_persons_list(sort_key='name')当尝试运行上述代码时,会遇到 TypeError: 'Person' object is not subscriptable 错误。
错误原因解析:mergesort 函数(或 sorted())在调用 key 函数时,会将列表中的每个元素(即 Person 对象)作为参数 x 传递给 lambda 表达式。此时,x 的类型是 Person。Python中的 Person 对象默认不支持像 x[0]、x[1] 这样的下标访问方式(除非显式实现了 __getitem__ 方法)。因此,尝试通过下标访问 Person 对象的属性会导致 TypeError。
3. 解决方案:直接访问对象属性
解决这个问题的关键在于理解 lambda x: 中的 x 就是 Person 对象本身。要访问 Person 对象的属性,应该使用点运算符(.),而不是下标运算符([])。
如果 Person 类的属性是 _name, _age 等(遵循Python的私有属性约定,但实际仍可访问),那么正确的 lambda 表达式应该是:
# 修正后的 create_persons_list 函数片段
def create_persons_list_corrected(n=10, sort_key='weight'):
person_objects = [
Person(np.random.choice(NAMES), np.random.randint(18, 101),
np.random.randint(150, 201), np.random.randint(45, 101))
for _ in range(n)
]
if sort_key == 'name':
return mergesort(person_objects, key=lambda x: x._name)
elif sort_key == 'age':
return mergesort(person_objects, key=lambda x: x._age)
elif sort_key == 'height':
return mergesort(person_objects, key=lambda x: x._height)
elif sort_key == 'weight':
return mergesort(person_objects, key=lambda x: x._weight)
else:
raise ValueError("Invalid sort_key.")
# 示例使用
sorted_persons_by_age = create_persons_list_corrected(sort_key='age')
print("Sorted by age (corrected):\n", sorted_persons_by_age)通过将 x[0] 改为 x._name,x[1] 改为 x._age,我们直接访问了 Person 对象的相应属性作为排序键,从而解决了 TypeError。
4. 提升代码质量:封装与灵活性
虽然直接访问 _attribute 解决了问题,但在面向对象编程中,直接访问以 _ 开头的“私有”属性通常不是最佳实践。更好的做法是提供公共接口(getter方法)或利用Python标准库中的工具。
4.1 引入公共Getter方法
为了更好的封装性,可以在 Person 类中添加公共的 getter 方法,例如 get_name(), get_age() 等。这样,外部代码可以通过这些方法安全地访问属性,而无需关心内部实现细节。
class Person:
def __init__(self, name, age, height, weight):
self._name = name
self._age = age
self._height = height
self._weight = weight
# 公共getter方法
def get_name(self):
return self._name
def get_age(self):
return self._age
def get_height(self):
return self._height
def get_weight(self):
return self._weight
def __repr__(self):
return f"Person({self._name}, {self._age} years, {self._height} cm, {self._weight} kg)"
# 使用getter方法修正后的 create_persons_list 函数
def create_persons_list_with_getters(n=10, sort_key='weight'):
person_objects = [
Person(np.random.choice(NAMES), np.random.randint(18, 101),
np.random.randint(150, 201), np.random.randint(45, 101))
for _ in range(n)
]
if sort_key == 'name':
return mergesort(person_objects, key=lambda x: x.get_name())
elif sort_key == 'age':
return mergesort(person_objects, key=lambda x: x.get_age())
elif sort_key == 'height':
return mergesort(person_objects, key=lambda x: x.get_height())
elif sort_key == 'weight':
return mergesort(person_objects, key=lambda x: x.get_weight())
else:
raise ValueError("Invalid sort_key.")
# 示例使用
sorted_persons_by_name_getters = create_persons_list_with_getters(sort_key='name')
print("\nSorted by name (with getters):\n", sorted_persons_by_name_getters)这种方式提高了代码的封装性,即使将来 Person 内部属性的存储方式发生变化,只要 getter 方法的接口不变,外部排序逻辑就不需要修改。
4.2 使用 operator.attrgetter 简化代码
Python的 operator 模块提供了一些函数,可以用来替代常见的 lambda 表达式,使代码更加简洁和高效。operator.attrgetter 就是其中之一,它能根据指定的属性名创建一个可调用对象,用于从对象中获取该属性的值。
import operator
# ... (Person类和mergesort函数保持不变,可以使用带getter的版本)
def create_persons_list_with_attrgetter(n=10, sort_key='weight'):
person_objects = [
Person(np.random.choice(NAMES), np.random.randint(18, 101),
np.random.randint(150, 201), np.random.randint(45, 101))
for _ in range(n)
]
# 使用字典映射 sort_key 到相应的 getter 方法或属性名
# 如果Person类有getter,这里映射getter名称
# 如果没有getter,直接映射属性名称 (例如 '_name')
key_map = {
'name': operator.attrgetter('get_name'), # 假设Person有get_name()
'age': operator.attrgetter('get_age'), # 假设Person有get_age()
'height': operator.attrgetter('get_height'),
'weight': operator.attrgetter('get_weight'),
}
# 如果Person类没有getter,直接访问_attribute
# key_map = {
# 'name': operator.attrgetter('_name'),
# 'age': operator.attrgetter('_age'),
# 'height': operator.attrgetter('_height'),
# 'weight': operator.attrgetter('_weight'),
# }
if sort_key in key_map:
return mergesort(person_objects, key=key_map[sort_key])
else:
raise ValueError("Invalid sort_key.")
# 示例使用
sorted_persons_by_weight_attrgetter = create_persons_list_with_attrgetter(sort_key='weight')
print("\nSorted by weight (with attrgetter and getters):\n", sorted_persons_by_weight_attrgetter)使用 operator.attrgetter 使得代码更加紧凑,特别是当排序键的逻辑只是简单地获取一个属性时。它也比 lambda x: x.attribute 略微高效一些,因为它避免了每次调用 lambda 时的函数创建开销。
5. 完整示例代码
结合上述讨论,以下是一个包含 Person 类(带getter方法)和使用 operator.attrgetter 进行动态排序的完整示例:
import numpy as np
import operator
# 模拟一个简单的归并排序函数,实际项目中可使用内置 sorted
def mergesort(data, key=None):
if key is None:
key = lambda x: x
return sorted(data, key=key)
NAMES = ["Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Grace", "Hank", "Ivy", "Jack"]
class Person:
def __init__(self, name, age, height, weight):
self._name = name
self._age = age
self._height = height
self._weight = weight
# 公共getter方法
def get_name(self):
return self._name
def get_age(self):
return self._age
def get_height(self):
return self._height
def get_weight(self):
return self._weight
def __repr__(self):
return f"Person({self._name}, {self._age} years, {self._height} cm, {self._weight} kg)"
def create_persons_list(n=10, sort_key='weight'):
person_objects = [
Person(np.random.choice(NAMES), np.random.randint(18, 101),
np.random.randint(150, 201), np.random.randint(45, 101))
for _ in range(n)
]
# 使用 operator.attrgetter 映射到 Person 类的 getter 方法
key_map = {
'name': operator.attrgetter('get_name'),
'age': operator.attrgetter('get_age'),
'height': operator.attrgetter('get_height'),
'weight': operator.attrgetter('get_weight'),
}
if sort_key in key_map:
return mergesort(person_objects, key=key_map[sort_key])
else:
raise ValueError(f"Invalid sort_key: '{sort_key}'. Supported values are {list(key_map.keys())}.")
# 生成并打印按不同键排序的列表
print("原始列表 (部分):\n", [Person(np.random.choice(NAMES), 25, 170, 60), Person(np.random.choice(NAMES), 30, 180, 75)])
sorted_persons_by_name = create_persons_list(sort_key='name')
print("\nSorted by name:\n", "\n".join(map(str, sorted_persons_by_name)))
sorted_persons_by_age = create_persons_list(sort_key='age')
print("\nSorted by age:\n", "\n".join(map(str, sorted_persons_by_age)))
sorted_persons_by_height = create_persons_list(sort_key='height')
print("\nSorted by height:\n", "\n".join(map(str, sorted_persons_by_height)))
sorted_persons_by_weight = create_persons_list(sort_key='weight')
print("\nSorted by weight:\n", "\n".join(map(str, sorted_persons_by_weight)))6. 总结与注意事项
- 理解 key 参数接收的对象类型: 当使用 sorted() 或 list.sort() 的 key 参数时,传入 lambda 函数的 x 始终是列表中的原始元素。因此,必须根据该元素的实际类型来访问其属性或执行操作。
- 避免 TypeError: 'object' is not subscriptable: 当对自定义对象进行排序时,如果对象不是序列类型(如列表、元组),则不能使用下标 [] 来访问其内容。应使用点运算符 . 来访问对象的属性。
- 封装的重要性: 尽量通过公共 getter 方法来访问对象的属性,而不是直接访问以 _ 开头的“私有”属性。这提高了代码的模块化和可维护性。
- 利用 operator 模块: 对于简单的属性获取作为排序键的场景,operator.attrgetter 是一个更简洁、有时更高效的选择。
- 错误处理: 在动态确定排序键时,务必进行有效的错误处理,例如检查 sort_key 是否在预期的范围内,以防止程序因无效输入而崩溃。
通过遵循这些原则,您可以有效地在Python中对自定义对象进行灵活且健壮的动态排序。










