Хабрахабр

[Перевод] Полезные советы по Python, которых вы ещё не встречали. Часть 2

Недавно мы опубликовали перевод материала, в котором были приведены полезные советы для Python-программистов. У того материала есть продолжение, которое мы представляем вашему вниманию сегодня.

Именование среза с использованием функции slice

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

# ID First Name Last Nameline_record = "2 John Smith" ID = slice(0, 8)FIRST_NAME = slice(9, 21)LAST_NAME = slice(22, 27) name = f"{line_record[FIRST_NAME].strip()} {line_record[LAST_NAME].strip()}"# name == "John Smith"

В этом примере можно видеть, что, дав срезам имена с помощью функции slice, и использовав эти имена при получении фрагментов строки, мы смогли избавиться от запутанных индексов. Узнать подробности об объекте slice можно с помощью его атрибутов .start, .stop и .step.

Запрос пароля у пользователя во время выполнения программы

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

import getpass user = getpass.getuser()password = getpass.getpass()# Выполнить некие действия...

Этот очень простой пакет позволяет запрашивать у пользователя его пароль, а также получать имя пользователя, извлекая имя, под которым он вошёл в систему. Правда, при работе с паролями стоит знать о том, что не все системы поддерживают скрытие паролей. Python постарается вас об этом уведомить. Если это произойдёт — вы увидите соответствующее предупреждение в командной строке.

Нахождение близких соответствий в строках

Теперь поговорим о немного более таинственной возможности стандартной библиотеки Python. Предположим, вы попали в ситуацию, когда вам понадобилось, задействуя концепцию наподобие расстояния Левенштейна, найти в списке слова, похожие на некую входную строку. Решить эту задачу можно с помощью модуля difflib.

import difflibdifflib.get_close_matches('appel', ['ape', 'apple', 'peach', 'puppy'], n=2)# returns ['apple', 'ape']

Метод difflib.get_close_matches ищет наилучшие, «достаточно хорошие» совпадения. Первый аргумент этого метода задаёт искомую строку, второй аргумент задаёт список, в котором выполняется поиск. Этому методу можно передать необязательный аргумент n, который задаёт максимальное число возвращаемых совпадений. Ещё этот метод поддерживает необязательный именованный аргумент cutoff (по умолчанию он установлен в значение 0.6), который позволяет задавать пороговое значение для оценки совпадений.

Работа с IP-адресами

Если вам приходится писать на Python программы для работы с сетью — это значит, что вам может очень пригодиться модуль ipaddress. Одним из вариантов его использование является генерирование списка IP-адресов из диапазона адресов, заданных в формате CIDR (Classless Inter-Domain Routing, бесклассовая адресация).

import ipaddressnet = ipaddress.ip_network('74.125.227.0/29') # Подходит и для работы с IPv6# IPv4Network('74.125.227.0/29') for addr in net: print(addr) # 74.125.227.0# 74.125.227.1# 74.125.227.2# 74.125.227.3# ...

Ещё одна полезная возможность этого модуля — проверка IP-адреса на предмет принадлежности его к некоей сети:

ip = ipaddress.ip_address("74.125.227.3") ip in net# True ip = ipaddress.ip_address("74.125.227.12")ip in net# False

У модуля ipaddress есть и много других интересных возможностей, о которых я тут не рассказываю. Почитать подробности о нём можно здесь. Правда, пользуясь этим модулем, учитывайте ограничения, касающиеся его совместной работы с другими модулями, имеющими отношение к сетевому программированию. Например, нельзя использовать экземпляры IPv4Network в виде строк адреса. Подобные объекты для этого сначала надо конвертировать в строки с помощью str.

Отладка программы в командной строке

Если вы — из тех, кто не хочет пользоваться IDE и пишет код в Vim или Emacs, тогда вы, возможно, попадали в ситуацию, когда вам пригодился бы отладчик, вроде тех, что есть в IDE. И знаете что? У вас такой отладчик уже есть. Для того чтобы им воспользоваться, достаточно запустить программу с помощью конструкции вида python3.8 -i. Флаг -i позволяет, после завершения программы, запустить интерактивную оболочку. С её помощью можно исследовать переменные и вызывать функции. Это интересная возможность, но как насчёт настоящего отладчика (pdb)? Давайте поэкспериментируем со следующей простой программой, код которой находится в файле script.py:

def func(): return 0 / 0 func()

Запустим её командой python3.8 -i script.py и получим следующее:

