8 трюков в Python, используемых опытными программистами

Автор оригинала: Erik van Baaren
  • Перевод
image

Вот восемь изящных приемов Python, которые, я уверен, вы еще не видели. Примените эти приемы в своем коде на Python, чтобы сделать его более лаконичным и производительным!

1. Сортировка объектов по нескольким ключам


Предположим, мы хотим отсортировать следующий список словарей:

people = [
{ 'name': 'John', "age": 64 },
{ 'name': 'Janet', "age": 34 },
{ 'name': 'Ed', "age": 24 },
{ 'name': 'Sara', "age": 64 },
{ 'name': 'John', "age": 32 },
{ 'name': 'Jane', "age": 34 },
{ 'name': 'John', "age": 99 },
]

Но мы не просто хотим сортировать их по имени или возрасту, мы хотим отсортировать их по обоим полям. В SQL это будет такой запрос:

SELECT * FROM people ORDER by name, age

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

Чтобы добиться сортировки по имени и возрасту, мы можем сделать это:

import operator
people.sort(key=operator.itemgetter('age'))
people.sort(key=operator.itemgetter('name'))

Обратите внимание, как я изменил порядок. Сначала сортируем по возрасту, а потом по имени. С помощью operator.itemgetter() мы получаем поля возраста и имени из каждого словаря в списке.

Это дает нам результат, который мы хотели:

[
 {'name': 'Ed',   'age': 24},
 {'name': 'Jane', 'age': 34},
 {'name': 'Janet','age': 34},
 {'name': 'John', 'age': 32},
 {'name': 'John', 'age': 64},
 {'name': 'John', 'age': 99},
 {'name': 'Sara', 'age': 64}
]

Имена сортируются в первую очередь, возраст сортируется, если имя совпадает. Таким образом, все Джоны сгруппированы по возрасту.

Источник вдохновения — вопрос со StackOverflow.

2. Списковые включения (Генератор списка)


Списковые включения могут заменить уродливые циклы, используемые для заполнения списка. Основной синтаксис для списковых включений:

[ expression for item in list if conditional ]

Очень простой пример для заполнения списка последовательностью чисел:

mylist = [i for i in range(10)]
print(mylist)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

И поскольку вы можете использовать выражение, вы также можете сделать некоторую математику:

squares = [x**2 for x in range(10)]
print(squares)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Или даже вызвать внешнюю функцию:

def some_function(a):
    return (a + 5) / 2
    
my_formula = [some_function(i) for i in range(10)]
print(my_formula)
# [2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0]

И, наконец, вы можете использовать «если» для фильтрации списка. В этом случае мы сохраняем только те значения, которые делятся на 2:

filtered = [i for i in range(20) if i%2==0]
print(filtered)
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

3. Проверьте использование памяти ваших объектов


С помощью sys.getsizeof() вы можете проверить использование памяти объектом:

import sys

mylist = range(0, 10000)
print(sys.getsizeof(mylist))
# 48

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

import sys

myreallist = [x for x in range(0, 10000)]
print(sys.getsizeof(myreallist))
# 87632

Итак, поиграв с sys.getsizeof(), вы можете больше узнать о Python и использовании вашей памяти.

4. Классы данных (Data classes)


Начиная с версии 3.7, Python предлагает классы данных. Есть несколько преимуществ перед обычными классами или другими альтернативами, такими как возвращение нескольких значений или словарей:

  • класс данных требует минимального количества кода
  • вы можете сравнить классы данных, потому что существует __eq__
  • вы можете легко вывести класс данных для отладки, потому что существует __repr__
  • классы данных требуют тайп хинты, что уменьшает шанс ошибок

Вот пример класса данных в работе:

from dataclasses import dataclass

@dataclass
class Card:
    rank: str
    suit: str
    
card = Card("Q", "hearts")

print(card == card)
# True

print(card.rank)
# 'Q'

print(card)
Card(rank='Q', suit='hearts')

Подробное руководство можно найти здесь.

5. Пакет attrs


Вместо классов данных вы можете использовать attrs. Есть две причины, чтобы выбрать attrs:

  • Вы используете версию Python старше 3.7
  • Вы хотите больше возможностей

