Using lambda function to change value of an attribute - python

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)

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

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

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.

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.

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

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]

Related

Is there a way for finding the name of the variable that was selected by the min function?

In the Python code below, I am simply assigning OPN_24n4 the minimum value of either Node_23n3 or Node_24n4. However, I also would like to know the name of the variable that was chosen. Is there a concise way of doing this? I suppose I could use some sort of if statement but thought there might be a function I'm unaware of.
OPN_24n4 = min(Node_23n3, Node_23n4)
min, max, and sorted can take a key function, which is a function that manipulate the input in order make the comparison over some part of it or whatever, and we can use this and then make tuples with the name of the variable and its value and with an appropriate key function get the result we desire
>>> min(("var1",10),("var2",2), key=lambda x:x[1])
('var2', 2)
>>>
If you are working with lists you can get the index of the minimum value with the index method
>>> x = [5,3,7,1,9]
>>> x.index(min(x))
3
>>>
It seems unlikely that you would ever need a solution like this , and it would probably be relatively fragile but, borrowing from this answer you could create a solution like this using inspect:
import inspect
def named_min(*args):
min_val = min(args)
callers_local_vars = inspect.currentframe().f_back.f_locals.items()
named_var = [var_name for var_name, var_val in callers_local_vars if var_val is args[args.index(min_val)]][0]
if args.count(min_val) > 1:
named_var = 'multiple'
return min_val, named_var
Node_23n3 = 0
Node_23n4 = 1
OPN_24n4, name = named_min(Node_23n3, Node_23n4)
print(OPN_24n4, name)
Node_23n4 = 0
OPN_24n4, name = named_min(Node_23n3, Node_23n4)
print(OPN_24n4, name)
Node_23n3 = 2
OPN_24n4, name = named_min(Node_23n3, Node_23n4)
print(OPN_24n4, name)
Node_23n5 = -1
OPN_24n4, name = named_min(Node_23n3, Node_23n4, Node_23n5)
print(OPN_24n4, name)
This does do what you're after:
0 Node_23n3
0 multiple
0 Node_23n4
-1 Node_23n5
but I certainly wouldn't recommend it.

What could be a better alternative to this For?

