34

Sometimes, I just want to execute a function for a list of entries -- eg.:

for x in wowList:
   installWow(x, 'installed by me')

Sometimes I need this stuff for module initialization, so I don't want to have a footprint like x in global namespace. One solution would be to just use map together with lambda:

map(lambda x: installWow(x, 'installed by me'), wowList)

But this of course creates a nice list [None, None, ...] so my question is, if there is a similar function without a return-list -- since I just don't need it.

(off course I can also use _x and thus not leaving visible footprint -- but the map-solution looks so neat ...)

Juergen
  • 12,378
  • 7
  • 39
  • 55
  • I assume you do not want to create your own apply function which will do it for you? – Anurag Uniyal Jul 03 '09 at 17:23
  • @Anurag: You are right. I don't want to do it, if it can be avoided. But off course, this could be a possible solution. – Juergen Jul 03 '09 at 17:44
  • That would be the easiest solution(not worth answering) and in real world may be that will be used, but as a puzzle that is no fun, thanks for the nice puzzle ;) – Anurag Uniyal Jul 03 '09 at 17:51
  • Similar to the answer you accepted, you could just write `any(installWow(x, 'installed by me') for x in wowList)` if the function doesn't return anything. If it does, you could use `all()` instead. – martineau Dec 08 '12 at 00:59

16 Answers16

30

You could make your own "each" function:


def each(fn, items):
    for item in items:
        fn(item)


# called thus
each(lambda x: installWow(x, 'installed by me'), wowList)

Basically it's just map, but without the results being returned. By using a function you'll ensure that the "item" variable doesn't leak into the current scope.

John Montgomery
  • 8,868
  • 4
  • 33
  • 43
18

You can use the built-in any function to apply a function without return statement to any item returned by a generator without creating a list. This can be achieved like this:

any(installWow(x, 'installed by me') for x in wowList)

I found this the most concise idom for what you want to achieve.

Internally, the installWow function does return None which evaluates to False in logical operations. any basically applies an or reduction operation to all items returned by the generator, which are all None of course, so it has to iterate over all items returned by the generator. In the end it does return False, but that doesn't need to bother you. The good thing is: no list is created as a side-effect.

Note that this only works as long as your function returns something that evaluates to False, e.g., None or 0. If it does return something that evaluates to True at some point, e.g., 1, it will not be applied to any of the remaining elements in your iterator. To be safe, use this idiom mainly for functions without return statement.

