Есть ли карта без результата в Python?

Иногда я просто хочу выполнить функцию для списка записей - например:

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

Иногда мне нужны эти вещи для инициализации модуля, поэтому я не хочу, чтобы в глобальном пространстве имен присутствовал элемент, подобный x. Одним из решений было бы просто использовать карту вместе с лямбда-выражением:

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

Но это, конечно, создает хороший список [None, None, ...], поэтому мой вопрос в том, есть ли подобная функция без списка возврата - поскольку мне просто это не нужно.

(Конечно, я также могу использовать _x и, следовательно, не оставлять видимый след - но решение карты выглядит так аккуратно...)

16 ответов

Решение

Вы можете использовать встроенный any Функция для применения функции без оператора возврата к любому элементу, возвращенному генератором, без создания списка. Это может быть достигнуто следующим образом:

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

Я нашел этот самый краткий смысл для того, чего ты хочешь достичь.

Внутренне installWow функция возвращает None который оценивает False в логических операциях. any в основном применяется or операция сокращения для всех элементов, возвращаемых генератором, которые все None конечно, поэтому он должен перебирать все элементы, возвращаемые генератором. В конце концов, он возвращается False, но это не должно беспокоить вас. Хорошая вещь: никакой список не создан как побочный эффект.

Обратите внимание, что это работает только до тех пор, пока ваша функция возвращает что-то, что оценивается как Falseнапример, None или 0. Если он возвращает что-то, что оценивается как True в какой-то момент, например, 1, он не будет применен ни к одному из оставшихся элементов в вашем итераторе. Чтобы быть в безопасности, используйте эту идиому в основном для функций без оператора возврата.

Вы можете сделать свою собственную функцию "каждая":


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


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

По сути, это просто карта, но без результатов. Используя функцию, вы убедитесь, что переменная "item" не попадет в текущую область.

Как насчет этого?

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

Каждое выражение оценивает что-то, так что вы всегда получаете результат, каким бы способом вы его не делали. И любой такой возвращаемый объект (как и ваш список) впоследствии будет отброшен, потому что на него больше нет ссылок.

Чтобы уточнить: очень мало вещей в Python являются заявлениями, которые ничего не возвращают. Даже вызов функции, как

doSomething()

по-прежнему возвращает значение, даже если оно сразу отбрасывается. В Python нет такого понятия, как различие функций / процедур Паскаля.

Вы можете попробовать это:

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

Таким образом, возвращаемый результат - пустой список, несмотря ни на что.

Или вы могли бы просто бросить and False если вы можете заставить installWow() всегда возвращаться False (или 0 или None или другое выражение, которое оценивает false).

Я не могу удержаться, чтобы опубликовать его как отдельный ответ

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

установка только твист

def installWow(*args):
    print args
    return installWow

Если это нормально, чтобы отвлечь wowList

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

если вы хотите сохранить wowList

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

и если порядок имеет значение

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

Хотя в качестве решения головоломки мне нравится первое:)

Вы можете использовать фильтр и функцию, которая не возвращает значение True. Вы получите пустой список возврата, так как фильтр добавляет только значения, которые оцениваются как true, что, я полагаю, сэкономит вам немного памяти. Что-то вроде этого:

#!/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

Запуск это производит этот вывод:

Filter output: ()
Side effect result: 10

