Найти тему
Nuances of programming

4 пайтонические техники для краткого кода

Источник: Nuances of Programming

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

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

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

1. Enumerate в цикле for

Применение циклов for избавляет от написания повторяющегося кода для одной и той же работы. Во многих случаях требуется регистрация положения элемента в итерируемом объекте. Рассмотрим 2 возможные реализации многословной версии без enumerate :

# Список гостей
arrived_guests = ["John" , "Ashley" , "Danny" , "Bobby" ]

for guest in arrived_guests:
arrived_order = arrived_guests.index(guest) + 1 print(f"# {arrived_order} : {guest} ")

for guest_i in range(len(arrived_guests)):
guest = arrived_guests[guest_i]
print(f"# {guest_i + 1 }: {guest} ")

  • Первый цикл for содержит метод index() для определения положения элемента, который извлекается напрямую путем доступа к списку.
  • Второй цикл for включает функцию range() для создания итерируемого объекта, производящего индекс, по которому извлекается элемент.

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

for guest_i, guest in enumerate(arrived_guests, 1 ):
print(f"# {guest_i} : {guest} ")

  • enumerate() получает в качестве первого параметра список, который производит итератор, содержащий каждый элемент в виде объекта кортежа.
  • Объект кортежа состоит из 2 компонентов: счетчика (или “индекса”) и элемента. В этом примере для получения к ним прямого доступа мы используем распаковку.
  • Второй параметр функции enumerate() определяет число, с которого запускается счетчик. В примере установлено значение 1 , указывающее на то, что отсчет начинается с 1 .

2. Проверка контейнера на пустоту

Как правило, кортежи, списки, словари и множества в Pyhton относятся к контейнерам, поскольку все эти типы данных содержат другие объекты в качестве элементов. Примечательно, что они могут быть пустыми. В связи с этим при работе с такими контейнерами данных зачастую необходимо проверять наличие в них элементов, перед тем как переходить к выполнению других операций.

В качестве примера рассмотрим список, но тот же принцип проверки распространяется и на другие типы данных.

Далее следуют 2 возможные реализации многословной версии:

# Список, полученный от сервера
fetched_data = []
if len(fetched_data) > 0 :
print("We fetched some data." )
else :
print("We didn't fetch any data." )

if fetched_data != []:
print("We fetched some data" )
else :
print("We didn't fetch any data" )

  • В первом примере присутствует функция len() , проверяющая число элементов в списке. Если его длина превышает 0 , значит, он не пустой.
  • Во втором примере сравниваются значения полученного и пустого списков. Если они не совпадают, то полученный список не пустой.

Следующий код демонстрирует более краткую версию:

if fetched_data:
print("We fetched some data" )
else :
print("We didn't fetch any data" )

Данный код использует то обстоятельство, что Python оценивает пустой список как False , а не пустой  —  как True . Такого принципа проверки также придерживаются и в отношении других контейнеров: кортежей, словарей и множеств. Кстати говоря, он же подходит и для строк, которые являются True при условии, что они не пустые.

3. Именованные кортежи в качестве контейнеров данных

Если проект предполагает чтение данных, обладающих одинаковой структурой, то можно прибегнуть к контейнерам, которые позволяют получать доступ к отдельным элементам данных. Допустим, один блок данных содержит 3 единицы информации о клиенте: имя, возраст и пол. Рассмотрим реализации многословной версии:

# Словари
client0 = {"name" : "John" , "age" : 37, "gender" : "M" }
client1 = {"name" : "Danny" , "age" : 41, "gender" : "M" }
client2 = {"name" : "Jennifer" , "age" : 34, "gender" : "F" }


# Пользовательский класс class Client:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender


client0 = Client("John" , 37, "M" )
client1 = Client("Danny" , 41, "M" )
client2 = Client("Jennifer" , 34, "F" )

  • Для представления каждого клиента возможен вариант с использованием словарей. Однако нельзя исключать вероятность ошибок в написании ключей, что приведет к исключениям KeyError .
  • Мы также можем создать пользовательский класс для управления информацией клиента. Но этот способ сопровождается потреблением памяти отдельными объектами и дополнительными затратами ресурсов на надлежащее обслуживание объекта класса.

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

from collections import namedtuple

