Топ-3 функции Python, о которых вы не знали (Наверное)

Привет, Хабр! Представляю вашему вниманию перевод статьи «Top 3 Python Functions You Don’t Know About (Probably)» автора Dario Radečić.

Будучи одним из самых популярных языков 21-го века, Python, безусловно, обладает множеством интересных функций, которые стоит изучить подробно. Три из них будут рассмотрены сегодня, каждая — теоретически, а потом и на практических примерах.

image

Основная причина, по которой я хочу рассказать эти про функции, заключается в том, что они помогают избежать написания циклов. Циклы могут быть затратными в некоторых случаях, и, кроме того, эти функции помогут увеличить скорость.

Вот функции, которые будут рассмотрены в статье:

1. map()
2. filter()
3. reduce()

Даже если вы уже слышали об этих функциях, нет ничего плохого в том, чтобы подкрепить свои знания немного бóльшим количеством теории и примеров.

Итак, без лишних слов… Давайте начнем!

map()


Функция «map()» принимает в качестве параметра другую функцию наряду с каким-либо массивом. Идея состоит в том, чтобы применить функцию (переданную в качестве аргумента) к каждому элементу в массиве.

Это пригодится по двум причинам:

  1. Вам не нужно писать цикл
  2. Это быстрее, чем цикл

Давайте посмотрим на это в действии. Я объявлю функцию «num_func()», которая принимает одно число в качестве параметра. Это число возводится в квадрат, делится на 2 и возвращается как таковое. Обратите внимание, что операции были выбраны произвольно, вы можете делать все что угодно внутри функции:

image

А теперь давайте объявим массив чисел, к которому мы хотим применить «num_func()». Обратите внимание, что «map()» сама вернет объект-отображение, поэтому вам необходимо преобразовать его в список:

image

Похоже, процесс успешно завершен. Здесь нет ничего революционного, но хорошо бы избегать циклы, когда это возможно.

filter()


Вот еще одна достойная функция, которая сэкономит ваше время — как при написании, так и при выполнении. Как следует из названия, идея состоит в том, чтобы хранить в массиве только те элементы, которые удовлетворяют определенному условию.

Как и в случае с «map()», мы можем заранее объявить функцию, а затем передать ее в «filter()» вместе с итерируемым объектом (например, списком).

Давайте посмотрим на это в действии. Я пошел дальше и объявил функцию «more_than_15 ()», которая, как следует из названия, вернет «true», если элемент, заданный в качестве параметра, больше 15:

image

Далее мы объявляем массив чисел и передаем их в качестве второго параметра в функцию «filter()»:

image

Как и ожидалось, только три значения удовлетворяют данному условию. Еще раз, ничего революционного здесь, но выглядит намного лучше, чем цикл.

reduce()


Теперь «reduce()». Она немного отличается от предыдущих двух. Для начала мы должны импортировать его из модуля functools. Основная идея заключается в том, что она будет применять данную функцию к массиву элементов и в результате будет возвращать одно значение.

Последняя часть имеет решающее значение — «reduce()» не возвращает массив элементов, он всегда возвращает одно значение. Давайте посмотрим на схему, чтобы конкретизировать эту концепцию:

image

Вот логика, написанная на случай, если диаграмма не ясна на 100%:

  1. 5 добавляется к 10, результаты в 15
  2. 15 добавляется к 12, результаты в 27
  3. 27 добавляется к 18, результат 45
  4. 45 добавляется к 25, результат 70

И 70 это значение, которое возвращается. Чтобы начать с реализации кода, давайте импортируем функцию уменьшения из модуля functools и объявим функцию, которая возвращает сумму двух чисел:

image

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

image

Пока не заходите в раздел комментариев — я прекрасно знаю, что есть другие способы суммировать элементы списка. Это самый простой пример, демонстрирующий работу функции.

Прежде, чем вы уйдете:


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

Если у вас есть другие примеры, не стесняйтесь поделиться ими в разделе комментариев.