В python 3 есть несколько способов использовать функцию без возврата (просто используйте точку с запятой в jupyter, чтобы исключить вывод из ячейки):

      [*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

Я протестировал несколько разных вариантов, и вот результаты, которые я получил.

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

Обратите внимание, что значения времени не столь точны (например, относительная производительность первых трех опций варьировалась от прогона к прогону). Мой вывод заключается в том, что вы должны просто использовать цикл, он более читабелен и работает по крайней мере так же, как альтернативы. Если вы хотите избежать загрязнения пространства имен, просто del переменная после его использования.

Сначала переписать цикл for как выражение генератора, которое не выделяет никакой памяти.

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

Но это выражение на самом деле ничего не делает, не находя способ потреблять его. Таким образом, мы можем переписать это, чтобы получить что-то определенное, а не полагаться на None Результат installWow,

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

который создает список, но возвращает только константу 1. это удобно использовать с reduce

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

Который удобно возвращает количество элементов в wowList, которые были затронуты.

Простой DIY, единственная цель которого - перебрать выражение генератора:

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

Затем используйте:

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

Просто заставьте installWow вернуть None или сделайте так, чтобы последний оператор проходил так:


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

и используйте это:


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

x не будет установлен в глобальном пространстве имен, и возвращенный список будет [] одиночным

Если вы беспокоитесь о необходимости контролировать возвращаемое значение (что необходимо сделать, чтобы использовать фильтр) и предпочитаете более простое решение, чем приведенный выше пример сокращения, тогда подумайте об использовании прямого сокращения. Ваша функция должна будет принять дополнительный первый параметр, но вы можете проигнорировать его или использовать лямбду, чтобы отбросить его:

reduce(lambda _x: installWow(_x, 'installed by me'), wowList, None)

Позвольте мне предвосхитить это, сказав, что кажется, что оригинальный постер был больше обеспокоен беспорядком пространства имен, чем что-либо еще. В этом случае вы можете обернуть ваши рабочие переменные в отдельное пространство имен функций и вызывать их после объявления их, или вы можете просто удалить их из пространства имен после того, как вы использовали их со встроенной командой del. Или, если вам нужно очистить несколько переменных, определите функцию со всеми временными переменными, запустите ее, а затем удалите.

Читайте дальше, если основной проблемой является оптимизация:

Еще три способа, потенциально более быстрые, чем описанные здесь:

  1. Для Python >= 2.7, используйте collection.deque((installWow(x, 'Установлено мной') для x в wowList),0) # сохраняет 0 записей при выполнении итерации всего генератора, но да, все еще имеет побочный продукт окончательного объект вместе с внутренней проверкой длины каждого элемента
  2. Если вы беспокоитесь об этом виде, установите Cytoolz. Вы можете использовать счетчик, у которого все еще есть побочный продукт приращения счетчика, но это может быть меньшее количество циклов, чем проверка каждого элемента в deque, не уверен. Вы можете использовать его вместо any() следующим образом:
  3. Замените выражение генератора на itertools.imap (когда installWow никогда не возвращает True. В противном случае вы можете рассмотреть itertools.ifilter и itertools.ifilterfalse с None для предиката): any(itertools.imap(installWow,wowList,itertools.repeat('установлен мне')))

Но настоящая проблема здесь в том, что функция возвращает что-то, а вы не хотите, чтобы она что-либо возвращала. Поэтому для решения этой проблемы у вас есть 2 варианта. Одним из них является рефакторинг вашего кода, поэтому installWow принимает wowList и повторяет его внутри. Другой способ - поразительно, но вы можете загрузить функцию installWow() в скомпилированный код, например так:

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

Затем вы можете сделать то же самое для внешней функции и пройти через ast, чтобы найти цикл for. Затем в тело цикла for вставьте тело определения функции ast с функцией instalWow(), сопоставляя имена переменных. Затем вы можете просто вызвать exec для самого ast и предоставить словарь пространства имен с правильными заполненными переменными. Чтобы убедиться, что ваши модификации дерева верны, вы можете проверить, как будет выглядеть окончательный исходный код, запустив astunparse.

И если этого недостаточно, вы можете перейти на cython и написать файл.pyx, который сгенерирует и скомпилирует файл.c в библиотеку с привязками python. Тогда, по крайней мере, потерянные циклы не будут потрачены на преобразование в объекты Python и обратно и повторную проверку типов.

Кто-то должен ответить -

Более питонический способ - не беспокоиться о загрязнении пространства имен и использовании __all__ определить публичные переменные.

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

затем

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']
Другие вопросы по тегам