# Скрипт дал сбой... Traceback (most recent call last): File "script.py", line 4, in <module> func() File "script.py", line 2, in func return 0 / 0ZeroDivisionError: division by zero>>> import pdb>>> pdb.pm() # Запускаем отладчик после завершения программы> script.py(2)func()-> return 0 / 0(Pdb)

Мы видим место программы, в котором произошёл сбой. Зададим точку останова:

def func(): breakpoint() # import pdb; pdb.set_trace() return 0 / 0 func()

Снова запустим скрипт.

script.py(3)func()
-> return 0 / 0
(Pdb)  # начинаем здесь
(Pdb) step
ZeroDivisionError: division by zero
> script.py(3)func()
-> return 0 / 0
(Pdb)

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

Объявление нескольких конструкторов в классе

Перегрузка функций — это одна из возможностей, весьма широко используемых в различных языках программирования, но не в Python. И даже хотя в Python нельзя перегрузить обычную функцию, мы можем пользоваться чем-то вроде перегрузки конструкторов с использованием методов класса:

import datetime class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day @classmethod def today(cls): t = datetime.datetime.now() return cls(t.year, t.month, t.day) d = Date.today()print(f"{d.day}/{d.month}/{d.year}")# 14/9/2019

В подобной ситуации вы, вместо использования методов класса, можете склониться к тому, чтобы поместить всю логику альтернативных конструкторов в __init__ и решить задачу с использованием *args, **kwargs и множества выражений if. В результате может получиться рабочий код, но этот код будет тяжело читать и поддерживать. Тут я порекомендовал бы поместить минимум логики в __init__ и выполнить все операции в отдельных методах/конструкторах. При таком подходе в нашем распоряжении окажется чистый код, с которым удобно будет работать и автору этого кода, и тому, кто этим кодом будет пользоваться.

Кэширование результатов вызова функций с помощью декоратора

Доводилось ли вам писать функции, которые выполняли какие-нибудь длительные операции чтения-записи, или достаточно медленные рекурсивные вычисления? Думали ли вы при этом о том, что таким функциям не повредило бы кэширование результатов? Кэшировать (мемоизировать) результаты вызова функции можно с помощью декоратора lru_cache из модуля functools:

from functools import lru_cacheimport requests @lru_cache(maxsize=32)def get_with_cache(url): try: r = requests.get(url) return r.text except: return "Not Found" for url in ["https://google.com/", "https://martinheinz.dev/", "https://reddit.com/", "https://google.com/", "https://dev.to/martinheinz", "https://google.com/"]: get_with_cache(url) print(get_with_cache.cache_info())# CacheInfo(hits=2, misses=4, maxsize=32, currsize=4)

В этом примере мы выполняем GET-запросы, результаты которых кэшируются (кэшировано может быть до 32 результатов). Тут можно увидеть и то, что мы получаем сведения о кэше функции, воспользовавшись методом cache_info. Декоратор, кроме того, даёт в наше распоряжение метод clear_cache, применяемый для инвалидации кэша. Тут мне ещё хотелось бы отметить то, что кэширование нельзя использовать с функциями, у которых есть побочные эффекты, или с функциями, создающими мутабельные объекты при каждом вызове.

Нахождение элементов, которые встречаются в итерируемом объекте чаще всего

Нахождение в списке таких элементов, которые встречаются в нём чаще других, это весьма распространённая задача. Решить её можно, например, воспользовавшись циклом for и словарём, в котором будут собраны сведения о количестве одинаковых элементов. Но такой подход — это пустая трата времени. Дело в том, что решать подобные задачи можно с помощью класса Counter из модуля collections:

from collections import Counter cheese = ["gouda", "brie", "feta", "cream cheese", "feta", "cheddar", "parmesan", "parmesan", "cheddar", "mozzarella", "cheddar", "gouda", "parmesan", "camembert", "emmental", "camembert", "parmesan"] cheese_count = Counter(cheese)print(cheese_count.most_common(3))# Вывод: [('parmesan', 4), ('cheddar', 3), ('gouda', 2)]

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

print(cheese_count["mozzarella"])# Вывод: 1 cheese_count["mozzarella"] += 1 print(cheese_count["mozzarella"])# Вывод: 2

Кроме того, при работе с Counter в нашем распоряжении оказывается метод update(more_words), используемый для добавления к счётчику новых элементов. Ещё одна полезная возможность Counter заключается в том, что он позволяет использовать математические операции (сложение и вычитание) при работе с экземплярами этого класса.

Итоги

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

Уважаемые читатели! Знаете какие-нибудь интересные приёмы Python-программирования? Если так — просим ими поделиться.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть