Приемы Python, которым редко учат начинающих

Python сейчас — самый используемый язык программирования в мире. А причина этого в том, что разработчикам нравится создавать программы на этом языке.

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

А еще, как и в любом языке, в Python есть целый ряд тонкостей и приемов, о которых редко рассказывают. Давайте рассмотрим некоторые из них.

Выбор случайного элемента из последовательности

Пакет random из стандартной библиотеки Python имеет много полезных функций. Но одна выделяется на общем фоне — random.choice(seq).

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

import random as r
my_list = [1, 2, 3, "go"]
print(r.choice(my_list))

# Случайный элемент

Применение на практике

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

# Импортируем только нужную нам функцию
from random import choice

def book_picker(books):
    book_choice = choice(books)
    books.remove(book_choice)    
    return f"You picked {book_choice}"

books = ["Harry Potter", "Don Quixote", "Frankenstein", "Dracula"]

print(book_picker(books)) # Случайный выбор

print(books) # Оставшиеся книги

Ограничения и исключения

Если вы попытаетесь использовать random.choice(seq) для неиндексируемой последовательности, например, для словаря, множества или числовых типов, Python выдаст ошибку.

# Со словарем
import random as r
scores = {"Jhon": 4, "Ben": 3, "Diana": 5}

print(r.choice(my_scores)) # Key error

А если последовательность будет пустой, вы получите IndexError.

# С пустой последовательностью
import random as r
empty_list = []

print(r.choice(empty_list)) # Index error

Распаковка элементов при помощи *

Бывает, нужно вывести элементы итерируемого объекта так, чтобы их разделяли пробелы. Обычно применяется следующее решение:

my_list = [1, 2, 3, 5, 7]

for i in my_list:
    print(i, end=" ") # 1 2 3 5 7 

И хотя этот код решает задачу, он не слишком питоничный. Есть более простое и элегантное решение — с использованием оператора распаковки *.

my_list = [1, 2, 3, 5, 7]

print(*my_list) # 1 2 3 5 7 

Как видите, оператор распаковки всегда ставится слева от итерируемого объекта. Он как бы говорит Python: «Помести каждый элемент этого итерируемого объекта в желаемый список или кортеж».

Не забывайте, что итерируемый объект — это любая последовательность, которую мы можем проитерировать в цикле for. Если вы хотите проверить, является ли какой-нибудь тип данных итерируемым, используйте функцию iter().

print(iter("This is a string")) # Str Iterable object

print(iter(["this", "is", "a", "list"])) # List iterable object

print(iter(1))
# Выбрасывается ошибка
# Числа нельзя итерировать

Использование распаковки с переменными

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

string = "Let's learn Python"

# Мы хотим назначить результат распаковки как значение var1
var1 = [*string]

print(var1)
# ['L', 'e', 't', "'", 's', ' ', 'l', 'e', 'a', 'r', 'n', ' ', 'P', 'y', 't', 'h', 'o', 'n']

Часть [*итерируемый_объект] может показаться непонятной, так что давайте разберемся.

Когда мы распаковываем итерируемый объект, Python нужна какая-нибудь структура данных для сохранения элементов этого объекта. Поэтому мы создаем список ([]), захватывая в него и оператор *.

Если мы захотим узнать тип результирующей переменной, мы получим:

another_str = "The * operator"

# Помещаем распаковку в список
var2 = [*another_str]

print(type(var2)) # Список

# Используем кортеж
# Кортежи заканчиваются запятой
var3 = (*another_str,)

print(type(var3)) # Кортеж

И естественно, если вы попытаетесь назначить в качестве значения переменной распаковку объекта без создания списка или кортежа, вы получите SyntaxError:

bad_variable = *"Bad String"
# Syntax error
[python_ad_block]

Еще один пример использования распаковки

Вы можете распаковать итерируемый объект в несколько переменных, среди которых указать одну со звездочкой. Всем переменным значения будут назначены по указанным позициям, а в переменную со звездочкой Python сохранит все остальное.

some_iterable = ["I", "have", "no", "idea", "how", "many", "values", "are", "here", "so", "this", "will", "be", "fun!"]
first, second, *_, last = *some_iterable
print(first, second, last)  # I have fun!

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

print(_)  # ['no', 'idea', 'how', 'many', 'values', 'are', 'here', 'so', 'this', 'will', 'be']

Вообще, распаковка — очень полезная вещь, имеющая множество вариантов применения.

Использование set для оптимизации операторов

Согласно документации Python, класс set(iterable) создает объект множества из итерируемого объекта.

Как многие из вас знают, множество — это неупорядоченная структура данных (а значит, и неиндексируемая). Одна из отличительных черт множества — в нем не может быть дубликатов элементов.

Практический пример

Функция, удаляющая дубликаты и возвращающая отсортированный список:

def eliminate_duplicates(lst):
    """
    Возвращает отсортированный список без дубликатов
    """ 
    new_list = list(set(lst)) 

    new_list.sort()    

    return new_list

list1 = [25, 12, 11, 4, 12, 12, 25]

print(eliminate_duplicates(list1))

Просмотр атрибутов и методов класса без выхода из редактора

Функция dir() возвращает атрибуты и методы класса. Мы можем использовать ее, чтобы составить список всех определений внутри класса.

-> $ python 
string = "A string"

print(dir(string))

# ['__add__', .....,'upper', 'zfill']

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

Практический пример

При использовании сторонних пакетов мы можем получить всю нужную информацию из класса, не выходя из терминала.

-> $ python


from django.views import View

print(dir(View))

# ['__class__', '__delattr__', .... 'setup']

Операции срезов

Срез — это не что иное, как способ обратиться к определенной части последовательности. В Python срезы позволяют нам проделывать различные трюки.

Разворот последовательности

# Разворот списков
lst = ["Fun", "is", "Programming"]

lst = lst[::-1]

print(lst) # ['Programming', 'is', 'Fun']

# Разворот строк

string = "Dog running on the park"

string = string[::-1]

print(string) # krap eht no gninnur goD

Практический пример

Функция, возвращающая последовательность до заданного индекса:

def cutoff(seq, index):
    if not len(seq) > index:
        return "Sorry the index is bigger than the sequence"

    return seq[:index]

long_string = "This is a long description of a blog post about Python and technology"

print(cutoff(long_string, 15))
# This is a long 

print(cutoff(long_string, 70))
# Sorry the index is bigger than the sequence

Введите 10 букв — и вызовите дебаггер

Функция breakpoint доступна в Python, начиная с версии 3.6. Она вызывает сессию pdb.set_trace().

Пример

n_odds = 0

for i in range(1, 14, 2):
    # Проверка значения i в каждой итерации
    breakpoint()
    # Плохое условие
    if i % 2 == 0:
        n_odds += 1

print(n_odds)
Скриншот терминала, показано использование функции breakpoint

Может показаться, что это так себе удобство, но это быстрый способ вызвать отладчик.

Итоги

Прочитав эту статью, вы научились:

  • выбирать рандомный элемент из последовательности
  • распаковывать элементы при помощи оператора *
  • эффективно удалять дубликаты при помощи множеств
  • искать методы и переменные, не покидая редактор кода
  • использовать срезы разными способами
  • вызывать отладчик при помощи функции breakpoint.