使用Lambda函数来改变属性的值

9

我能否使用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)

@Ned 'def change_age(student): student.age +=3 return student new_students_age_list = map(change_age,students)'@Ned 'def change_age(学生): 学生.age +=3 返回 学生 新学生年龄列表 = map(change_age,学生)' - python dev
正如你所知道的,lambda不允许进行赋值操作。因此,你需要向类中添加一个适当的方法,并从lambda中调用它。 如果你无法更改类,你总是可以向实例中添加一个方法并获得相同的效果。 - Evgeny Prokurat
@pythondev 如果你这样做,那么increased_students_age_liststudents的内容完全相同。你有两个完全相同的列表,这没有任何意义。 - Ned Batchelder
6个回答

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

除了函数定义行中的空格外,我真的很喜欢这个答案! :) - Ned Batchelder
@NedBatchelder 嘿,我知道你不喜欢那个空格——但是我喜欢在定义时放一个;P - poke

5

像其他人说的那样,使用简单的for循环来检索要更新每个学生的年龄是足够好的,但如果你仍然想使用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

也许所谓的“一行代码”只有在娱乐方面才有用... :)

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

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

上述方法不会返回一个新列表;相反,它会返回 Nonesetattr 的返回值)。添加一个 or 比较与你想在数组中使用的对象(在这种情况下是 s)将修正这个问题。

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

比较相当于None or s,总是返回后者。同时注意新列表与旧列表是完全相同的。

3

lambda函数只能包含表达式,而不能包含语句。在Python中,赋值是一种语句,因此lambda无法执行赋值操作。此外,赋值语句不会计算其值,因此您的map函数将不会生成学生列表。

您需要这样:

for student in students:
    student.age += 3

这并不会给你一个新的列表,它修改的是旧列表,但无论如何,你的旧列表都会被修改,你没有做任何事情来生成新的学生。

严谨地说,Python中的“赋值”是一种操作。执行此操作的典型方式是使用语句。...而lambda可以执行赋值操作,但不能执行“赋值语句”。Poke完全避免了这个问题(尽管他用那个不雅的额外空格创建了自己的问题)。 - Gerrat

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))

0

问:我能使用lambda函数循环遍历类对象列表并更改属性值吗?

答:可以……但你不应该这样做。这是糟糕的编码风格,效率低下,只适用于像代码高尔夫这样的事情。

你应该像其他两个答案建议的那样编写它。

……但如果你真的想这样做……

new_list_of_students = [(lambda student:(setattr(student, 'age', student.age+3), 
student))(s)[1] for s in students]

print [student.age for student in new_list_of_students]

打印:

[21, 23, 32]

...或者甚至:

from operator import itemgetter
new_list_of_students = map(itemgetter(1),map(lambda student:(setattr(student, 
'age', student.age+3), student),students))

print [student.age for student in new_list_of_students]

[相同的输出]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接