Как писать эффективный код на Python Учебное пособие для начинающих

Пишем эффективный код на Python учебное пособие для новичков

  

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

Если вы приходите из другого языка программирования, такого как C++ или JavaScript, то этот учебник предназначен для вас, чтобы выучить несколько советов по написанию эффективного кода на Python. Но если вы начинающий – изучаете Python как свой первый (программный) язык, то этот учебник поможет вам писать код на Pythonic с самого начала.

Мы сосредоточимся на следующем:

 

  • Pythonic циклы
  • Компрехеншн списков и словарей
  • Контекстные менеджеры
  • Генераторы
  • Классы коллекций

Итак, приступим!

 

1. Write Pythonic Loops

 

Понимание циклических структур важно независимо от языка программирования, на котором вы программируете. Если вы приходите из языков таких, как C++ или JavaScript, полезно научиться писать Pythonic циклы.

 

Создайте последовательность чисел с помощью функции range

 

Функция range() генерирует последовательность чисел, часто используемую как итератор в циклах.

Функция range() возвращает объект диапазона, который по умолчанию начинается с 0 и доходит до (но не включая) указанного числа.

Вот пример:

for i in range(5):    print(i)

 

Output >>>01234

 

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

 

Доступ как к индексу, так и к элементу с помощью функции enumerate

 

Функция enumerate() полезна, когда вам нужно как индекс, так и значение каждого элемента в итерируемом объекте.

В этом примере мы используем индекс для доступа к списку fruits:

fruits = ["яблоко", "банан", "вишня"]for i in range(len(fruits)):    print(f"Индекс {i}: {fruits[i]}")

 

Output >>>Индекс 0: яблокоИндекс 1: бананИндекс 2: вишня

 

Но с помощью функции enumerate() вы можете получить доступ и к индексу, и к элементу следующим образом:

fruits = ["яблоко", "банан", "вишня"]for i, fruit in enumerate(fruits):    print(f"Индекс {i}: {fruit}")

 

Output >>>Индекс 0: яблокоИндекс 1: бананИндекс 2: вишня

 

Итерация одновременно по нескольким итерируемым объектам с помощью функции zip

 

Функция zip() используется для итерации по нескольким итерируемым объектам одновременно. Она соединяет соответствующие элементы из разных итерируемых объектов вместе.

Рассмотрим следующий пример, где вам нужно пройти по спискам names и scores одновременно:

names = ["Алиса", "Боб", "Чарли"]scores = [95, 89, 78]for i in range(len(names)):    print(f"{names[i]} набрал {scores[i]} баллов.")

 

Это выводит:

Output >>>Алиса набрала 95 баллов.Боб набрал 89 баллов.Чарли набрал 78 баллов.

 

Вот более читаемый цикл с функцией zip():

names = ["Алиса", "Боб", "Чарли"]scores = [95, 89, 78]for name, score in zip(names, scores):    print(f"{name} набрал(а) {score} баллов.")

 

Output >>>Алиса набрала 95 баллов.Боб набрал 89 баллов.Чарли набрал 78 баллов.

 

Использование zip() в Python более элегантно и позволяет избежать необходимости ручного индексирования, что делает код чище и более читаемым.

 

2. Используйте генераторы списков и словарей

 

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

Начнем с версии цикла, а затем перейдем к выражениям генераторов для списков и словарей.

 

Генератор списка в Python

 

Предположим, у вас есть список numbers. И вы хотите создать список squared_numbers. Вы можете использовать цикл for, как показано ниже:

numbers = [1, 2, 3, 4, 5]squared_numbers = []for num in numbers:    squared_numbers.append(num ** 2)print(squared_numbers)

 

Output >>> [1, 4, 9, 16, 25]

 

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

  

Вот краткая альтернатива с использованием выражения генератора списка:

numbers = [1, 2, 3, 4, 5]squared_numbers = [num ** 2 for num in numbers]print(squared_numbers)

 

Output >>> [1, 4, 9, 16, 25]

 

Здесь генератор списка создает новый список, содержащий квадраты каждого числа из списка numbers.

 

Генератор списка с условной фильтрацией

 

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

numbers = [1, 2, 3, 4, 5]odd_numbers = [num for num in numbers if num % 2 != 0]print(odd_numbers)

 

Output >>> [1, 3, 5]

 

В этом примере генератор списка создает новый список, содержащий только нечетные числа из списка numbers.

 

Генератор словаря в Python

 

Синтаксис генератора словаря похож на генератор списка и позволяет создавать словари из существующих итерируемых объектов.

  

Предположим, у вас есть список fruits. Вы хотите создать словарь с ключами fruit:len(fruit).

Вот как вы можете сделать это с помощью цикла for:

fruits = ["яблоко", "банан", "вишня", "финик"]fruit_lengths = {}for fruit in fruits:    fruit_lengths[fruit] = len(fruit)print(fruit_lengths)

 

Вывод >>> {'apple': 5, 'banana': 6, 'cherry': 6, 'date': 4}

 

Давайте теперь напишем словарное включение, эквивалентное:

fruits = ["apple", "banana", "cherry", "date"]fruit_lengths = {fruit: len(fruit) for fruit in fruits}print(fruit_lengths)

 

Вывод >>> {'apple': 5, 'banana': 6, 'cherry': 6, 'date': 4}

 

Это словарное включение создает словарь, где ключи – это названия фруктов, а значения – это длины названий фруктов.

 

Словарное включение с условной фильтрацией

 

Давайте модифицируем наше словарное включение, чтобы включить условие:

fruits = ["apple", "banana", "cherry", "date"]long_fruit_names = {fruit: len(fruit) for fruit in fruits if len(fruit) > 5}print(long_fruit_names)

 

Вывод >>> {'banana': 6, 'cherry': 6}

 

Здесь словарное включение создает словарь с названиями фруктов в качестве ключей и их длинами в качестве значений, но только для фруктов с названиями длиннее 5 символов.

 

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

 

Менеджеры контекста в Python позволяют эффективно управлять ресурсами. С помощью менеджеров контекста вы можете легко настраивать и закрывать (освобождать) ресурсы. Самый простой и наиболее распространенный пример использования менеджеров контекста – это обработка файлов.

Взгляните на следующий фрагмент кода:

filename = 'somefile.txt'file = open(filename,'w')file.write('Something')

 

Он не закрывает дескриптор файла, что приводит к утечке ресурсов.

print(file.closed)Вывод >>> False

 

Вы, наверное, придумаете следующее:

filename = 'somefile.txt'file = open(filename,'w')file.write('Something')file.close()

 

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

Что ж, теперь вы можете реализовать отлов исключений и попытку открытия файла и записи в него в отсутствие ошибок:

filename = 'somefile.txt'file = open(filename,'w')try:    file.write('Something')finally:    file.close()

 

Но это многословно. Теперь рассмотрим следующую версию, использующую оператор with, который поддерживает функцию open(), являющуюся менеджером контекста:

filename = 'somefile.txt'with open(filename, 'w') as file:    file.write('Something')print(file.closed)

 

Вывод >>> True

 

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

 

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

 

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

 

Что такое генераторы?

 

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

 

Как работают генераторы?

 

 

Давайте узнаем, как работают генераторы:

  • Генераторная функция определяется как обычная функция, но вместо использования ключевого слова return вы будете использовать yield, чтобы выдать значение.
  • Когда вы вызываете генераторную функцию, она возвращает объект-генератор. Вы можете перебрать его, используя цикл или вызовом next().
  • Когда встречается оператор yield, состояние функции сохраняется, и возвращается выданное значение вызывающей стороне. Выполнение функции приостанавливается, но ее локальные переменные и состояние сохраняются.
  • При следующем вызове метода next() генератора, выполнение возобновляется с того места, где оно было приостановлено, и функция продолжается до следующего оператора yield.
  • Когда функция завершается или вызывает исключение StopIteration, генератор считается исчерпанным, и дальнейшие вызовы next() вызовут исключение StopIteration.

 

Создание генераторов

 

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

Вот пример генераторной функции:

def countdown(n):    while n > 0:        yield n        n -= 1# Использование генераторной функцииfor num in countdown(5):    print(num)

 

Вывод >>> 5 4 3 2 1

 

Генераторные выражения похожи на генераторные выражения списка, но они создают генераторы вместо списков.

