What’s wrong with my ternary expression or mapping in Python? - python

I need to retrieve a list (lets call it list1) of the double of every element smaller than 5 and put them in another list (let’s call it list2). I tried to use map as below but for some reason I get invalid syntax:
list2 = map(lambda x: x*2 if x < 5, list1)
I suspect it’s because the ternary expression needs an else condition. Is that it? And what should I do about it?

You’re right about the ternary expression part. Python doesn't allow you to use the syntax: var = <action> if <condition> without else because, in the case where <condition> == False, var becomes unknown.
You don’t really need map, however, you could use list comprehensions because not only do they solve your problems, they’re more efficient than mapping:
list2 = [x*2 for x in list1 if x < 5]

Related

list.remove() not works but work when llist.remove() twice

I have list like words = [MyOwnClass(), MyOwnClass(), None, MyOwnClass(), None]. I wanted to delete None.
I tried words.remove(None). but only worked when did it twice.
is this right way? or not, I want to know pythonic way.
class Dictionary:
words = [Word(), Word(), None, Word(), None]
def delete_None(self)
self.words.remove(None) # not works
self.words.remove(None) # now works
If you have to do multiple removes, it is better to rebuild from scratch:
words[:] = (x for x in words if x is not None)
This is a single linear operation, while every remove itself is linear as well because of the left-shifts needed for all subsequent elements.
Also note that the slice assignment words[:] = ... makes this a mutation on the original list object (just like the remove calls would be), not just a rebind of the variable whose effects may only be local. The generator (...) expression instead of a list comprehension [...] is more space efficient as it doesn't build an intermediate list, though the comprehension might be slightly faster.
For arbitrary objects, testing equality instead of identity would often be preferable:
lst[:] = (x for x in lst if x != obj_to_remove)
Or for a more functional approach:
from operator import ne # "not equal"
from functools import partial
lst[:] = filter(partial(ne, obj_to_remove), lst)
If you want to remove all the Nones, you can use filter:
words = list(filter(lambda x: x is not None, words))

How to detect a self referencing list in python

I have been trying to find a self-referencing loop in a list. I have referenced the list like this:
a = [1, 2, None]
a[2] = a
What is the right way to detect this loop in python?
I'm not sure if there's a "right" way, but the is operator is your friend. To check if the list contains itself:
any(x is a for x in a)
To find the index of the list:
next(i for i, x in enumerate(a) if x is a)
This version is like a.index in that it raises an error if the list is not found in itself. To simulate a.find instead, add a default value to next:
next(..., -1)
You can't use a.find or a.index directly because those will search by equality, which will be infinitely recursive on a positive match in this case.
Checking for identity should be relatively foolproof in this case.

Can I write a lambda function to raise an exception?

Suppose I have the following python list:
my_list = [1, 2,'X', 'Y', 0]
Suppose I want to copy values of this list into a new list as follows:
If it is a digit between 0-9, copy that value into the new list
ElIf it is 'X', copy None into the new list
Else raise an Exception
Can I do it with a lambda function as shown below? If so, how?
new_list = map(lambda(x): something-here-but-what??, my_list)
Why not just write a function that does what you want and put it in the lambda? I don't see a reason to try to make a convoluted one-liner for something that should be more than one line.
my_list = [1, 2,'X', 'Y', 0]
def replace(x):
if x == 'X':
return None
elif type(x) == int and x <= 9 and x >= 0:
return x
else:
raise ValueError('Bad value')
new_list = map(lambda(x): replace(x), my_list[:-2]) # Returns [1, 2, None]
new_list = map(lambda(x): replace(x), my_list) # Raises exception
To back up Brenden's (quite correct) answer...
You can actually do some weird things with Python ternary expressions... but the result is just unbearable. Consider a partial solution:
>>> new_list = map(lambda x: x if isinstance(x, int) and (0 <= x and x <= 9) else ValueError('Bad things happened'), [1, 2, 3, "blah"])
>>> list(new_list)
[1, 2, 3, ValueError('Bad things happened',)]
Not only is that horrid and would probably confuse most Pythonistas (not just the use of an unusual construction, but why would you use this construction?), I don't know quite what to do yet about actually raising the exception right there without redefining the way list() works. (raise only works when it is standing alone.)
So now we have a confusing lambda that conditionally permits a member into the new map construction or includes a ValueError object instead. Yuk.
Much better to abstract this whole idea away behind a function that does, in a very simple way, exactly what you want -- and let the "beautiful code part" be the bit people will normally need to read in the future that goes something like:
new_list = valid_list_to_map(your_list)
Use a conditional expression.
a = list(map(lambda n: n if n in (0,1,2,3,4,5,6,7,8,9) else (None if n == 'X' else 1/0), my_list))
Other exceptions that can be raised:
In the conditional expression replace 1/0 with
{}[n] #KeyError
x #NameError
(_ for _ in ()).throw(Exception('Foo'))) #any kind of exception you want
int('x') #ValueError
To raise an exception you have to use 'try' and 'except' statement and Statements are not allowed in the lambda expression. In the Lambda expression, you can only have expressions so you can't raise the exception in the lambda function.

