Is using map(...) in Python 3 Pythonic (compared to a loop) - python

I want to convert a list of string representations of tuples, such as:
["(279, 256000, '.m4a')", "(217, 256000, '.m4a')", "(174, 128000, '.mp3')"]
into a list of tuples, such as:
[(279, 256000, '.m4a'), (217, 256000, '.m4a'), (174, 128000, '.mp3')]
This seems to be the most concise (and clear) way to do it
recs = ... # loaded from text file
data = map(eval, recs)
However, Ive seen a posting Python course - lambda
that seems to imply map() may not be good python or may become deprecated.
The alternative would seem to be something like the more verbose (and hence slightly less clear):
data = []
for r in recs:
data += [eval(r)]
Which is more pythonic?

map is fine when used with a named function; it’s when you use it with an inline lambda that a list comprehension or generator expression becomes cleaner. eval, on the other hand, is not really fine. Consider ast.literal_eval instead.
import ast
data = map(ast.literal_eval, recs)
Also, map returns an iterable (in Python 3); if you want a list, you’ll have to call list on the result.
data = list(map(ast.literal_eval, recs))

In my opinion using map is a nice functional solution, but can be seen as a redundant language feature since generators were added. On the other hand your example that iterates over the array and concatenates to a list is not Pythonic.
Some Pythonic alternatives to map:
# a list comprehesion
[ast.literal_eval(r) for r in recs]
or
# a generator expression
(ast.literal_eval(r) for r in recs)
or
# a generator function
def mapYourData(recs):
for r in recs:
yield ast.literal_eval(r)
Don't forget to import ast
EDIT: as #minitech pointed out you shold use ast.literal_eval instead of eval.

Related

Can the walrus operator be used to avoid multiple function calls within a list comprehension?

Let's say I have a list of lists like this
lol = [[1, 'e_r_i'], [2, 't_u_p']]
and I want to apply a function to the string elements which returns several values from which I need only a subset (which ones differ per use-case). For illustration purposes, I just make a simple split() operation:
def dummy(s):
return s.split('_')
Now, let's say I only want the last two letters and concatenate those; there is the straightforward option
positions = []
for _, s in lol:
stuff = dummy(s)
positions.append(f"{stuff[1]}{stuff[2]}")
and doing the same in a list comprehension
print([f"{dummy(s)[1]}{dummy(s)[2]}" for _, s in lol])
both give the identical, desired outcome
['ri', 'up']
Is there a way to use the walrus operator here in the list comprehension to avoid calling dummy twice?
PS: Needless to say that in reality the dummy function is far more complex, so I don't look for a better solution regarding the split but it is fully about the structure and potential usage of the walrus operator.
I will have to say that your first explicit loop is the best option here. It is clear, readable code and you're not repeating any calls.
Still, as you asked for it, you could always do:
print([f"{(y:=dummy(s))[1]}{y[2]}" for _, s in lol])
You could also wrap the processing in another function:
def dummy2(l):
return f"{l[1]}{l[2]}"
And this removes the need of walrus altogether and simplifies the code further:
print([dummy2(dummy(s)) for _, s in lol])
Yes. This is what you want
output = [f"{(stuff := dummy(s))[1]}{stuff[2]}" for _, s in lol]

When and why to map a lambda function to a list

I am working through a preparatory course for a Data Science bootcamp and it goes over the lambda keyword and map and filter functions fairly early on in the course. It gives you syntax and how to use it, but I am looking for why and when for context. Here is a sample of their solutions:
def error_line_traces(x_values, y_values, m, b):
return list(map(lambda x_value: error_line_trace(x_values, y_values, m, b, x_value), x_values))
I feel as if every time I go over their solutions to the labs I've turned a single return line solution into a multi-part function. Is this style or is it something that I should be doing?
I'm not aware of any situations where it makes sense to use a map of a lambda, since it's shorter and clearer to use a generator expression instead. And a list of a map of a lambda is even worse cause it could be a list comprehension:
def error_line_traces(x_values, y_values, m, b):
return [error_line_trace(x_values, y_values, m, b, x) for x in x_values]
Look how much shorter and clearer that is!
A filter of a lambda can also be rewritten as a comprehension. For example:
list(filter(lambda x: x>5, range(10)))
[x for x in range(10) if x>5]
That said, there are good uses for lambda, map, and filter, but usually not in combination. Even list(map(...)) can be OK depending on the context, for example converting a list of strings to a list of integers:
[int(x) for x in list_of_strings]
list(map(int, list_of_strings))
These are about as clear and concise, so really the only thing to consider is whether people reading your code will be familiar with map, and whether you want to give a meaningful name to the elements of the iterable (here x, which, admittedly, is not a great example).
Once you get past the bootcamp, keep in mind that map and filter are iterators and do lazy evaluation, so if you're only looping over them and not building a list, they're often preferable for performance reasons, though a generator will probably perform just as well.

How could I use map() in this code?

