Источник: Nuances of Programming
Программирование само по себе очень увлекательное занятие, а программирование на Python увлекательнее вдвойне, поскольку в данном языке существует много разных способов реализации одних и тех же функциональностей. Однако в большинстве случаев предпочтительно использовать реализации, присущие исключительно Python, характерной особенностью которых являются чистота и краткость кода.
Программирование на Python или любом другом языке — это не высшая математика, здесь многое зависит от степени сформированности ваших навыков. Если вы целенаправленно начнете писатькод в стиле Python (иначе говоря, питонический), то эти техники вскоре пополнят ваш набор инструментов, и постепенно их использование в проектах станет самым привычным делом. Рассмотрим ряд таких простых приемов, которые, я надеюсь, окажутся вам полезны.
1. Отрицательная индексация
Работать с последовательностями — одно удовольствие, поскольку в таких случаях нам известен порядок элементов, и мы можем с легкостью оперировать ими, опираясь на него. Самыми часто используемыми последовательностями в Python являются строки, кортежи и списки. Доступ к их отдельным элементам происходит с помощью механизма индексации. Подобно другим ведущим языкам программирования Python поддерживает индексацию с отсчётом от 0, при которой мы получаем доступ к первому элементу, используя 0 в квадратных скобках. Помимо этого, можно также задействовать объекты среза для извлечения конкретных элементов последовательности, как показано в нижеприведенных примерах.
Однако Python расширяет свои возможности и поддерживает отрицательную индексацию. Речь идет об использовании -1 для обращения к последнему элементу последовательности и отсчета в обратную сторону. Так предпоследний элемент получает индекс -2 и т. д. Обратите внимание, что отрицательная индексация в объекте среза может также работать с положительным индексом.
2. Проверка пустоты контейнера
Контейнеры относятся к тем типам данных, которые могут хранить в себе другие данные. В число часто используемых встроенных контейнеров входят кортежи, списки, словари и множества. При работе с ними часто приходится проверять, содержат ли они какие-либо элементы, прежде чем приступать к выполнению дополнительных операций. Фактически мы можем проверить длину этих контейнеров, которая соответствует числу сохраненных элементов. Если длина равна 0, то контейнер пуст. Ниже представлен один из простых примеров.
if len(some_list) > 0:
# выполняет определенное действие, если список содержит элементы
else:
# если список пуст - выполняет другое действие
Однако данный способ не совсем питонический. Вместо этого мы можем просто проверить сам контейнер, который в случае наличия в нем элементов будет вычисляться какTrue. Несмотря на то, что следующий пример кода демонстрирует основные контейнеры, такой способ использования применим и к строкам (т. е. любые непустые строки — True).
3. Создание списка строк с помощью метода Split()
Строки часто применяются как указатели на определенные объекты. Например, мы можем использовать их для ключей в словаре. В проектах науки о данных строки часто представляют собой имена столбцов. При необходимости выбора нескольких из них мы будем вынуждены создать список строк. Мы действительно можем создавать строки, используя в списке литералы. Однако в этом случае нам придется прописывать пары кавычек, заключающие каждую строку, что для нас, “ленивых” программистов, немного утомительно. В связи с этим я бы предпочел создать список строк, воспользовавшись преимуществом метода split(), как показано в следующем фрагменте кода.
Как следует из примера, в качестве разделителей метод split() по умолчанию использует пробелы и создает список строк из строки. Обратите внимание, что, создавая список строк с элементами, содержащими пробелы, вы можете при желании использовать и другой вид разделителя, например запятые.
Такой способ использования возможен благодаря встроенным фукциональностям. К примеру, при создании класса именованного кортежа мы можем сделать следующее: Student = namedtuple(“Student”, [“name”, “gender”, “age”]). Список строк определяет “атрибуты” кортежа. При этом данная операция также по умолчанию осуществима следующим образом: Student = namedtuple(“Student”, “name gender age”). В другом случае создание класса Enum поддерживает те же альтернативные решения.
4. Тернарное выражение
Во многих случаях нам нужно определить переменные с конкретными значениями на основе заданных условий, для проверки которых мы можем просто использовать выражение if…else. Но для этого требуется несколько строк кода. Если речь идет о присваивании значения одной переменной, то возможен вариант с использованием тернарного выражения, которое проверяет условие и завершает присваивание в одной строке кода. Кроме того, благодаря более краткой форме данного выражения код становится гораздо лаконичнее. Рассмотрим следующий пример:
Иногда данные можно получить из определяемой функции и благодаря этому написать сокращенный вариант операции тернарного выражения, как показано ниже:
5. Инструкция with для файлового объекта
Зачастую нам необходимо как считывать данные из файлов, так и вносить их туда. Самый простой способ — открыть файл, используя встроенную функцию open(), создающую файловый объект, с которым можно работать. Приходилось ли вам ранее решать следующую задачу?
В данном фрагменте кода мы начинаем с текстового файла, содержащего текст “Hello World!”, затем добавляем в него новые данные. Однако через некоторое время, собравшись снова поработать с этим файлом и прочитать из него информацию, мы сталкиваемся с тем, что он по-прежнему содержит старые данные. Иначе говоря, добавленный текст не сохраняется в текстовом файле. Почему же это происходит?
Причина, прежде всего, в том, что мы не закрыли файловый объект, а без этой операции изменения не могут быть сохранены. На самом деле, мы можем напрямую вызвать метод close() для файлового объекта. Однако в нашем распоряжении также есть вариант с использованием инструкции “with”, которая автоматически закроет файловый объект, как показано в примере ниже. Завершив операцию с файлом, убедимся, что он закрыт через обращение к атрибуту файлового объекта closed.
В более общем смысле инструкция with — это синтаксис для использования менеджеров контекста в Python. Предыдущий пример предусматривает операцию с файлом, поскольку файлы являются совместно используемыми ресурсами, и мы несем ответственность за их освобождение. Контекстные менеджеры помогают нам справиться с этой задачей. Как демонстрирует вышеуказанный пример, по завершению операции с файлом он автоматически закрывается через использование инструкции with.
6. Вычисление нескольких условий
Довольно часто нам нужно вычислить несколько условий. В связи с этим существует ряд возможных сценариев. Применительно к численным значениям можно использовать несколько сравнений для одной и той же переменной. В этом случае мы можем связать их в цепочку.
В некоторых других сценариях мы сталкиваемся с несколькими сравнениями равенства и используем преимущество следующей техники, проверяя принадлежность с помощью ключевого слова in:
Суть другой техники для вычисления нескольких условий состоит в использовании встроенных функций all() и any(). Говоря точнее, функция all() вычисляется как True, когда все элементы итерируемого объекта — True, в связи с чем она подходит для замены серии логических сравнений AND. С другой стороны, функция any() вычисляется как True, когда любой элемент итерируемого объекта — True, а значит она подходит для замены серии логических операций OR. Обратимся к соответствующим примерам.
7. Использование значений по умолчанию в объявлениях функций
Почти во всех проектах Python большая часть кода предусматривает создание и вызов функций. Иначе говоря, мы постоянно имеем дело с их объявлениями и рефакторингом. Во многих сценариях функцию нужно вызывать несколько раз. В зависимости от различных наборов параметров она будет работать немного по-разному. Однако один набор параметров может использоваться чаще других, и в этом случае при объявлении функций следует рассмотреть установку значений по умолчанию. Обратимся к простому примеру.
Примечание. Когда при работе с изменяемыми типами данных (например, списками, множествами) вы устанавливаете значение по умолчанию, убедитесь, что вместо конструктора используете None (например, arg_name=[]). Так как Python создает объект функции там, где он определен, предоставление пустого списка будет за ним “закреплено”. Иначе говоря, при вызове объекта функции, этот объект не будет создаваться попутно. Вместо этого вы будете иметь дело с тем же объектом функции, включая его предустановленный изменяемый объект, изначально созданный в памяти, что может привести к неожиданному поведению.
8. Использование счетчика для подсчета элементов
Работая со списками, кортежами или строками, содержащими несколько элементов (например, несколько символов), у нас часто возникает потребность подсчитать количество каждого из них. Для реализации данной функциональности можно написать вот такой громоздкий код:
Как показано выше, сначала мы должны создать множество, включающее только уникальные слова. После этого мы перебираем это множество слов и используем метод count(), чтобы установить количество вхождений каждого из них. Однако есть более эффективный способ для решения этой задачи — применение класса Counter, предназначенного для подсчета элементов.
Класс Counter доступен в модуле collections. Для него мы просто создали генератор x.lower() for x in words и произвели подсчет каждого из элементов. Как следует из примера, Counter является подобным словарю объектом отображения, где каждый ключ соответствует уникальному элементу списка слов, а значения представляют собой подсчитанное количество этих элементов. Довольно лаконично, не так ли?
Более того, если вам понадобится выявить наиболее часто встречающиеся в списке слов элементы, можете воспользоваться методом most_common() объекта Counter. В следующем примере кода демонстрируются особенности его применения. Вам лишь необходимо указать целое число (N), которое позволит определить N наиболее часто встречающихся элементов в списке. Следует также отметить, что объект Counter будет работать и с другими последовательностями, такими как строки и кортежи.
9. Сортировка согласно различным условиям последовательности
Во многих проектах перед нами стоит задача сортировки элементов списка. В основном ее проводят по числовому или алфавитному порядку, используя функцию sorted(). По умолчанию данная функция сортирует список (на самом деле, это может быть любой итерируемый объект) по возрастанию. Если мы зададим условие, при котором аргумент reverse является True, то сможем получить последовательность элементов в порядке убывания. Обратимся к простым примерам.
Помимо основных случаев использования, можно указать аргумент key для сортировки сложных элементов, таких как список кортежей. Рассмотрим соответствующий пример.
В данном коде показаны 2 продвинутых примера сортировки с использованием лямбда-функции, которая передается в аргумент key. В первом случае элементы сортируются в порядке убывания, а во втором — в установленном по умолчанию порядке возрастания. А что если объединить два этих условия? Если вы рассматриваете возможность применения аргумента reverse, то, скорее всего, вас ждет разочарование, поскольку при попытке провести сортировку по нескольким критериям аргумент reverse будет применен ко всем. Что же предпринять? Обратимся к следующему фрагменту кода.
Как вы видите, ни один из вариантов установки аргумента reverse, будь то True или False, не сработал. А вот прием с отрицанием оценок оказался успешным, так как при предустановленной сортировке по возрастанию баллы будут отсортированы в обратном порядке вследствие отрицания этих значений. Однако, прибегая к этому методу, следует помнить, что отрицания применимы только к числовым значениям, но не к строкам.
10. Прием с defaultdict
Словари являются эффективным типом данных, позволяющим хранить их в виде пар ключ-значение. Все ключи изначально должны быть хэшируемыми, поскольку хранение этих данных может подразумевать использование хэш-таблицы. Такая реализация позволяет добиться эффективности O(1) при извлечении и вводе данных. Однако следует отметить, что кроме встроенного типа dict мы можем использовать альтернативные словари, на один из которых, а именно тип defaultdict, мне бы хотелось обратить ваше внимание. Defaultdict, в отличие от встроенного типа dict, позволяет нам по умолчанию установить фабричную функцию, которая в случае отсутствия ключа создает элемент. Вам, вероятно, приходилось встречать ошибку такого рода:
Предположим, что мы имеем дело со словами, и нам нужно сгруппировать одинаковые символы в список. Эти списки соотносятся с символами, являющимися ключами. Приведем пример простой реализации с использованием встроенного типа dict. При этом необходимо проверить, имеет ли объект dict ключ letter, поскольку, если его не существует, то вызов метода append() может привести к исключению KeyError.
Теперь рассмотрим вариант с defaultdict для написания более краткого кода. Несмотря на свою простоту, предложенный пример позволит вам составить представление о классе defaultdict, который избавляет нас от необходимости работать с несуществующими среди объектов словаряключами .
Заключение
Возможно, какие-то приемы были вам известны и до знакомства с материалом данной статьи, но надеюсь, что вы провели время с пользой и освежили свои знания. Использование питонических техник в проектах поможет вам писать более читаемый и производительный код.
Благодарю за внимание!
Читайте также:
Перевод статьи Yong Cui, Ph.D.: Write Better Python Code With These 10 Tricks