Як користуватися фільтром, картою та зменшенням у Python 3


321

filter, mapі reduceвідмінно працюють в Python 2. Ось приклад:

>>> def f(x):
        return x % 2 != 0 and x % 3 != 0
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]

>>> def cube(x):
        return x*x*x
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

>>> def add(x,y):
        return x+y
>>> reduce(add, range(1, 11))
55

Але в Python 3 я отримую такі результати:

>>> filter(f, range(2, 25))
<filter object at 0x0000000002C14908>

>>> map(cube, range(1, 11))
<map object at 0x0000000002C82B70>

>>> reduce(add, range(1, 11))
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    reduce(add, range(1, 11))
NameError: name 'reduce' is not defined

Буду вдячний, якщо хтось міг би мені пояснити, чому це так.

Скріншот коду для подальшої ясності:

IDLE-сеанси Python 2 та 3 набік


1
Коротше кажучи, список - не єдиний тип даних. Якщо ви хочете список, скажіть, що ви хочете список. Але в більшості випадків все одно хочеться чогось іншого.
Векі

Відповіді:


346

Про зміни в розділі Що нового в Python 3.0 ви можете прочитати . Ви повинні прочитати його ретельно, переходячи від 2.x до 3.x, оскільки багато було змінено.

Вся відповідь тут - цитати з документації.

Перегляди та ітератори замість списків

Деякі відомі API більше не повертають списки:

  • [...]
  • map()і filter()повернути ітератори. Якщо вам справді потрібен список, швидке виправлення є, наприклад list(map(...)), але краще виправлення - це використання розуміння списку (особливо, коли в оригінальному коді використовується лямбда) або переписання коду, щоб його список взагалі не потребував. Особливо складно map()викликати побічні ефекти функції; правильне перетворення полягає у використанні регулярного forциклу (оскільки створення списку було б просто марнотратним).
  • [...]

Вбудовані

  • [...]
  • Вилучено reduce(). Використовуйте, functools.reduce()якщо вам це справді потрібно; проте 99 відсотків часу явного forциклу є більш читабельним.
  • [...]

21
Додавання list(map(...) скрізь ... як у світі допомагає читабельність ... pythonне здається впоратися з прогресивним / потоковим використанням функціональних комбінаторів. Інші мови Я можу зв'язати десяток операцій проти колекції поспіль, і це читається. Тут? що ти хочеш - десяток способу вкладений in??
javadba

11
Якщо ви працюєте в імперативному контексті, то варіант для циклу, ймовірно, є більш читабельним варіантом. Але є вагомі причини віддати перевагу функціональному контексту - і перерва з того, щоб повернутися до процесуального, може бути досить некрасивим.
MatrixManAtYrService

2
@javadba Ви впевнені, що в "потоковому додатку" вам потрібно listвзагалі додати дзвінок? Я подумав, що сенс "потокової передачі" полягає в тому, що "жодний список не створюється взагалі; обробляйте кожен елемент вводу повністю, перш ніж перейти до наступного".
Непрохідна ніч

@MatrixManAtYrService Якщо ви впевнені, що поведінка python 2 - це те, що вам потрібно, ви завжди можете просто переглянути їх map.
Непрохідна ніч

6
Я досі не можу зрозуміти, як аргумент читабельності призводить до такої зміни. Якби це було з міркувань виступу, я можу зрозуміти ...
Мінато

86

Функціональність mapта filterнавмисно змінена для повернення ітераторів, а зменшення було вилучено із вбудованого та розміщеного functools.reduce.

Отже, для filterі map, ви можете обернути їх, list()щоб побачити результати, як раніше.

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> list(filter(f, range(2, 25)))
[5, 7, 11, 13, 17, 19, 23]
>>> def cube(x): return x*x*x
...
>>> list(map(cube, range(1, 11)))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>> import functools
>>> def add(x,y): return x+y
...
>>> functools.reduce(add, range(1, 11))
55
>>>

Рекомендація полягає в тому, що ви замінюєте своє використання карти та фільтру на генераторні вирази чи списки. Приклад:

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> [i for i in range(2, 25) if f(i)]
[5, 7, 11, 13, 17, 19, 23]
>>> def cube(x): return x*x*x
...
>>> [cube(i) for i in range(1, 11)]
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>>

Кажуть, що для циклів 99 відсотків часу легше читати, ніж зменшувати, але я б просто дотримувався цього functools.reduce.

Редагувати : 99-відсотковий показник витягується безпосередньо зі сторінки " Що нового в Python 3.0 ", автором якої є Гідо ван Россум.


5
Не потрібно створювати додаткові функції в розумінні списку. Просто використовуйте[i*i*i for i in range(1,11)]
Сяо

2
Ви абсолютно правильні. Я зберігав функцію в прикладах розуміння списку, щоб вона виглядала схожою на приклади фільтр / карта.
Джошуа Д. Бойд

5
i ** 3 також еквівалент i * i * i
Breezer

5
@Breezer насправді i**3зателефонує i.__pow__(3)та i*i*i i.__mul__(i).__mul__(i)(або щось подібне). Для ints це не має значення, але з nummy номерами / спеціальними класами це може навіть давати різні результати.
синонім

1
Я помічав, що кожного разу, коли ми чуємо, що "Гуйдо прийняв рішення X", біль є ймовірним результатом. Це чудовий приклад: list(list(list(.. )))робити те, що вже було багатослівним у python.
javadba

12

Як доповнення до інших відповідей, це звучить як прекрасний випадок використання для контекстного менеджера, який переймене імена цих функцій на ті, які повертають список та вводять reduceу глобальному просторі імен.

Швидка реалізація може виглядати приблизно так:

from contextlib import contextmanager    

@contextmanager
def noiters(*funcs):
    if not funcs: 
        funcs = [map, filter, zip] # etc
    from functools import reduce
    globals()[reduce.__name__] = reduce
    for func in funcs:
        globals()[func.__name__] = lambda *ar, func = func, **kwar: list(func(*ar, **kwar))
    try:
        yield
    finally:
        del globals()[reduce.__name__]
        for func in funcs: globals()[func.__name__] = func

З використанням, яке виглядає так:

with noiters(map):
    from operator import add
    print(reduce(add, range(1, 20)))
    print(map(int, ['1', '2']))

Які відбитки:

190
[1, 2]

Всього мої 2 копійки :-)


