using filter in Python 3 - python

I am pretty much copying from the example given in online docs, but using filter() in windows-based Python 3 is perplexing me. What is wrong here:
a=[1,2,3,4]
b=[1,0,1,0]
f=filter(b,a)
for fs in f : print(fs)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'list' object is not callable
f=list(filter(b,a))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'list' object is not callable
Online docs say to throw list() in, but that clearly helps not.

First argument of filter must be a function or lambda. You passed b which is a list.

Filter's documentation clearly states that it takes a function and an iterable as input. Perhaps if you want to check if a value is in both a and b, you could do something like:
f = filter(lambda x: x in b, a)
and then loop over f.

You are misunderstanding how filter works, filter needs a function that returns True for each item that you want to keep.
I'm assuming b describes the things in a you want to select, then you meant something like::
f = filter(lambda x: x[1], zip(a, b))
for fs, _ in f: ...
You can also replace filter with a list comprehension:
f = [x for x, y in zip(a, b) if y]
for fs in f: ...
But there is a function in itertools that does exactly this:
import itertools as it
f = it.compress(a, b)
for fs in f: ...

Basically filter takes a function and an iterable value to filter in
a = [1,2,3,4]
b = [1,0,1,0]
f = filter(lambda val: val not in b, a)
for fs in f: print(fs)
output:
2
3
4
Confusion occurs when we call filter with None
list(filter(None, [0,1,2]))
# This returns [1,2] Which is a special case
# and it is not equal to:
list(filter(1, [0,1,2])) #invalid
The reason being When we pass filter condition as None filter function checks each value in the list and sees if they qualify the if value condition.
Which roughly translates to this.
[value for value in [0,1,2] if value]

Related

TypeError: 'int' object is not callable when using reduce and lambda

from functools import reduce
lists = [lambda x: x+5, lambda x: x+6, lambda x: x+7]
n = 5
print(list(reduce(lambda a, b: a(n) + b(n), lists)))
I am not sure why I am getting the error "TypeError: 'int' object is not callable"
a and b are both functions, but only b is guaranteed to be a function from the list argument. a is the result of the last call to of the previous call to the first argument to reduce. You want to reduce lists using function composition.
>>> reduce(lambda a, b: lambda n: b(a(n)), lists)(5)
23
(In this particular case, a(b(n)) would work the same, because all three functions involved the same commutative operation, so the composition is commutative as well. In general, though, a is bound to the first function and b the second on the first iteration; after that, a is bound to the result and b bound to the next function from the list.)
To make it work with an empty list, you need to supply the identity function (lambda x: x) as the initial value, as that is the identity for function composition:
>>> reduce(lambda a, b: lambda n: b(a(n)), [])(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: reduce() of empty sequence with no initial value
>>> reduce(lambda a, b: lambda n: b(a(n)), [], lambda x: x)(5)
5
reduce uses the previous result of calling the function as one of the inputs to the next invocation. The function returns an int the first time you call it, so the second time you call it its two arguments are that int and another function -- and an int isn't a valid parameter for the reducing function you've provided (it expects two functions), so you get that error.
You need to supply an initial value to reduce and accumulate other values later.
reduce(lambda acc, b: acc + b(n), lists, 0)
Alternative, we can split this into two operations: a map and a reduce.
reduce(lambda a, b: a + b, map(lambda f: f(n), lists), 0)
reduce with + and 0 is just sum, so we can shorten this to
sum(map(lambda f: f(n), lists))
or, equivalently
sum(f(n) for f in lists)

Turning any single-argument function into a generator function?

In David Beazley's talk on generators, he shows how to create a generator function from any single-argument function thus:
def generate(func):
def gen_func(s):
for item in s:
yield func(item)
return gen_func
and illustrates it with math.sqrt:
gen_sqrt = generate(math.sqrt)
for x in gen_sqrt(xrange(100)):
print x
So why does:
gen_sum = generate(sum)
for x in gen_sum([1,2,3,4,5]):
print x
produce:
TypeError Traceback (most recent call last)
<ipython-input-73-ef521f2bbfc8> in <module>()
1 gen_sum = generate(sum)
----> 2 for x in gen_sum(1):
3 print x
<ipython-input-50-3c0ba12c2429> in gen_func(s)
1 def generate(func):
2 def gen_func(s): # closure
----> 3 for item in s:
4 yield func(item)
5 return gen_func
TypeError: 'int' object is not iterable
Is it more accurate to say that the function being a single-argument function is a necessary but insufficient condition for this approach to work? And that the other necessary condition is that the single argument must be a single item (and not a sequence)?
You're passing a list whose elements are the wrong type. Of course it's not going to work, for the same reason that gen_sqrt(['a', 's', 'd', 'f']) wouldn't have worked.
You need to pass gen_sum a list of things it makes sense to call sum on, such as other lists:
for x in gen_sum([[1, 2], [3, 4]]):
print x
Output:
3
7
You are correct, both are necessary requirements:
def generate(func):
def gen_func(s):
for item in s: # <--- requires s to be interable
yield func(item) # <--- requires func to accept a single argument
return gen_func
So in
gen_sum = generate(func)
for x in gen_sum(s):
print x
func must accept a single argument and s must be iterable.
generate is the generator version of map.
map(lambda x: x*2, range(5)) [0, 2, 4, 6, 8]
It takes the input range and applies the function the each of the element in the range.
generate does the same, but it doesn't return a list. It yields the result of each transformation.
Now take a look at your example. What would be the first result? sum(1).
But sum expects a list, not an integer, hence the error message.

generator that does not necessarily yield anything

I want to have a generator that may or may not have anything to yield, and if .next() or similar is used it will not have a StopIteration error if none of the conditions to yield are met.
An example:
def A(iterable):
for x in iterable:
if x == 1:
yield True
Which works like the following:
>>> list(A([1,2]))
[True]
>>> A([1]).next()
True
>>> A([2]).next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
But what I would want is for A([2]).next() to return is None.
generally, in these circumstances you'd use the builtin next function:
my_iterator = A([2])
value = next(my_iterator, None)
In addition to being able to pass an optional "default" value when the iterator is empty, next has the advantage of working on python2.x and python3.x where the name of the method changes to __next__.
How about this:
def A(iterable):
for x in iterable:
yield True if x == 1 else None

Python curiosity: [] > lambda n: n

One of my coworkers was using the builtin max function (on Python 2.7), and he found a weird behavior.
By mistake, instead of using the keyword argument key (as in key=lambda n: n) to pre-sort the list passed as a parameter, he did:
>>> max([1,2,3,3], lambda n : n)
[1, 2, 3, 3]
He was doing what in the documentation is explained as:
If two or more positional arguments are provided, the largest of the positional arguments is returned., so now I'm curious about why this happens:
>>> (lambda n:n) < []
True
>>> def hello():
... pass
...
>>> hello < []
True
>>> len(hello)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object of type 'function' has no len()
I know it's not a big deal, but I'd appreciate if any of the stackoverflowers could explain how those comparisons are internally made (or point me into a direction where I can find that information). :-)
Thank you in advance!
Python 2 orders objects of different types rather arbitrarily. It did this to make lists always sortable, whatever the contents. Which direction that comparison comes out as is really not of importance, just that one always wins. As it happens, the C implementation falls back to comparing type names; lambda's type name is function, which sorts before list.
In Python 3, your code would raise an exception instead:
>>> (lambda n: n) < []
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: function() < list()
because, as you found out, supporting arbitrary comparisons mostly leads to hard-to-crack bugs.
Everything in Python (2) can be compared, but some are fairly nonsensical, as you've seen.
>>> (lambda n:n) < []
True
Python 3 resolves this, and produces exceptions instead.

