Home > Back-end >  Python 3 map is returning a list of NoneType objects instead of the type I modified?
Python 3 map is returning a list of NoneType objects instead of the type I modified?

Time:10-08

I'm working on getting a better grasp of Python 3 fundamentals, specifically objects and modifying them in the context of a list (for now).

I created a simple class called MyThing() that just has a number, letter, and instance method for incrementing the number. My goal with this program was to create a list of 3 "MyThings", and manipulate the list in various ways. To start, I iterated through the list (obj_list_1) and incremented each number using each object's instance method. Easy enough.

What I'm trying to figure out how to do is perform the same operation in one line using the map function and lambda expressions (obj_list_2).

#!/usr/bin/env py
import copy

class MyThing:
    def __init__(self, letter='A', number=0):
        self.number = number
        self.letter = letter

    def __repr__(self) -> str:
        return("(letter={}, number={})".format(self.letter, self.number))
        
    def incr_number(self, incr=0):
        self.number  = incr

# Test program to try different ways of manipulating lists
def main():
    obj1 = MyThing('A', 1)
    obj2 = MyThing('B', 2)
    obj3 = MyThing('C', 3)

    obj_list_1 = [obj1, obj2, obj3]
    obj_list_2 = copy.deepcopy(obj_list_1)    

    # Show the original list
    print("Original List: {}".format(obj_list_1))
    # output: [(letter=A, number=1), (letter=B, number=2), (letter=C, number=3)]

    # Standard iterating over a list and incrementing each object's number.
    for obj in obj_list_1:
        obj.incr_number(1)
    print("For loop over List, adding one to each number:\n{}".format(obj_list_1))
    # output: [(letter=A, number=2), (letter=B, number=3), (letter=C, number=4)] 

    # Try using map function with lambda
    obj_list_2 = list(map(lambda x: x.incr_number(1), obj_list_2))
    print("Using maps with incr_number instance method:\n{}".format(obj_list_2))
    # actual output: [None, None, None] <--- If I don't re-assign obj_list_2...it shows the proper sequence
    # expected output: [(letter=A, number=2), (letter=B, number=3), (letter=C, number=4)] 

if __name__ == "__main__":
    main()

What I can't figure out is how to get map() to return the correct type, a list of "MyThing"s.

I understand that between Python 2 and Python 3, map changed to return an iterable instead of a list, so I made sure to cast the output. What I get is a list of 'None' objects.

What I noticed, though, is that if I don't re-assign obj_list_2, and instead just call list(map(lambda x: x.incr_number(1), obj_list_2)), then print obj_list_2 in the next line, the numbers get updated as I expect.

However, if I don't cast the map iterable and just do map(lambda x: x.incr_number(1), obj_list_2), the following print statement shows the list as having not been updated. I read in some documentation that the map function is lazy and doesn't operate until it's use by something...so this makes sense.

Is there a way that I can get the output of list(map(lambda x: x.incr_number(1), obj_list_2)) to actually return my list of objects?

Are there any other cool one-liner solutions for updating a list of objects with their instance methods that I'm not thinking of?

CodePudding user response:

This is because, your incr_number doesn't return anything. Change it to:

def incr_number(self, incr=0):
    self.number  = incr
    return self

CodePudding user response:

TL;DR: Just use the for-loop. There's no advantage to using a map in this case.


Firstly:

  • You're getting a list of Nones because the mapped function returns None. That is, MyThing.incr_number() doesn't return anything, so it returns None implicitly.

  • Fewer lines is not necessarily better. Two simple lines are often easier to read than one complex line.

  • Notice that you're not creating a new list in the for-loop, you're only modifying the elements of the existing list.

  • list(map(lambda)) is longer and harder to read than a list comprehension:

    [x.incr_number(1) for x in obj_list_2]
    

    vs

    list(map(lambda x: x.incr_number(1), obj_list_2))
    

Now, take a look at Is it Pythonic to use list comprehensions for just side effects? The top answer says no, it creates a list that never gets used. So there's your answer: just use the for-loop instead.

CodePudding user response:

The loop is clearly better, but here's another way anyway. Your incr_number doesn't return anything, or rather returns the default None. Which is a false value, so if you simply append or x, then you do get the modified value instead of the None

Change

list(map(lambda x: x.incr_number(1), obj_list_2))

to this:

list(map(lambda x: x.incr_number(1) or x, obj_list_2))
  • Related