Пакет attrs поддерживает все основные версии Python, включая CPython 2.7 и PyPy. Некоторые из дополнительных атрибутов, предлагаемых attrs по сравнению с обычными классами данных, являются валидаторами и конвертерами. Давайте посмотрим на пример кода:

@attrs
class Person(object):
    name = attrib(default='John')
    surname = attrib(default='Doe')
    age = attrib(init=False)
    
p = Person()
print(p)
p = Person('Bill', 'Gates')
p.age = 60
print(p)

# Output: 
#   Person(name='John', surname='Doe', age=NOTHING)
#   Person(name='Bill', surname='Gates', age=60)

Авторы attrs фактически работали в PEP, которые ввели классы данных. Классы данных намеренно хранятся проще (легче для понимания), в то время как attrs предлагает полный набор функций, которые вам могут понадобиться!

Дополнительные примеры можно найти на странице примеров attrs.

6. Объединение словарей (Python 3.5+)


Начиная с Python 3.5, легче объединять словари:

dict1 = { 'a': 1, 'b': 2 }
dict2 = { 'b': 3, 'c': 4 }
merged = { **dict1, **dict2 }
print (merged)
# {'a': 1, 'b': 3, 'c': 4}

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

В Python 3.9 объединение словарей становится еще чище. Вышеупомянутое слияние в Python 3.9 может быть переписано как:

merged = dict1 | dict2

7. Поиск наиболее часто встречающегося значение


Чтобы найти наиболее часто встречающееся значение в списке или строке:

test = [1, 2, 3, 4, 2, 2, 3, 1, 4, 4, 4]
print(max(set(test), key = test.count))
# 4

Вы понимаете, почему это работает? Попробуйте разобраться в этом сами, прежде чем читать дальше.

Вы даже попытались, не так ли? Я все равно скажу вам:

  • max() вернет самое большое значение в списке. Аргумент key принимает функцию единственного аргумента для настройки порядка сортировки, в данном случае это test.count. Функция применяется к каждому элементу итерируемого.
  • test.count — встроенная функция списка. Она принимает аргумент и будет подсчитывать количество вхождений для этого аргумента. Таким образом, test.count(1) вернет 2, а test.count(4) вернет 4.
  • set(test) возвращает все уникальные значения из test, поэтому {1, 2, 3, 4}

Итак, в этой единственной строке кода мы принимаем все уникальные значения теста, который равен {1, 2, 3, 4}. Далее max применит к ним функцию list.count и вернет максимальное значение.

И нет — я не изобрел этот однострочник.

Обновление: ряд комментаторов справедливо отметили, что есть гораздо более эффективный способ сделать это:

from collections import Counter
Counter(test).most_common(1)
# [4: 4]

8. Возврат нескольких значений


Функции в Python могут возвращать более одной переменной без словаря, списка или класса. Это работает так:

def get_user(id):
    # fetch user from database
    # ....
    return name, birthdate

name, birthdate = get_user(4)

Это нормально для ограниченного числа возвращаемых значений. Но все, что превышает 3 значения, должно быть помещено в (data) класс.

image

Узнайте подробности, как получить востребованную профессию с нуля или Level Up по навыкам и зарплате, пройдя платные онлайн-курсы SkillFactory:



Читать еще