Create function in python to find the highest of all function arguments, and return the "tag" of the value

Consider the following:
p1=1;
p2=5;
p3=7;
highest=max(p1,p2,p3).
The max function would return 7. I am looking to create a similar function, which would return "p3". I have created a small function (by simple comparisons) for the above example, shown below. however I am having trouble when the number of arguments go up.
def highest(p1,p2,p3):
if (p1>p2) and (p1>p3):
return "p1"
if (p2>p1) and (p2>p3):
return "p2"
if (p3>p1) and (p3>p1):
return "p3"
Is there a simpler way to do this>
Update: Paul Hankin pointed out that max() took a key function, which I didn't know. So:
>>> def argmax(**kw):
... return max(kw, key=kw.get)
...
>>> argmax(foo=3, bar=5, frotz=1, kaka=-3)
'bar'
Other solutions for completeness:
In Python 2.7 and 3.x you can use dictionary comprehensions.
>>> def argmax(**kw):
... wk = {v:k for k,v in kw.items()}
... return wk[max(wk)]
...
>>> argmax(foo=3, bar=5, frotz=1, kaka=-3)
'bar'
Dictionary comprehensions are neat. :)
In earlier versions of Python you can do this:
>>> def argmax(**kw):
... wk = dict([(v,k) for k,v in kw.items()])
... return wk[max(wk)]
...
>>> argmax(foo=3, bar=5, frotz=1, kaka=-3)
'bar'
Which will work in anything after Python 2.2 or so.
There is no way to get the name of the variable that had the highest value in the caller (because it might be a number or a complex expression), but by using keyword arguments exclusively, you can get the name of the parameter. Something like this:
def argmax(**kwargs):
mx = -1e+400 # overflows to -Inf
amx = None
for k, v in kwargs.iteritems():
if v > mx:
mx = v
amx = k
return amx
Works like this:
>>> argmax(a=1,b=2,c=3)
'c'
but the catch is, it doesn't work if any of the arguments is positional:
>>> argmax(1,2,3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: argmax() takes exactly 0 arguments (3 given)
Depending on what you're doing, this construct might be more useful: (hat tip to http://lemire.me/blog/archives/2008/12/17/fast-argmax-in-python/ )
>>> a = [9,99,999]
>>> a.index(max(a))
2
The only way to keep this anything close to extensible is to take a list as argument and return the index of its highest element. You can put a p in front and begin counting at 1 if you really want to.
def highest(x):
return 'p' + repr(x.index(max(x))+1)
Obviously it dose not handle variable length arguments. If you want variable length argument then that's a different issue. If you have 10 arguments then just add them in the definition and it will return the correct argument name (not necessarily started with 'p'). But the catch is the number of arguments (3 or 5 or 10 or whatever else) is not variable. You need to know how many arguments you require.
def highest(p1,p2,p3,p4,p5):
d = locals()
keys = d.keys()
max_key = keys[0]
max_val = d[max_key]
for i in range(1,len(keys)):
key = keys[i]
val = d[key]
if val > max_val:
max_val = val
max_key = key
return max_key
print highest(3,2,5,10,1)
print highest(1,5,2,2,3)
print highest(5,2,5,1,11)
print highest(3,2,1,1,2)

Categories

Resources