
本文旨在深入探讨如何在python中对自定义对象列表进行动态排序。我们将详细解析当使用lambda表达式作为排序键时,如何正确访问对象属性,避免常见的typeerror: 'person object is not subscriptable`错误。此外,文章还将介绍更优雅的实现方式,如通过公共getter方法增强封装性,或利用operator.attrgetter简化代码,以提高代码的可读性和维护性。
在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。
假设我们有一个 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。
解决这个问题的关键在于理解 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。
虽然直接访问 _attribute 解决了问题,但在面向对象编程中,直接访问以 _ 开头的“私有”属性通常不是最佳实践。更好的做法是提供公共接口(getter方法)或利用Python标准库中的工具。
为了更好的封装性,可以在 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 方法的接口不变,外部排序逻辑就不需要修改。
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 时的函数创建开销。
结合上述讨论,以下是一个包含 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)))通过遵循这些原则,您可以有效地在Python中对自定义对象进行灵活且健壮的动态排序。
以上就是Python自定义对象排序:动态键值与TypeError解析的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号