Спасибо за прочтение.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 23

    +3
    Мне вместо map намного удобнее использовать list generator да и читается оно легче.
      +2
      Хочу заметить, что списковые выражения совмещают в себе и map, и filter, ещё и быстрее работают.

      А ещё хочу сказать автору, что это 3 базовые функции для работы с последовательностями в любом языке. Не могу представить, как их можно не знать.
        0

        Базовая функция — это fold (ну или reduce в питоне). map и filter выражаются через нее.


        Собственно, компиляторы ФП-языков делают оптимизации через приведение функций над списками к двум базовым (build и foldr).

      0
      «повышения скорости» не будет вообще. функции в питоне дороги, и тот же цикл, записанный имплицитно, будет работать в разы быстрее. увы.
        +1

        Ну во-первых, далеко не в разы, а всего лишь процентов на 5. Во-вторых — list comprehension работает еще быстрее. Но тоже далеко не в разы, а процентов на 10 быстрее цикла (И соответственно, процентов на 15 быстрее map).


        Пример на моей машине
        In [10]: l = list(range(1000000))
        
        In [11]: %timeit test_map(l)
        346 ms ± 3.94 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        
        In [12]: %timeit test_loop(l)
        328 ms ± 802 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
        
        In [13]: %timeit test_list_comprehension(l)
        292 ms ± 970 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
        
        In [14]: %timeit  test_generator(l)
        313 ms ± 2.44 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        
        In [15]: %timeit test_loop2(l)
        324 ms ± 411 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

        Сами тесты
        In [1]: def test_map(l):
           ...:     return list(map(lambda x: x ** 2, l))
           ...:
        
        In [2]: def test_loop(l):
           ...:     res = []
           ...:     for n in l:
           ...:         res.append(n ** 2)
           ...:     return res
           ...:
        
        In [3]: def test_list_comprehension(l):
           ...:     return [n**2 for n in l]
           ...:
        
        In [4]: def test_generator(l):
           ...:     return list((n**2 for n in l))
           ...:
        
        In [5]: def test_loop2(l):
            ...:     res = [0] * len(l)
            ...:     for i, n in enumerate(l):
            ...:         res[i] = n ** 2
            ...:     return res
            ...:
          –2
          буквально на днях тесты проводил как раз: github.com/zmej-serow/imperative_vs_functional_python/blob/master/testing.py

          • reduce: 5.9973929
          • any_gen: 1.0433306
          • any_lst: 5.5095213
          • loop: 0.5569408000000013
            +2

            У вас тесты делают разные вещи. Reduce и any_lst пробегают весь список, тогда как в остальных двух тестах есть ранняя остановка.


            Так что объективно с циклом можно сравнивать только any_gen. И да в данном случае он работает в ~2 раза медленнее.
            Но учитывайте, что оба варианта (и список и генератор) здесь делают в среднем всего лишь ок. 50 итераций на тест. Поэтому накладные расходы на инициализацию генераторов заметно сказываются на времени выполнения. Если проверять на более длинных списках, то никаких "в разы быстрее" уже не останется.

              0
              а, да. действительно, я эти тесты делал для иллюстрирования немного другой идеи.
              ну хорошо, давайте сделаем такой цикл, а генератор забудем как нерелевантный:

              def loop(error, list_of_errors):
                  a = False
                  for error_message in list_of_errors:
                      if error in error_message:
                          a = True
                  return a


              list items 10, list length 500:
              • loop 5.5052784
              • reduce 12.4489548
              • any_lst 9.775371399999997


              list items 5000, list length 10:
              • loop 40.1854446
              • reduce 53.282886000000005
              • any_lst 56.423127699999995


              всё равно ощутимо медленнее. оверхед на вызов функции даёт о себе знать.

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

                Я не уверен, что цепочки filter, map, reduce так уж красивы и выразительны в Python. Все же синтаксис Python, изначально императивного языка, активно сопротивляется подобному. Например lambda функции, которые часто используют в фильтрах и мапах, в Python сделаны слишком уж многословными. Или отсутствие в языке встроеного метода композиции. Иной раз приходится на ревью кода плакать кровавыми слезами от попыток в лоб сделать многоступенчатую обработку с кучей лямбд.

                  0
                  Во всём меру знать нужно, конечно. Например, за такой comprehension golf можно и селёдкой по щщам:

                  {
                      'pages': [
                          {
                              'customerapprovalStatus'
                                  if k == 'statusCA' else k:
                              'APPROVED'
                                  if k == 'statusCA' else page[k]
                              for k in page if k == 'pageID' or k == 'statusCA'
                          }
                          for page in elements['pages']
                      ]
                  }


                  А для построения IMAP-запроса с множественными FROM reduce подходит как нельзя лучше: gist.github.com/zmej-serow/4d6292591c690563e7dd205964f8b8ee
        0
        Вряд ли кто не слышал про эти функции. Да и это не самые «питонячие» вещи.
        map и filter заменяются генератором списков с условием. А reduce Гвидо хотел выпилить еще 15 лет назад, т.к. если в add_nums что-то сложнее a+b, то сходу трудно понять. Проще вынести в цикл.
          +11

          Автогенератор таких статей:


          import random
          
          def write_part():
              item = random.choice(dir(__builtins__))
              print(f"# {item}")
              print(getattr(__builtins__,item).__doc__)
          
          if __name__ == '__main__':
              print("Три объекта питона, о которых вы не знали (наверное)")
              write_part()
              write_part()
              write_part()

          Алгоритимический метакопирайтинг.


          И да, я не знал про __package__, memoryview и __import__. Забавно.

            0

            Вы забыли воспользоваться Google Translate API. Не самому же переводить.

              +2

              Ну, как проект интересно, но уже не в комментах на хабре. Я и этот-то задолбался отлаживать.

            +2

            Гугл-перевод — это конечно хорошо, но нужно его вычитывать перед публикацией. И желательно чтобы это делал человек, который хоть немного понимает, о чем речь в тексте и способен на чуть большее, чем согласование частей речи в предложении.


            мы можем заранее объявить функцию, а затем передать ее в «filter()» вместе со списком итераций.

            Что такое "список итераций"?


            Тут конечно и в оригинале неверная формулировка


            and then pass it to filter() alongside the list of iterables.

            второй параметр у filter — это не "the list of iterables", а один iterable (частным случаем которого является list).
            Но все же в оригинале фраза менее бредова, чем в переводе (прошу заметить, там the list of iterables, а не the list of iterations).


            Обратите внимание, что «map()» сама вернет объект карты

            Какой карты? Игральной? Географической?


            Название функции map происходит от глагола to map, т.е. отображать. И адекватный перевод фразы map object в данном контексте — объект-отображение или что-то в этом духе. Или даже map-объект. Все лучше, чем какие-то карты, непонятно откуда взявшиеся.

              0
              Спасибо за замечание. Но как все-таки перевести про «the list of iterables»?

              И да, я почти не использовал переводчик. Такие ошибки появляются из-за того, что я еще новенький в программировании и просто хотел попробовать себя в этом. Я учту все, что написали здесь в комментариях.
                0

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

              0
              Что касается лично меня, то я практически всегда использую циклы. Все остальное только в случае крайней необходимости. Просто потому, что циклы на порядок легче читаются и понимаются.
              Если же делаю что-то другое, тот же map() или list comprehension, то пишу коммент, где написано, что тут делается.
                0

                Зря. Некоторые вещи очень идиоматично получаются.


                for token in map(str.strip, filter(line.split(','))):
                     ....

                Оно заменяет собой вот такое:


                for raw_token in line.split(','):
                    if not raw_token:
                        continue
                    token = raw_token.strip()

                Лишний if с continue, лишние переменные без принципиального смысла. Вот если в map зафигачить лямбду с нетривиальным выражением — уже не очень.


                Но это вопрос общей культуры.

                  0
                  Опять же я очень часто использую переменные без принципиального смысла. Чисто для того чтобы можно было:
                  — придя через неделю прочитать имя переменной и получить из этого имени подсказку, что происходит.
                  — Если что-то идет не так гораздо легче понять, где проблема в дебаггере или вставив print()
                  0
                  долго переходил с map-filter-reduce на list/dict comprehensions, циклы for — это даже читать не очень просто.
                  Сейчас циклы использую только если прям массово нужны side-effects в процессе обработки, или есть коллеги, которым тяжело читать coomprehensions.

                  И про эффективность не забываем, конечно
                  0
                  Использую list comprehension и как-то не было необходимости в вышеупомянутом
                    0
                    2020 год, если вдруг кто не заметил.
                    В качестве внекласного чтения рекомендую почитать pep на всякие comprehensions, и найти дату, когда reduce вынесли в модуль functools

                    p.s. аааа!!! pop-202, list comprehension создан в 2000 году…

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое