Полезные советы по 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 <module>
    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-программирования? Если так — просим ими поделиться.

Источник: https://habr.com/ru/company/ruvds/blog/485646/


Интересные статьи

Интересные статьи

В прошлый раз вашему вниманию предстал легендарный, но недоступный большинству пользователей процессор Pentium® Pro. Теперь заглянем немного глу… Нет, не глубже в пучину времен, ско...
Машину какой марки вы водите? Различные исследования (о которых пойдет речь в этой статье) выдвигают предположение, что марка автомобиля является ярким показателем того, насколько грубый в...
Эта статья продолжает цикл переводных заметок об OpenWhisk от автора Priti Desai. Сегодня рассмотрим примеры развертывания Zip-функций, зависимости GitHub, а также подробнее опишем синхронизаци...
Ваш сайт работает на 1С-Битрикс? Каждому клиенту вы даёте собственную скидку или назначаете персональную цену на товар? Со временем в вашей 1С сложилась непростая логика ценообразования и формирования...
В данной статье мы разберемся с эксплуатацией некоторых WEB-узвимостей на примере прохождения варгейма Natas. Каждый уровень имеет доступ к паролю следующего уровня. Все пароли также хранятся...