Client = namedtuple("Client" , "name age gender" )
client0 = Client("John" , 37, "M" )
client1 = Client("Danny" , 41, "M" )
client2 = Client("Jennifer" , 34, "F" )

  • namedtuple —  это фабричная функция, доступная в модуле collections . Такое название обусловлено тем, что она создает новый тип данных, являющихся подтипом кортежей, как показано ниже:

>> > type(Client)
<class 'type '> >> > issubclass(Client, tuple)
True

  • В функции namedtuple мы передаем имя класса в качестве первого параметра, а атрибуты (строку, разделенную пробелами, или список строк)  —  в качестве второго.
  • При создании экземпляров класса Client можно задействовать тот же самый метод инстанцирования, что и для обычного пользовательского класса.
  • Более того, есть возможность воспользоваться той же точечной нотацией для обращения к “атрибутам” объекта кортежа аналогично объектам пользовательского класса:

>> > client0 = Client("John" , 37 , "M" )
>> > client0 .name
'John' >> > client0 .age
37 >> > client0 .gender
'M'

4. Частичные функции

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

# Общая вспомогательная функция
def save_image_to_directory (image_data, file_name, desired_directory) : print(f"{image_data} , {file_name} , {desired_directory} ")

# Событие 0
save_image_to_directory("image_data0_101" , "event0_101.png" , "folder_for_event0" )
save_image_to_directory("image_data0_102" , "event0_102.png" , "folder_for_event0" )
# Много других вызовов

# Событие 1
save_image_to_directory("image_data1_101" , "event1_101.png" , "folder_for_event1" )
save_image_to_directory("image_data1_102" , "event1_102.png" , "folder_for_event1" )
# Много других вызовов

  • Вспомогательная функция save_image_to_directory используется в различных модулях.
  • При работе с Event 0 мы передаем 3 параметра функции. Примечательно, что третий параметр всегда один и тот же в области видимости модуля.
  • Что касается другого события, то здесь выполняется тот же сценарий с повторением третьего параметра для каждого из вызовов.

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

from functools import partial
# Событие 0
save_image_for_event0 = partial (save_image_to_directory, desired_directory='folder_for_event0' )
save_image_for_event0("image_data0_101" , "event0_101.png" )
save_image_for_event0("image_data0_102" , "event0_102.png" )

# Событие 1
save_image_for_event1 = partial (save_image_to_directory, desired_directory='folder_for_event1' )
save_image_for_event1("image_data1_101" , "event1_101.png" )
save_image_for_event1("image_data1_102" , "event1_102.png" )

  • Функция partial доступна в модуле functools . Она берет существующую функцию и применяет общий параметр для каждого модуля. В данном случае таким параметром является desired_directory .
  • Функция partial создает другую функцию. Ее вызов устраняет необходимость передавать общий параметр. Как видите, с этого момента нужно просто установить 2 параметра частичной функции.

Создать такую функцию также можно, воспользовавшись лямбда-функцией, как показано ниже. Однако это не столь явный, как очевидный прием с частичной функцией.

save_image_for_event2 = lambda x, y: save_image_to_directory(x, y, desired_directory='folder_for_event2' )
save_image_for_event2("image_data2_101" , "event2_101.png" )

Проверка лямбда-функции также проблематична, поскольку она не предоставляет никакой полезной информации в отличие от частичной функции, созданной с помощью partial . Можете сравнить оба варианта:

>> > save_image_for_event1
functools.partial(<function save_image_to_directory at 0x111bf68b0 >, desired_directory='folder_for_event1' )
>> > save_image_for_event2
<function <lambda> at 0x111bf6940 >

Заключение

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

Подведем краткие итоги:

  • Функция enumerate() применяется с целью создания счетчиков для элементов итерируемых объектов в циклах for .
  • Python оценивает пустые контейнеры как False , поэтому нет необходимости сравнивать их с другим значением.
  • Именованные кортежи  —  это легкий в реализации и гибкий контейнер данных, предназначенный только для их чтения.
  • Частичные функции устраняют необходимость повторять общие параметры внутри конкретной области видимости.

Читайте также:

Читайте нас в Telegram , VK

Перевод статьи Yong Cui : Apply These 4 Techniques To Write Concise Python Code