I have a string list
[str1, str2, str3.....]
and I also have a def to check the format of the strings, something like:
def CheckIP(strN):
if(formatCorrect(strN)):
return True
return False
Now I want to check every string in list, and of course I can use for to check one by one. But could I use map() to make code more readable...?
You can map your list to your function and then use all to check if it returns True for every item:
if all(map(CheckIP, list_of_strings)):
# All strings are good
Actually, it would be cleaner to just get rid of the CheckIP function and use formatCorrect directly:
if all(map(formatCorrect, list_of_strings)):
# All strings are good
Also, as an added bonus, all uses lazy-evaluation. Meaning, it only checks as many items as are necessary before returning a result.
Note however that a more common approach would be to use a generator expression instead of map:
if all(formatCorrect(x) for x in list_of_strings):
In my opinion, generator expressions are always better than map because:
They are slightly more readable.
They are just as fast if not faster than using map. Also, in Python 2.x, map creates a list object that is often unnecessary (wastes memory). Only in Python 3.x does map use lazy-computation like a generator expression.
They are more powerful. In addition to just mapping items to a function, generator expressions allow you to perform operations on each item as they are produced. For example:
sum(x * 2 for x in (1, 2, 3))
They are preferred by most Python programmers. Keeping with convention is important when programming because it eases maintenance and makes your code more understandable.
There is talk of removing functions like map, filter, etc. from a future version of the language. Though this is not set in stone, it has come up many times in the Python community.
Of course, if you are a fan of functional programming, there isn't much chance you'll agree to points one and four. :)
An example, how you could do:
in_str = ['str1', 'str2', 'str3', 'not']
in_str2 = ['str1', 'str2', 'str3']
def CheckIP(strN):
# different than yours, just to show example.
if 'str' in strN:
return True
else:
return False
print(all(map(CheckIP, in_str))) # gives false
print(all(map(CheckIP, in_str2))) # gives true
L = [str1, str2, str3.....]
answer = list(map(CheckIP, L))
answer is a list of booleans such that answer[i] is CheckIP(L[i]). If you want to further check if all of those values are True, you could use all:
all(answer)
This returns True if and only if all the values in answer are True. However, you may do this without listifying:
all(map(CheckIP, L)), as, in python3, `map` returns an iterator, not a list. This way, you don't waste space turning everything into a list. You also save on time, as the first `False` value makes `all` return `False`, stopping `map` from computing any remaining values

Saving strings into list

I have a function which returns strings. What I would like to do is get these strings and save it into a list. How can I do this?
for i in objects:
string = getstring(i)
x = 0
list[x] = string
x = x+1
You should first declare the list:
L = []
then, in the for loop, you can append items to it:
for i in objects:
string = getstring(i)
L.append(string)
It is better to use a list comprehension in this case,
my_list = [getstring(obj) for obj in objects]
Instead of creating a list and storing string in it, we are creating a list of strings, based on the objects in objects. You can do the same with map function as well
my_list = map(getstring, objects)
This takes each and every item in objects and applies getstring function to them. All the results are gathered in a list. If you are using Python 3.x, then you might want to do
my_list = list(map(getstring, objects))
Since using map is not preferred, whenever possible go with List Comprehension. Quoting from the BDFL's blog,
Curiously, the map, filter, and reduce functions that originally motivated the introduction of lambda and other functional features have to a large extent been superseded by list comprehensions and generator expressions.

Use Numpy to slices rows?

Have table want to use numpy to slice into sections
table = ['212:3:0:70.13911:-89.85361:3', '212:3:1:70.28725:-89.77466:7', '212:3:2:70.39231:-89.74908:9', '212:3:3:70.48806:-89.6414:11', '212:3:4:70.60366:-89.51539:14', '212:3:5:70.60366:-89.51539:14', '212:3:6:70.66518:-89.4048:16']
t = np.asarray (table, dtype ='object')
Want to use numpy to slice all........ 212:3:0, 212:3:1 as k.
Want all '212:3:0:70.13911:-89.85361:3','212:3:1:70.28725:-89.77466:7' as v
into a dictionany dict (k,v). I dont want to use a for-loop to do this...
I have done this as for loop its slow.
NOTE: the row has ":", but the ":" does mean the dict ':'.
Basics of dict comprehensions
To convert something into a dict, you need to make it into an iterable that generates 2-sequences (anything that generates a sequence of two elements), like [[1,2],[3,4]] or [(1,2),(3,4)] or zip([1,2,3,4], [5,6,7,8]))
E.g.
>>> mylst = [(1,2), (3,4), (5,6)]
>>> print dict(mylst)
{1:2, 3:4, 5:6}
so you need to split each of your strings in such a way that you produce a
tuple. say you've already written a function that does this, called
split_item that takes in a two strings and returns a tuple. You could then
write a generator expression like the following so that you don't need to load
everything into memory until you create the dict.
def generate_tuples(table):
length = len(table)
for i in range(1, length - 1):
yield split_item(table[i-1], table[i])
then just call the dict builtin on your generator function.
>>> dict(generate_tuples(table))
Since you say you already wrote this with a for-loop, I'm guessing you already have a split_items function written.
Making it fast
Here's a guide to high-performance Python, written by Ian Ozsvald, that can help you experiment with other ways to increase the speed of processing. (credit to #AndrewWalker 's SO post here)
Is this what you're after?
dict( (t.rsplit(':', 3)[0], t) for t in table ) )

Categories

Resources