使用lambda函数更改属性值

9 投票
6 回答
22766 浏览
提问于 2025-04-18 15:44

我可以用lambda函数来遍历一个类对象的列表,并改变某个属性的值吗?这个改变是针对所有对象,还是只针对满足特定条件的对象?

class Student(object):
    def __init__(self,name,age):
        self.name = name
        self.age = age

student1 = Student('StudOne',18)
student2 = Student('StudTwo',20)
student3 = Student('StudThree',29)
students = [student1,student2,student3]

new_list_of_students = map(lambda student:student.age+=3,students)

6 个回答

1

你有没有试过使用 setattrgetattr 这两个函数?用这两个函数,你可以直接读取和写入对象的可写属性。

你可以这样做:

map_of_students = map(lambda student: setattr(student, "age", getattr(student, "age") + 3), students)

print("Age BEFORE list: " + str(students[0].age)); list(map_of_students); print("Age AFTER list: " + str(students[0].age))

在这个例子中,原来的学生列表会被更新,每个学生的年龄都会改变,这可能不是你想要的结果。不过,你可以通过在把映射对象转换成列表之前,先备份原始列表中的对象来避免这个问题。这样的话,你可以这样做:

students_bkp = []
for s in students:
    students_bkp.append(Student(**s.__dict__))

下面是完整的代码片段:

class Student(object):
    def __init__(self,name,age):
        self.name = name
        self.age = age

student1 = Student('StudOne',18)
student2 = Student('StudTwo',20)
student3 = Student('StudThree',29)
students = [student1,student2,student3]

students_bkp = []
for s in students:
    students_bkp.append(Student(**s.__dict__))

map_of_students = map(lambda student: setattr(student, "age", getattr(student, "age") + 3), students)
print("Age BEFORE list: " + str(students[0].age)); list(map_of_students); print("Age AFTER list: " + str(students[0].age)); print("Age BKP: " + str(students_bkp[0].age))
3

Lambda 函数只能包含表达式,而不能包含语句。在 Python 中,赋值是一个语句。也就是说,Lambda 函数不能进行赋值。此外,赋值语句不会返回它们的值,所以你用 map 函数时不会得到一个学生的列表。

你想要的是这个:

for student in students:
    student.age += 3

这个操作不会给你一个新的列表,它只是修改了旧的列表,但即使不这样做,你的旧列表也会被修改,因为你并没有做任何事情来生成新的学生。

4

你可以使用 setattr,这个方法会对对象进行修改。一个很大的好处是,你可以继续使用同一个列表。

map(lambda s: setattr(s, 'age', s.age + 3), students)

根据文档的说明:

这个函数会把值赋给属性,只要对象允许这样做。比如,setattr(x, 'foobar', 123) 相当于 x.foobar = 123

for s in students:
    s.age += 3

如果你真的想要一个新的列表:

上面的方法不会返回一个新的列表,而是返回 None(这是 setattr 的返回值)。不过,如果你在想要的数组对象(在这个例子中是 s)后面加一个 or 比较,就可以解决这个问题。

new_students = map(lambda s: setattr(s, 'age', s.age + 3) or s, students)

这个比较相当于 None or s,结果总是会得到后面的那个值。此外,新的列表和旧的列表是完全一样的。

5

使用简单的for循环来获取students并更新每个学生的age,就像其他人说的那样,这已经足够了。不过,如果你还是想用lambda来更新值,你可能需要用到exec()这个函数:

_ = list(map(lambda student: exec("student.age+=3"), students))
for _ in students: print(_.age)

输出结果:

21 23 32

在这个例子中,真正负责更新的其实是exec(),而map()只是返回None。所以返回的结果没有什么意义,我加了一个_来说明这一点。更简洁的写法是这样的:

list(map(lambda _: exec("_.age+=3"), students))

另外,如果只考虑你想做的事情,其实根本不需要用到map()(不过这样可能会更让人困惑):

[(lambda _: exec("_.age += 3"))(_) for _ in students]

而且,lambda也可以省略:

[exec("_.age += 3") for _ in students]

如你所见,上面的“技巧”代码似乎没有比其他答案更简洁:

for s in students:
    s.age += 3

所以,也许所谓的“一行代码”在玩的时候才有用... :)

7

很遗憾,这种做法是不可能的,因为lambda函数的主体只允许使用简单的表达式,而像student.age += 3这样的写法是一个语句。所以在这里你不能使用lambda。不过,你仍然可以使用map的方法:

def incrementAge (student):
    student.age += 3
    return student

students2 = map(incrementAge, students)

需要注意的是,students2会包含和students一样的学生,所以你其实不需要捕获输出(或者从incrementAge返回什么)。另外,在Python 3中,map会返回一个生成器,你需要先对它进行迭代。你可以用list()来实现这一点:list(map(…))

最后,一个更好的解决方案是使用简单的循环。这样,你就不需要额外的函数,也不需要创建一个重复的学生列表,而且你的意图会非常清晰:

for student in students:
    student.age += 3

撰写回答