Is there a `?.` operator in Python?

Groovy has a very handy operator ?.. This checks if the object is not null and, if it is not, accesses a method or a property. Can I do the same thing in Python?
The closest I have found is the ternary conditional operator. Right now I am doing
l = u.find('loc')
l = l.string if l else None
whereas it would be nice to write
l = u.find('loc')?.string
Update: in addition to getattr mentioned below, I found a relatively nice way to do it with a list:
[x.string if x else None for x in [u.find('loc'), u.find('priority'), ...]]
Another alternative, if you want to exclude None:
[x.string for x in [u.find('loc'), u.find('priority'), ...] if x]
You could write something like this
L = L and L.string
Important to note that as in your ternary example, this will do the "else" part for any "Falsy" value of L
If you need to check specifically for None, it's clearer to write
if L is not None:
L = L.string
or for the any "Falsy" version
if L:
L = L.string
I think using getattr is kind of awkward for this too
L = getattr(L, 'string', None)

When to drop list Comprehension and the Pythonic way?

I created a line that appends an object to a list in the following manner
>>> foo = list()
>>> def sum(a, b):
... c = a+b; return c
...
>>> bar_list = [9,8,7,6,5,4,3,2,1,0]
>>> [foo.append(sum(i,x)) for i, x in enumerate(bar_list)]
[None, None, None, None, None, None, None, None, None, None]
>>> foo
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
>>>
The line
[foo.append(sum(i,x)) for i, x in enumerate(bar_list)]
would give a pylint W1060 Expression is assigned to nothing, but since I am already using the foo list to append the values I don't need to assing the List Comprehension line to something.
My questions is more of a matter of programming correctness
Should I drop list comprehension and just use a simple for expression?
>>> for i, x in enumerate(bar_list):
... foo.append(sum(i,x))
or is there a correct way to use both list comprehension an assign to nothing?
Answer
Thank you #user2387370, #kindall and #Martijn Pieters. For the rest of the comments I use append because I'm not using a list(), I'm not using i+x because this is just a simplified example.
I left it as the following:
histogramsCtr = hist_impl.HistogramsContainer()
for index, tupl in enumerate(local_ranges_per_histogram_list):
histogramsCtr.append(doSubHistogramData(index, tupl))
return histogramsCtr
Yes, this is bad style. A list comprehension is to build a list. You're building a list full of Nones and then throwing it away. Your actual desired result is a side effect of this effort.
Why not define foo using the list comprehension in the first place?
foo = [sum(i,x) for i, x in enumerate(bar_list)]
If it is not to be a list but some other container class, as you mentioned in a comment on another answer, write that class to accept an iterable in its constructor (or, if it's not your code, subclass it to do so), then pass it a generator expression:
foo = MyContainer(sum(i, x) for i, x in enumerate(bar_list))
If foo already has some value and you wish to append new items:
foo.extend(sum(i,x) for i, x in enumerate(bar_list))
If you really want to use append() and don't want to use a for loop for some reason then you can use this construction; the generator expression will at least avoid wasting memory and CPU cycles on a list you don't want:
any(foo.append(sum(i, x)) for i, x in enumerate(bar_list))
But this is a good deal less clear than a regular for loop, and there's still some extra work being done: any is testing the return value of foo.append() on each iteration. You can write a function to consume the iterator and eliminate that check; the fastest way uses a zero-length collections.deque:
from collections import deque
do = deque([], maxlen=0).extend
do(foo.append(sum(i, x)) for i, x in enumerate(bar_list))
This is actually fairly readable, but I believe it's not actually any faster than any() and requires an extra import. However, either do() or any() is a little faster than a for loop, if that is a concern.
I think it's generally frowned upon to use list comprehensions just for side-effects, so I would say a for loop is better in this case.
But in any case, couldn't you just do foo = [sum(i,x) for i, x in enumerate(bar_list)]?
You should definitely drop the list comprehension. End of.
You are confusing anyone reading your code. You are building a list for the side-effects.
You are paying CPU cycles and memory for building a list you are discarding again.
In your simplified case, you are overlooking the fact you could have used a list comprehension directly:
[sum(i,x) for i, x in enumerate(bar_list)]

Categories