SkillFactory
Онлайн-школа по программированию

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

    +8
    Функции в Python могут возвращать более одной переменной без словаря, списка или класса
    кортежи не считаются, видимо
    >>> a = 1, 2
    >>> a
    (1, 2)
    >>> type(a)
    <class 'tuple'>
      +1
      > Это нормально для ограниченного числа возвращаемых значений. Но все, что превышает 3 значения, должно быть помещено в (data) класс.

      Без (data) класса будет ошибка?
        0
        Без (data) класса «потомки» проклянут
          0

          Каша, где надо будет помнить кто на ком стоял в порядке вывода.
          Хотя, некоторые именно так и живут(аддоны WoW на Lua, я на Вас смотрю!), с этими


          local _, _, _, _, _, itemType, itemSubType = GetItemInfo(itemID)
            0
            *_, itemType, itemSubtype = GetItemInfo(itemID)
              0

              Это если нужное в хвосте или в начале. А ежели в середине — нужно снова уточнять порядок выходных аргументов. И работает только если этот порядок не изменяется.


              Вот тут датакласс даже в простейшей форме отлично заменил бы и был удобней в применении.

          +5
          функции сортировки обеспечивают стабильный порядок сортировки
          В русском языке нет термина «стабильный порядок сортировки», зато есть термин «устойчивая сортировка». Вы объявляете себя «школой по программированию», но при этом не знаете русской терминологии?
            –5

            Кажется, в данном случае корректно переводить термины "напрямую". Все равно подавляющее большинство людей используют английскую терминологию.
            У меня, например, было несколько случаев, когда я не понимал русскую терминологию. Особенно это проявляется в тех ситуациях, когда термины еще и сокращают. Простым примером может служить GCD, который у нас принято называть НОД.
            Да и с точки зрения написания/чтения кода неудобно туда-сюда переводить термины.


            Учитывая приведенные мной аргументы, можно сказать, что школа как раз-таки делает жизнь своих учеников проще, при этом не срезая углы

            +12
            import operator
            people.sort(key=operator.itemgetter('age'))
            people.sort(key=operator.itemgetter('name'))


            Вы выполняете сортировку дважды. Плюс к тому же вам нужно перечислить ключи в порядке противоположном естественному, что может запутывать и стать причиной ошибки. Правильное решение:

            people.sort(key=lambda item: (item['name'], item['age']))


            Ну и не вполне корректно выдавать списковые включения, классы данных и возврат нескольких значений за какие-то трюки опытных программистов. Это вполне обычные элементы языка.
            Хотя, конечно, статью «8 рандомно надёрганных элементов Питона» прочитало бы меньше людей.
              +2
              Вы выполняете сортировку дважды
              это не они — материал переводной, а автор даже не распознал кортеж в последнем примере
                +2

                people.sort(key=operator.itemgetter(' age ', ' name ')


                Вот так работает тоже. Странный пример у автора конечно, видно тупо перевели, а примеры ее проверили.


                Все равно спасибо всем за практику и некоторые примеры возьму в применение.

                  –1
                  Не работает так сортировка по обоим ключам. Выдает что первое стоит только:

                  people = [
                      {'name': 'Mike', 'age': 30},
                      {'name': 'Tom', 'age': 21},
                      {'name': 'Sarah', 'age': 34},
                      {'name': 'Jack', 'age': 45},
                      {'name': 'Piter', 'age': 13},
                      {'name': 'Vasja', 'age': 23},
                      {'name': 'Lora', 'age': 27}
                  ]
                  
                  
                  people.sort(key=lambda item: (item['name'], item['age']))
                  for i in people:
                      print(i)


                  Ответ:

                  {'name': 'Jack', 'age': 45}
                  {'name': 'Lora', 'age': 27}
                  {'name': 'Mike', 'age': 30}
                  {'name': 'Piter', 'age': 13}
                  {'name': 'Sarah', 'age': 34}
                  {'name': 'Tom', 'age': 21}
                  {'name': 'Vasja', 'age': 23}

                  сортирует только по имени, ну или по первому вхождению ключа

                  Вообще существует способ отсортировать оба параметра?
                    +1
                    Не вполне понял ваш комментарий.
                    У вас в примере ни одно имя не повторяется. Для таких данных сортировка по двум ключам ни чем не будет отличаться от сортировки только по имени. Увидеть как работает сортировка по двум параметрам можно только на данных, в которых есть повторения по первому параметру.
                  +3
                  Начиная с версии 3.7, Python предлагает классы данных

                  никогда не слышал чтоб так называли
                  датаклассы — так говорят
                    0

                    Так автор в оригинале написал, буквально через пробел.

                    +1

                    Упомянут генератор списка но не упомянут простой генератор через круглые скобки, который будет в большинстве случаев эффективнее.
                    А также создание множества (set) и словаря (dict)

                      +5

                      Уж никак не мог подумать что list comprehension это «трюк» для «опытных программистов»

                        0

                        dataclass чем то похожи на слоты))

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

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