f0xdx
  • 1,379
  • 15
  • 20
  • 7
    This is dangerous: If `installWow` is modified so it does return a "true" value sometimes, it will not be applied to all items in `wowList`. – mkrieger1 Nov 29 '17 at 00:49
  • Correct, hence the caveat "[...] to apply a function without return statement to any item [...]". I will update the answer to emphasize this point. – f0xdx Nov 29 '17 at 11:17
  • 3
    This is an awfully error-prone and hard to read solution for something so simple. You'd be *much* better off writing a 2-liner function with a `for` loop inside, as seen in [this answer](https://stackoverflow.com/a/1080300/1222951). – Aran-Fey Aug 03 '18 at 11:18
  • 4
    The OPs question was how to avoid creating a result list of `[None, None, ...]` when mapping a function to a list of entries. It follows, that this function has no return statement, in which case applying this idom is *not* error prone. Hard to read on the other hand is imho a matter of personal taste. – f0xdx Aug 07 '18 at 15:02
  • 1
    replace the 'any' with simply 'list' -- `list(installWow(x, 'installed by me') for x in wowList)` - now I dont care what installWow returns and will never early exit. – gbtimmon Dec 19 '18 at 15:28
  • 1
    @f0xdx the utility of Stack Overflow comes from the fact that you're not just answering the specific question of the OP but also the question of the 12,000 people that have viewed this question, who are just asking the question in the title, who aren't reading the details (and shouldn't be expected to) and probably don't have the same situation. – Boris Verkhovskiy May 04 '20 at 15:52
14

How about this?

for x in wowList:
    installWow(x, 'installed by me')
del x
RichieHindle
  • 272,464
  • 47
  • 358
  • 399
  • Ok, another possible solution to the original problem, but does not fit into the intented question. – Juergen Jul 03 '09 at 16:49
  • 1
    @Juergen: You said "I don't want to have a footprint like x in global namespace". Later you said "I am looking for an elegant and fast implementation". Now this answer may not answer the question in the title ("Is there a map without result in python?") but it certainly does answer the question as a whole. That was a harsh downvote, for a answer that tries to solve your problem in an elegant way. – RichieHindle Jul 03 '09 at 16:56
  • 2
    @RichieHindle: Yes, I was looking for an "elegant" and "fast" solution. What about "elegant"?? "del x" is possible, but not elegant in my opinion. Ok, I don't wanted to say it -- this solution is so easy, that I also was using it, but it is not elegant in my opinion. And I don't wanted to list all "unelegant" solutions. The questions title was clearly enough stating what direction I was thinking. Sorry for any inconveniance! – Juergen Jul 03 '09 at 16:59
  • 6
    @Juergen: del x ist way more elegant than using a functional construct like map() or reduce() for the side-effect it produces. If the function application is the important part instead of the returned result then use a loop to make this explicit. –  Jul 03 '09 at 18:05
  • 1
    @unbeknown: good point. functional applications used for side-effect are almost as "inelegant" as is possible. +1 to your comment and +1 to the answer for clarity. – SingleNegationElimination Jul 03 '09 at 18:55
6

You might try this:

filter(lambda x: installWow(x, 'installed by me') and False, wowList)

That way, the return result is an empty list no matter what.

Or you could just drop the and False if you can force installWow() to always return False (or 0 or None or another expression that evaluates false).

Mark Rushakoff
  • 249,864
  • 45
  • 407
  • 398
  • This looks good rather good, since an empty list is better as a growing list. But why do I need "or False"? – Juergen Jul 03 '09 at 16:38
  • 2
    Same thing with list comprehension: [1 for x in wowList if installWow(x, 'installed by me') and False] – stephan Jul 03 '09 at 16:40
  • @Juergen: you're right, the or False was unnecessary because (_ and False) is by definition False. Duh on my part :) Updated accordingly. – Mark Rushakoff Jul 03 '09 at 16:47
  • 5
    @stephan, your LC still pollutes the module's namespace with x (in Python 2.*), so it has zero advantage wrt the plain loop which the OP hates! – Alex Martelli Jul 03 '09 at 17:19
  • 2
    @stephan: `[]` introduces `x` into global namespace (if it is executed at module level) – jfs Jul 03 '09 at 17:20
  • @Alex Martelli, @J.F. Sebastian: Thanks for pointing out that the list comprehension only works as desired for Python 3.x. – stephan Jul 04 '09 at 06:55
  • Elegant solution but that would obscure the entent of the code. Similarly, we could write: reduce(lambda x, y: None, map(lambda x: installWow(x, 'installed by me'), wowList)) – Tarik Jul 02 '19 at 16:46
6

Every expression evaluates to something, so you always get a result, whichever way you do it. And any such returned object (just like your list) will get thrown away afterwards because there's no reference to it anymore.

To clarify: Very few things in python are statements that don't return anything. Even a function call like

doSomething()

still returns a value, even if it gets discarded right away. There is no such thing as Pascal's function / procedure distinction in python.

balpha
  • 50,022
  • 18
  • 110
  • 131
  • 5
    That might be right. But it is just a big difference, if a function (also a builtin function) creates a big, fat list that is just wasted or if the result is just None. Just accumulating values in a list costs temporary memory and CPU cycles. – Juergen Jul 03 '09 at 16:34
  • 1
    I totally agree; I wasn't saying you don't have a point. But the created overhead probably would be the least if you use your for loop example (you could del the x afterwards if you don't want it lying around). – balpha Jul 03 '09 at 16:39
  • Ok, to answer your clarification: I knew that, but map accumulates that something -- None. So the result of map is [None, None, ...] I want to avoid this. – Juergen Jul 03 '09 at 16:40
  • I figured you knew that, the clarification was more in the general sense of "we're building an encyclopedia here." Anyway, I don't think we really disagree on anything here :-) – balpha Jul 03 '09 at 16:44
  • That's ok. I also think, there is not so big disagreement here. It is just a matter of style here. I am looking for an elegant and fast implementation ... what might be a little heretic to some Python purists ... – Juergen Jul 03 '09 at 16:47
  • @Juergen map accumulates nothing. It returns an iterator. "Python map() function is used to apply a function on all the elements of specified iterable and return map object. Python map object is an iterator, so we can iterate over its elements." – Tarik Jul 02 '19 at 16:24
  • @Tarik: Thank you for your comment. You are right, in Python 3.x, map does only return an iterator. But when I asked the question exactly 10 years ago, Python 2.x was there and did return a list. Check the docs. – Juergen Jul 03 '19 at 18:55
3

You could use a filter and a function that doesn't return a True value. You'd get an empty return list since filter only adds the values which evaluates to true, which I suppose would save you some memory. Something like this:

#!/usr/bin/env python
y = 0
def myfunction(x):
  global y
  y += x

input = (1, 2, 3, 4)

print "Filter output: %s" % repr(filter(myfunction, input))
print "Side effect result: %d" % y

Running it produces this output:

Filter output: ()
Side effect result: 10
Alexander Ljungberg
  • 6,302
  • 4
  • 31
  • 37
  • Don't know, why the others don't liked it. I like it. Off course, the implicit condition that myfunction must not return True or something that evaluates to True can make troubles when you want to use it for general usage. But since the other solutions don't hit my intentions much better, I accept your answer also for the reason the others gave such a negative vote. – Juergen Jul 05 '09 at 15:15
  • @Juergen because it doesn't answer the question and is very ugly, being that it still requires the creation of a list, only now it is even more superfluous than is was originally and requires more computation to get there – Dodgie May 14 '17 at 20:51
3

if it is ok to distruct wowList

while wowList: installWow(wowList.pop(), 'installed by me')

if you do want to maintain wowList

wowListR = wowList[:]
while wowListR: installWow(wowListR.pop(), 'installed by me')

and if order matters

wowListR = wowList[:]; wowListR.reverse()
while wowListR: installWow(wowListR.pop(), 'installed by me')

Though as the solution of the puzzle I like the first :)

Anurag Uniyal
  • 85,954
  • 40
  • 175
  • 219
  • If it is ok to destruct the wowList, perhaps prepending a wowList.reverse() would be appropriate, since the order of the list items could be important. pop() pops off the end of the list. pop(0) is inefficient since it continuously shifts all remaining items towards the start of the list. – tzot Jul 03 '09 at 18:50
  • Instead of reverse you could use indexes: [::-1]. But I don't think that this is better rather than using for-loop. We still have an extra variable. If we could just delete it, than why not to delete variable after for-loop. – simplylizz Oct 06 '14 at 13:32
  • For the first example you might as well write `for w in wowList: installWow(w, 'installed by me')` on one line, is even shorter & doesn't distruct lists – Nearoo Feb 10 '20 at 10:58
3

I can not resist myself to post it as separate answer

reduce(lambda x,y: x(y, 'installed by me') , wowList, installWow)

only twist is installWow should return itself e.g.

def installWow(*args):
    print args
    return installWow
Anurag Uniyal
  • 85,954
  • 40
  • 175
  • 219
2

I tested several different variants, and here are the results I got.

Python 2:

>>> timeit.timeit('for x in xrange(100): L.append(x)', 'L = []')
14.9432640076
>>> timeit.timeit('[x for x in xrange(100) if L.append(x) and False]', 'L = []')
16.7011508942
>>> timeit.timeit('next((x for x in xrange(100) if L.append(x) and False), None)', 'L = []')
15.5235641003
>>> timeit.timeit('any(L.append(x) and False for x in xrange(100))', 'L = []')
20.9048290253
>>> timeit.timeit('filter(lambda x: L.append(x) and False, xrange(100))', 'L = []')
27.8524758816

Python 3:

>>> timeit.timeit('for x in range(100): L.append(x)', 'L = []')
13.719769178002025
>>> timeit.timeit('[x for x in range(100) if L.append(x) and False]', 'L = []')
15.041426660001889
>>> timeit.timeit('next((x for x in range(100) if L.append(x) and False), None)', 'L = []')
15.448063717998593
>>> timeit.timeit('any(L.append(x) and False for x in range(100))', 'L = []')
22.087335471998813
>>> timeit.timeit('next(filter(lambda x: L.append(x) and False, range(100)), None)', 'L = []')
36.72446593800123

Note that the time values are not that precise (for example, the relative performance of the first three options varied from run to run). My conclusion is that you should just use a loop, it's more readable and performs at least as well as the alternatives. If you want to avoid polluting the namespace, just del the variable after using it.

Nick
  • 21
  • 2
  • Use the for loop and use the `__all__` parameter that prevent the global namespace from getting cluttered on import. – gbtimmon Dec 19 '18 at 15:41
1

first rewrite the for loop as a generator expression, which does not allocate any memory.

(installWow(x,  'installed by me') for x in wowList )

But this expression doesn't actually do anything without finding some way to consume it. So we can rewrite this to yield something determinate, rather than rely on the possibly None result of installWow.

( [1, installWow(x,  'installed by me')][0] for x in wowList )

which creates a list, but returns only the constant 1. this can be consumed conveniently with reduce

reduce(sum, ( [1, installWow(x,  'installed by me')][0] for x in wowList ))

Which conveniently returns the number of items in wowList that were affected.

SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
1

Just make installWow return None or make the last statement be pass like so:


def installWow(item, phrase='installed by me'):
  print phrase
  pass

and use this:


list(x for x in wowList if installWow(x))

x won't be set in the global name space and the list returned is [] a singleton

freegnu
  • 793
  • 7
  • 11
1

If you're worried about the need to control the return value (which you need to do to use filter) and prefer a simpler solution than the reduce example above, then consider using reduce directly. Your function will need to take an additional first parameter, but you can ignore it, or use a lambda to discard it:

reduce(lambda _x: installWow(_x, 'installed by me'), wowList, None)
benlast
  • 479
  • 4
  • 5
1

Let me preface this by saying that it seems the original poster was more concerned about namespace clutter than anything else. In that case, you can wrap your working variables in separate function namespace and call it after declaring it, or you can simply remove them from the namespace after you've used them with the "del" builtin command. Or, if you have multiple variables to clean up, def the function with all the temp variables in it, run it, then del it.

Read on if the main concern is optimization:

Three more ways, potentially faster than others described here:

  1. For Python >= 2.7, use collections.deque((installWow(x, 'installed by me') for x in wowList),0) # saves 0 entries while iterating the entire generator, but yes, still has a byproduct of a final object along with a per-item length check internally
  2. If worried about this kind of overhead, install cytoolz. You can use count which still has a byproduct of incrementing a counter but it may be a smaller number of cycles than deque's per-item check, not sure. You can use it instead of any() in the next way:
  3. Replace the generator expression with itertools.imap (when installWow never returns True. Otherwise you may consider itertools.ifilter and itertools.ifilterfalse with None for the predicate): any(itertools.imap(installWow,wowList,itertools.repeat('installed by me')))

But the real problem here is the fact that a function returns something and you do not want it to return anything.. So to resolve this, you have 2 options. One is to refactor your code so installWow takes in the wowList and iterates it internally. Another is rather mindblowing, but you can load the installWow() function into a compiled ast like so:

lines,lineno=inspect.getsourcelines(func) # func here is installWow without the parens
return ast.parse(join(l[4:] for l in lines if l)) # assumes the installWow function is part of a class in a module file.. For a module-level function you would not need the l[4:]

You can then do the same for the outer function, and traverse the ast to find the for loop. Then in the body of the for loop, insert the instalWow() function ast's function definition body, matching up the variable names. You can then simply call exec on the ast itself, and provide a namespace dictionary with the right variables filled in. To make sure your tree modifications are correct, you can check what the final source code would look like by running astunparse.

And if that isn't enough you can go to cython and write a .pyx file which will generate and compile a .c file into a library with python bindings. Then, at least the lost cycles won't be spent converting to and from python objects and type-checking everything repeatedly.

parity3
  • 643
  • 9
  • 18
1

A simple DIY whose sole purpose is to loop through a generator expression:

def do(genexpr):
    for _ in genexpr:
        pass

Then use:

do(installWow(x, 'installed by me') for x in wowList)
Michael Ekoka
  • 19,050
  • 12
  • 78
  • 79
1

In python 3 there are some ways to use a function with no return(just use a semicolon in jupyter ot ommit the output from the cell):

[*map(print, MY_LIST)]; # form 1 - unpack the map generator to a list

any(map(print, MY_LIST)); # form 2 - force execution with any

list(map(print, MY_LIST)); # form 3 - collect list from generator
Diogo Santiago
  • 229
  • 2
  • 9
0

Someone needs to answer --

The more pythonic way here is to not worry about polluting the namespace, and using __all__ to define the public variables.

myModule/__init__.py:
     __all__ = ['func1', 'func2']

     for x in range(10): 
         print 'init {}'.format(x)

     def privateHelper1(x):
         return '{}:{}'.format(x,x)

     def func1(): 
         print privateHelper1('func1')

     def func2(): 
         print privateHelper1('func1')

Then

python -c "import myModule; help(myModule);"

init 0
init 1
init 2
init 3
init 4
init 5
init 6
init 7
init 8
init 9
Help on package mm:

NAME
    myModule

FILE
    h:\myModule\__init__.py

PACKAGE CONTENTS


FUNCTIONS
    func1()

   func2()

DATA
   __all__ = ['func1', 'func2']
gbtimmon
  • 4,238
  • 1
  • 21
  • 36