I'm a beginner trying to write up a game about trading in Python 3.x.
I have two classes (Depot and Good). Instances of the class Good are stored in a dictionary inside depot.inv (the instance as Key, and the amount of it as Value). When the user is asked to write what does he want to take, he will write the name of the instance ('Iron' instead of ir). So I took a For loop and searched through everything the Depot had in it's inventory. If he finds that the reply of the user is = to the name of any instance (ir.name, for example), then the program gains access to the instance as a whole.
The question is, how can I do this without a For-Loop?
I imagine that searching the whole inventory each time a reply is made is not optimal, even less if it's a linear search.
class Depot:
def __init__ (self, name, inv, bank):
self.name = name
self.inv = inv
self.bank = bank
class Good:
def __init__(self, name, weight, rare):
self.name = name
self.weight = weight
self.rare = rare
ir = Good('Iron', 1, 0.1)
gd = Good('Gold', 0.4, 2)
sl = Good('Silver', 0.7, 6.3)
mars = Depot('Mars', {ir: 10500, gd: 800, sl: 6000}, 1000)
player = {ir: 100}
reply = input('What do you want to take?')
i, q = reply.split()
for k in mars.inv.keys(): #This is the crux of the problem
if i in k.name:
x = k
print('Got it')
if x in mars.inv:
if int(q) > mars.inv[x]:
print('We dont have that much.')
elif int(q) <= mars.inv[x]:
mars.inv[x] -= int(q)
if x in player:
player[x] += int(q)
elif i not in player:
player[x] = int(q)
Using list comprehension and next() allows you to only find the first occurance of the item you're looking for, and then stop. Breaking the for loop after finding the key you're looking for would achieve the same thing. Both are O(n) there isn't really that much room to optimize. A one liner would be
x = next((k for k in mars.inv.keys() if i in k.name), None)
I'd probably duplicate the name as the key of the dictionary and map it to a tuple of the other information:
{"Iron": (ir, 10500), "Gold": (gd, 800), "Silver": (sl, 6000}
Then you can do mars.inv["Iron"] to get the tuple of (ir, 10500), and you can extract whichever data you want from that.
Note though, this requires an exact match on the name. You're currently using in to do the check, which will prevent any optimizations. Since from your description, you're doing lookups based on exact names (ignoring case, since that can be easily accounted for), in is unnecessary.

Sort list with information from text file

I have been reading class objects from a text file and stored the information in a list. My question is: How can I sort the list in a numerical order.
Text file:
Hugo 10
Isac 9
John 90
Kelly 8
Code:
class A:
def __init__(self,name, age):
self.name = name
self.age = age
def sort_list():
a = list()
file = open('my_textfile.txt', 'r').readlines()
for k in file:
k = k.split(' ')
af = A(k[0], k[1])
a.append(af)
return a
Then I want to sort my list so that both columns get sorted numerically. Like this:
Kelly 8
Isac 9
Hugo 10
John 90
You most likely have found a solution now. If not, let me suggest something.
First, I made a slight modification to your class: I added a string representation that makes prints more readable:
class A:
def __repr__(self):
return f"A('{self.name}', {self.age})"
def __init__(self, name, age):
self.name = name
self.age = age
You don't need that for the following!
Now the sorting:
def sort_list():
lst = list()
lines = open('my_textfile.txt', 'r').readlines()
for line in lines:
line = line.split()
lst.append(A(line[0], int(line[1])))
lst.sort(key=lambda a: (a.age, a.name))
return lst
Apart from renaming some variables I have made the following adjustments:
I converted the second part of the strings to an int because I think that's what you actually want (int(line[1])).
After appending all the data to the list it get's sorted. Lists have a sort method, but in your case you have to give it some hints on how to sort, since the elements are a new class. The hint is the lambda function given to the optional key argument of sort. It returns for every element of the list the tuple (a.age, a.name). sort then actually sorts according to this values. Since the age is first, the sorting looks a it first and the result is an increasing age in the final list. For entries with same age the sorting proceeds in alphabetical order on the names.
The result of
lst = sort_list()
print(lst)
is
[A('Kelly', 8), A('Isac', 9), A('Hugo', 10), A('John', 90)]
Here is one solution using the code you suggest. Break the data into two parts sort it and put it back together again. There are other ways of doing this with pandas.
class A:
def __init__(self,name, age):
self.name = name
self.age = age
def sort_list():
a = list()
name = list()
data = list()
file = open('my_textfile.txt', 'r').readlines()
for k in file:
k = k.split(' ')
name.append(k[0])
data.append(k[1])
name.sort()
data.sort()
length = len(name)
for i in range(length):
af = A(name[i], data[i])
a.append(af)
return a

Dynamic list comprehension

I would like to know if python is able to create a list by comprehension using multiple and optional criteria.
Let's make an example. Considering the following object (partial description):
class Person():
def __init__(self):
self.id = <next id of some kind>
self.name = 'default name'
self.gender = 'm'
self.age = 20
<...>
Suppose I created a list of all Persons into world. Then I want to create a GUI which will allow me to browse the collection based on search criteria (the GUI conception is out of scope of the question), e.g name (regex based), id, gender and age (with equal, not equal and greater or lesser than). None of the search criteria are mandatory (we can suppose it's None I guess) and the type do not really matter for this question.
How can I filter the list of Person in a clever python-way?
If I have known criteria I could do a comprehension :
l = [person for person in world if re.search(person.name, '.*Smith') and person.gender = 'm' and person.age < 20]
But as the user is able to choose what he wants, I won't know what criteria to use. I can of course build this as fully fledged function:
l = world
if nameSearch:
l = [person for person in l if re.search(person.name, nameSearch)]
if genderSearch:
l = [person for person in l if gender == genderSearch]
<...>
return l
But I feel python would have a way to do it more properly.
Based DCS' comment, here is a example how to use functions as filters. A filter is just a function which returns a boolean (given an instance of Person). For faster processing I suggest you take a look at pandas, which is a very good choice for data filtering/sorting/munging, but this might get you started with a simple solution. The only task that is left to you, is to create the filters based on the user's input.
from random import random
class Person():
def __init__(self, id):
self.id = id
self.name = 'Name{}'.format(id)
self.gender = 'm' if random() > 0.5 else 'f'
self.age = int(random() * 10) + 10
def __repr__(self):
return 'Person-{} ({}, {}. {})'.format(self.id,
self.name,
self.gender,
self.age)
Setting up some test data:
people = [Person(id) for id in range(10)]
[Person-0 (Name0, f. 15),
Person-1 (Name1, f. 14),
Person-2 (Name2, f. 12),
Person-3 (Name3, f. 18),
Person-4 (Name4, m. 12),
Person-5 (Name5, f. 18),
Person-6 (Name6, f. 15),
Person-7 (Name7, f. 15),
Person-8 (Name8, f. 10),
Person-9 (Name9, m. 16)]
Output:
def by_age(age):
return lambda person: person.age == age
def by_name(name):
return lambda person: re.search(person.name, name)
def by_gender(gender):
return lambda person: person.gender == gender
filters = (by_age(15),
by_gender('f'))
filtered_people = (p for p in people if all([f(p) for f in filters]))
list(filtered_people)
Which gives us the following filtered list of people:
[Person-0 (Name0, f. 15), Person-6 (Name6, f. 15), Person-7 (Name7, f. 15)]
You could even change the predicate all to any in order select all people which match any of the specified filters.
Elaborating my comment above:
As functions are first class citizens in Python, you could write a bunch of matcher functions, put them (dynamically) in a list and match against them in a single list comprehension.
Let predicates be a list of one-argument functions of type Person -> bool.
Then simply do:
[ pers for pers in world if all([f(pers) for f in predicates]) ]
Further exploring the functional route of thinking, you can create "dynamic matching functions" by creating functions returning matching functions:
def age_matcher(age):
return lambda p: p.age > age
An age_matcher(someAge) can be added to your predicates array.
Side note
For these "database-search"-like tasks, you will probably want to really should look at libraries like Pandas, where you can make queries similar to SQL. You may be re-inventing a fairly complex type of wheel.
How about this?
def search(self, condition):
return filter(condition, self.l)
def search_re(self, **kwargs):
filters = []
for key, value in kwargs.items():
if isinstance(value, str):
value = re.compile(value)
filters.append(lambda x: re.search(getattr(x, key), value))
elif callable(value):
filters.append(lambda x: value(getattr(x, key)))
else:
filters.append(lambda x: getattr(x, key) == value)
def condition(person):
return all(
f(person) for f in filters
)
return self.search(condition)
Usage:
persons.search(lambda x: x.name == "bla")
persons.search_re(name=".*Smith", gender="male")

How to think like a Computer Scientist exercise

So as the title hopefully suggest, this is for an example in said book.
I'm still new to programming and having difficulty debugging. With that said any criticism is welcomed, specially if it shows a more efficient way of coding; just keep in mind that I'm still new so there's a good chance i might not know what you're referring to if you toss me a new built-in function or something.
So the point of this exercise is to write a function, giving it three arguments, to determine if those three arguments form a triangle. Here's my code:
def is_triangle(a,b,c):
num_list = [a,b,c]
biggest = max(num_list)
other_two = num_list.remove(biggest)
sum_of_two = sum(other_two)
if sum_of_two > biggest:
print 'Congrats, %d, %d, and %d form a triangle!' % (a,b,c)
elif sum_of_two == biggest:
print 'That forms a degenerate triangle!'
else:
print 'That does\'t make any sort triangle... >:['
def sides():
side1 = raw_input('Please input side numero Juan: ')
side2 = raw_input('Now side two: ')
side3 = raw_input('...aaaannnd three: ')
import time
time.sleep(1)
print 'Thanks >:]'
side1 = int(side1)
side2 = int(side2)
side3 = int(side3)
is_triangle(side1,side2,side3)
sides()
Whenever i run it, however, i get the following:
Traceback (most recent call last):
File "A:/Python/is_triangle.py", line 27, in <module>
sides()
File "A:/Python/is_triangle.py", line 25, in sides
is_triangle(side1,side2,side3)
File "A:/Python/is_triangle.py", line 5, in is_triangle
sum_of_two = sum(other_two)
TypeError: 'NoneType' object is not iterable
My guess is the sum_of_two line but i don't know what's wrong with it. Could someone help me debug this?
I spend a good hour rewriting it with out the built_in function (in various ways, bunch of ors everywhere). But it looked terrible and I'd rather learn to write this way.
The problem is the fact that remove modifies the underlying list - it doesn't return a new list. Change it to:
num_list.remove(biggest)
sum_of_two = sum(num_list)
To see exactly why this happens, try the following in IDLE:
>>> x = [1,2,3,4,5]
>>> x.remove(1)
>>> x
[2,3,4,5]
Sincenum_list.remove(biggest) returns None, Consider this instead
other1, other2, biggest = sorted(num_list)
sum_of_two = other1 + other2
It looks like the if block needs to be indented too
def is_triangle(a, b, c):
num_list = [a, b, c]
other1, other2, biggest = sorted(num_list)
sum_of_two = other1 + other2
if sum_of_two > biggest:
print 'Congrats, %d, %d, and %d form a triangle!' % (a,b,c)
elif sum_of_two == biggest:
print 'That forms a degenerate triangle!'
else:
print 'That does\'t make any sort triangle... >:['
You just made a very common mistake, a simple one. In Python, when a function causes a change in a data structure, it does not return the changed structure. Usually it returns None. If the function gets a new data structure or new value, it returns it.
So, str.lower() doesn't actually change a string; it returns a new string where the characters are lower-case. If you have some list named lst and you run sorted(lst), it doesn't change the list; it returns a new list that is sorted. But lst.sort() sorts the list in-place, so it doesn't return a reference to the list; it returns None.
In the comments below, #lvc pointed out that list.pop() removes a value from a list and returns the value. So, this is an example of a function that changes a data structure and returns something other than None, but it still definitely does not return a reference to the changed data structure.
The list.remove() function changes a list, and it returns None. All you need to do is change your function to use the same list for everything: first use the list to find the max, then remove the max value from the list, then pass the list to sum().
The reason Python does things this way is to obey the "Command/Query Separation" principle.
http://en.wikipedia.org/wiki/Command%E2%80%93query_separation

Categories