1
pythonяк мова безлад - але вона має V хорошою до відмінною бібліотеки: numpy, pandas, statsmodelsі друзів .. Я був Buliding бібліотеки зручності , як ви показати тут , щоб зменшити біль рідної мови - але втратили енергію і намагатися не бродячий далеко від data.frame/ datatable, або xarray. Але кудо за спробу ..
javadba

7

Оскільки reduceметод був видалений із вбудованої функції з Python3, не забудьте імпортувати functoolsсвій код. Перегляньте фрагмент коду нижче.

import functools
my_list = [10,15,20,25,35]
sum_numbers = functools.reduce(lambda x ,y : x+y , my_list)
print(sum_numbers)

2

Ось приклади функцій фільтра, відображення та зменшення.

числа = [10,11,12,22,34,43,54,34,67,87,88,98,99,87,44,66]

// Фільтр

oddNumbers = список (фільтр (лямбда х: х% 2! = 0, числа))

друк (непарні номери)

// Карта

multiplyOf2 = список (карта (лямбда x: x * 2, числа))

друк (multiplyOf2)

// Зменшити

Функція зменшення, оскільки вона зазвичай не використовується, була видалена із вбудованих функцій на Python 3. Вона все ще доступна у модулі functools, тому ви можете робити:

зменшити імпорт фунікулерів

sumOfNumbers = зменшити (лямбда x, y: x + y, числа)

друк (sumOfNumbers)


0

Однією з переваг картографування, фільтрування та зменшення є те, наскільки вони стають розбірливими, коли ви "з'єднуєте" їх разом, щоб зробити щось складне. Однак вбудований синтаксис не є розбірливим і є всім "назад". Отже, я пропоную використовувати PyFunctionalпакет ( https://pypi.org/project/PyFunctional/ ). Ось порівняння двох:

flight_destinations_dict = {'NY': {'London', 'Rome'}, 'Berlin': {'NY'}}

PyFunctional версія

Дуже розбірливий синтаксис. Ви можете сказати:

"У мене є послідовність пунктів призначення рейсів. З яких я хочу отримати ключ dict, якщо місто значить dict. Нарешті, відфільтруйте порожні списки, які я створив у процесі."

from functional import seq  # PyFunctional package to allow easier syntax

def find_return_flights_PYFUNCTIONAL_SYNTAX(city, flight_destinations_dict):
    return seq(flight_destinations_dict.items()) \
        .map(lambda x: x[0] if city in x[1] else []) \
        .filter(lambda x: x != []) \

Версія Python за замовчуванням

Це все назад. Вам потрібно сказати:

"Добре, значить, є список. Я хочу відфільтрувати порожні списки з нього. Чому? Тому що я вперше отримав ключ dict, якщо місто було в значеннях dict. "

def find_return_flights_DEFAULT_SYNTAX(city, flight_destinations_dict):
    return list(
        filter(lambda x: x != [],
               map(lambda x: x[0] if city in x[1] else [], flight_destinations_dict.items())
               )
    )
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.