9

Can I use lambda function to loop over a list of class objects and change value of an attribute (for all objects or for the one that meet a certain condition)?

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)
3
  • @Ned 'def change_age(student): student.age +=3 return student new_students_age_list = map(change_age,students)' Commented Aug 2, 2014 at 14:09
  • As you have been told, lambda doesn't allow to do assignments. So you have to add an appropriate method to the class and call it from lambda. If you cant change the class, you always can add a method to the instances and get the same effect Commented Aug 2, 2014 at 14:10
  • @pythondev if you do that, then increased_students_age_list has exactly the same contents as students. You have two lists that are identical, there's no point in that. Commented Aug 2, 2014 at 14:10

6 Answers 6

8

Unfortunately, that’s not possible since the body of a lambda only allows for simple expressions while a student.age += 3 is a statement. So you can’t use a lambda there. You could however still use the map solution:

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

students2 = map(incrementAge, students)

Note that students2 will contain the same students as students though, so you don’t really need to capture the output (or return something from incrementAge). Also note that in Python 3, map returns a generator which you need to iterate on first. You can call list() on it to do that: list(map(…)).

Finally, a better solution for this would be to use a simple loop. That way, you don’t have overhead of needing a function or create a duplicate students list, and you would also make the intention very clear:

for student in students:
    student.age += 3
Sign up to request clarification or add additional context in comments.

2 Comments

Other than the space in the function def line, I really like this answer! :)
@NedBatchelder Heh, I figured you disliked that space—but I like to place one there for definitions ;P
5

Using a simple for-loop to retrieve the students to update the age for each is good enough like others said, but if you still want to use a lambda to update the values, you may need to leverage the exec() function:

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

Output:

21 23 32

In this case, what actually does the updating is the exec(), and the map() just yields None. So the returned result makes no sense and I put a _ to clarify this. A more concise form would be this:

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

Besides, if only considering what you want to do, you don't need to use a map() at all (probably more confusing though):

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

Furthermore, a lambda can be discarded either:

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

As you can see, no "trick" codes above seem more concise than what other answers post:

for s in students:
    s.age += 3

So maybe the so-called "one-liner" is useful just when it comes to having fun... :)

Comments

4

You can use setattr, which will apply the change to the objects. A big plus is that you can continue using the same list.

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

From the docs:

The function assigns the value to the attribute, provided the object allows it. For example, setattr(x, 'foobar', 123) is equivalent to x.foobar = 123.

The equivalency of which is:

for s in students:
    s.age += 3

If you really want a new list:

The above approach doesn't return a new list; instead returning None (the return value of setattr). Adding an or comparison with the object you want in the array (in this case s) will amend that, though.

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

The comparison is equivalent to None or s which will always yield the latter. Also note that the new list is identical to the old one.

Comments

3

Lambda functions can only contain expressions, not statements. Assignment in Python is a statement. Lambdas cannot do assignments. Additionally, assignment statements do not evaluate to their values, so your map would not produce a list of students.

You want this:

for student in students:
    student.age += 3

This does not give you a new list, it modifies the old list, but your old list would be modified anyway, you aren't doing anything to produce new Students.

1 Comment

Pedantically speaking, Assignment in Python is an operation. The typical way of performing this operation is with a statement. ...and lambdas can do assignments, just not assignment statements. Poke avoided this issue entirely (although he created one of his own with that unseemly extra space)
1

Have you tried playing around with the setattr and getattr functions? With these you can write and read the writable attributes of the object directly.

So you could do something like this:

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

In this case the original students' list will be updated with the new ages for each student, which might not be necessarily what you want, but can easily be worked around by making a backup of the original list's objects before casting the map object to a list. In that case, you could do:

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

Here is the full snippet:

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

Comments

0

Q: "Can I use lambda function to loop over a list of class objects and change value of an attribute"

A: Yes....but you shouldn't. It's poor coding style, inefficient, and only appropriate for things like code golf

You should write it like the other two answers have suggested.

...but if you really wanted to...

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]

Prints:

[21, 23, 32]

...or even:

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]

[Same output]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.