[Перевод] Полезные советы по Python, которых вы ещё не встречали. Часть 2
Недавно мы опубликовали перевод материала, в котором были приведены полезные советы для Python-программистов. У того материала есть продолжение, которое мы представляем вашему вниманию сегодня.
Именование среза с использованием функции slice
Работа с множеством значений, которые заданы индексами, может быстро обернуться беспорядком — как в плане поддержки, так и в плане читабельности кода. Один из вариантов улучшения ситуации заключается в использовании констант для значений, задаваемых индексами. Но есть и более удачный способ написания качественного кода:
# ID First Name Last Name
line_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 difflib
difflib.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 ipaddress
net = 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
func()
File "script.py", line 2, in func
return 0 / 0
ZeroDivisionError: 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_cache
import 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-программирования? Если так — просим ими поделиться.