python

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

  • среда, 1 июля 2020 г. в 00:30:24
https://habr.com/ru/company/skillfactory/blog/508992/
  • Блог компании SkillFactory
  • Python
  • Программирование
  • Учебный процесс в IT


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. Классы данных


Начиная с версии 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:



Читать еще