# Генераторное выражение для создания последовательности квадратовsquares = (x ** 2 for x in range(1, 6)) # Использование генераторного выраженияfor square in squares:    print(square)

 

Вывод >>> 1 4 9 16 25

 

5. Использование коллекционных классов

 

Завершим учебник, изучив два полезных коллекционных класса:

  • NamedTuple
  • Counter

 

Более читаемые кортежи с NamedTuple

 

В Python namedtuple в модуле collections представляет собой подкласс встроенного класса кортежей, но с именованными полями. Это делает его более читаемым и самодокументирующимся, чем обычные кортежи.

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

# Кортеж для 3D-точкиcoordinate = (1, 2, 3)# Доступ к данным с использованием распаковки кортежаx, y, z = coordinateprint(f"X-координата: {x}, Y-координата: {y}, Z-координата: {z}")

 

Вывод >>> X-координата: 1, Y-координата: 2, Z-координата: 3

 

А вот версия с использованием namedtuple:

from collections import namedtuple# Определение именованного кортежас Coordinate3D = namedtuple("Coordinate3D", ["x", "y", "z"])# Создание объекта Coordinate3Dcoordinate = Coordinate3D(1, 2, 3)print(coordinate)# Доступ к данным с использованием именованных полейprint(f"X-координата: {coordinate.x}, Y-координата: {coordinate.y}, Z-координата: {coordinate.z}")

 

Вывод >>>Coordinate3D(x=1, y=2, z=3)X-координата: 1, Y-координата: 2, Z-координата: 3

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

Используйте Counter, чтобы упростить подсчет

Counter – это класс в модуле collections, предназначенный для подсчета частоты элементов в итерируемом объекте, таком как список или строка). Он возвращает объект Counter с парами ключ-значение {element:count}.

Давайте рассмотрим пример подсчета частот символов в длинной строке.

Вот традиционный подход к подсчету частоты символов с использованием циклов:

word = "непостижимости"# инициализируем пустой словарь для подсчета символовchar_counts = {}# Подсчитываем частоту символовfor char in word:    if char in char_counts:        char_counts[char] += 1    else:         char_counts[char] = 1# выводим словарь char_countsprint(char_counts)# находим наиболее часто встречающийся символmost_common = max(char_counts, key=char_counts.get)print(f"Самый частый символ: '{most_common}' (появляется {char_counts[most_common]} раз)")

Мы вручную перебираем строку, обновляем словарь для подсчета частоты символов и находим наиболее часто встречающийся символ.

Вывод >>>{'и': 5, 'н': 2, 'п': 1, 'о': 1, 'с': 1, 'т': 1, 'ж': 1, 'м': 1, 'с': 2, 'м': 1, 'в': 2, 'о': 1, 'т': 1}Самый частый символ: 'и' (появляется 5 раз)

Теперь давайте выполним ту же задачу с использованием класса Counter с использованием синтаксиса Counter(iterable):

from collections import Counterword = "непостижимости"# Подсчитываем частоту символов с помощью Counterchar_counts = Counter(word)print(char_counts)# Найдем наиболее часто встречающийся символmost_common = char_counts.most_common(1)print(f"Самый частый символ: '{most_common[0][0]}' (появляется {most_common[0][1]} раз)")

Вывод >>>Counter({'и': 5, 'н': 2, 'п': 1, 'о': 1, 'с': 1, 'т': 1, 'ж': 1, 'м': 1, 'с': 2, 'м': 1, 'в': 2, 'о': 1, 'т': 1})Самый частый символ: 'и' (появляется 5 раз)

Так Counter предоставляет намного более простой способ подсчета частоты символов без необходимости вручного перебора и управления словарем.

Заключение

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

Удачи в обучении!

[Бала Прия C](https://twitter.com/balawc27) – разработчик и технический писатель из Индии. Она любит работать в области математики, программирования, науки о данных и создания контента. Ее область интересов и экспертизы включает в себя DevOps, науку о данных и обработку естественного языка. Она любит чтение, писание, кодирование и кофе! В настоящее время она работает над изучением и передачей своих знаний сообществу разработчиков путем написания руководств, практических руководств, мнений и многого другого.