Is there a map without result in python?

2019-01-15 06:04发布

问题:

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 ...)

回答1:

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.



回答2:

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.



回答3:

How about this?

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


回答4:

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.



回答5:

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).



回答6:

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


回答7:

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 :)



回答8:

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


回答9:

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.



回答10:

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.



回答11:

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



回答12:

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)


回答13:

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.



回答14:

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']