使用lambda函数更改属性值
我可以用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 个回答
你有没有试过使用 setattr 和 getattr 这两个函数?用这两个函数,你可以直接读取和写入对象的可写属性。
你可以这样做:
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))
Lambda 函数只能包含表达式,而不能包含语句。在 Python 中,赋值是一个语句。也就是说,Lambda 函数不能进行赋值。此外,赋值语句不会返回它们的值,所以你用 map 函数时不会得到一个学生的列表。
你想要的是这个:
for student in students:
student.age += 3
这个操作不会给你一个新的列表,它只是修改了旧的列表,但即使不这样做,你的旧列表也会被修改,因为你并没有做任何事情来生成新的学生。
你可以使用 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
,结果总是会得到后面的那个值。此外,新的列表和旧的列表是完全一样的。
使用简单的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
所以,也许所谓的“一行代码”在玩的时候才有用... :)
很遗憾,这种做法是不可能的,因为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