Изображение автора

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

Как энтузиаст данных и Python-разработчик, вы должны овладеть этими полезными практиками, чтобы оставаться востребованными на современном конкурентном рынке, где тысячи технических специалистов ищут работу.

Почему Python?

Python — чрезвычайно мощный язык программирования общего назначения. Он отлично подходит для решения задач, связанных с наукой о данных, благодаря обширным библиотекам и потрясающим фреймворкам, таким как SciKit-Learn, TensorFlow и PyTorch, благодаря простому и понятному синтаксису.

Это означает, что знание Python обеспечит гибкость при создании самых разных приложений. К примеру, он поможет любому новичку справиться с задачами автоматизации или разработки чат-ботов для взаимодействия с LLM с открытым исходным кодом. Ведь Python можно освоить легче и быстрее, чем другие языки программирования.

Итак, приступим к ознакомлению с лучшими практиками создания Python-кода.

Как повысить качество кода на Python

1. Использование функций-генераторов для экономии памяти

Допустим, вам нужно проанализировать большой файл (large_file), который не помещается в оперативной памяти. Чтобы эффективно решить эту проблему, можно создать функцию-генератор process_large_file, которая будет читать большой файл построчно.

Функции-генераторы не только помогают обрабатывать большие данные партиями, но и экономят память, поскольку избавляют от необходимости хранить все данные в памяти.

def process_large_file(file_path):
"""
Функция-генератор для построчного прочтения большого файла.
"""
with open(file_path, 'r') as file:
for line in file:
# Обработка каждой строки
yield line

# Использование генератора для обработки лог-файла
log_file_path = 'path/to/your/large_file.txt'

# Обработка большого файла
large_file = process_large_file(file_path)

for entry in large_file:
# осуществление действий над каждой записью лога
print(entry)

Приведенный выше код отображает вывод, который записывается в файл large_file.txt.

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

2. Использование .setdefault() в словарях

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

Чтобы упростить этот процесс, используйте функцию setdefault(), вставляя ключ с заданным значением по умолчанию, если такого ключа еще нет в словаре.

# Первоначальное состояние запасов
inventory: dict[str, int] = {"jeans": 500, "top": 600}

# Добавление дополнительных товаров с уровнями запасов по умолчанию, если их еще нет.
products_to_add: list[str] = ["skirt", "shirt", "tee"]

for product in products_to_add:
inventory.setdefault(product, 500)

# Выведение конечного состояния запасов
print("Final updated inventory:", inventory)

"""
# Вывод:
Final updated inventory: {'jeans': 500, 'top': 600, 'skirt': 500, 'shirt': 500, 'tee': 500}
"""

Так можно избежать необходимости явных проверок и присваиваний, что сделает код более лаконичным и читабельным.

3. Создание словарей для предотвращения множества if/elif

Предположим, у вас несколько функций должны вызываться в зависимости от пользовательского ввода.

Наиболее распространенным способом решения этой проблемы является использование условных операторов if-elif. Но такой подход может стать слишком длинным и сложным, если иметь дело с сотнями функций.

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

# Функция 1 
def first():
print("Calling First Function...")


# Функция 2
def second():
print("Calling Second Function...")


# Функция 3
def third():
print("Calling Third Function...")


# Функция по умолчанию
def default():
print("Calling Default Function...")

# Пользовательский ввод
options: int = int(input("Enter an option :", ))

# Словарь для хранения опций в качестве ключей и функций в качестве значений
funcs_dict : dict[int, str] = {1: first, 2: second, 3: third}

# Проверяем, существует ли ключ, и если его нет, то запускаем функцию по умолчанию
final_result = funcs_dict.get(options, default)
final_result()

Запустив программу, увидите на экране следующие результаты:

"""
# Вывод:
# Когда опция send была равна 0
Enter an option :0
Calling Default Function...

# Когда опция send была равна 1
Enter an option :1
Calling First Function...

# и так далее...
"""

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

4. Применение счетчика из модуля collections

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

Counter (счетчик) предоставляет простой и эффективный способ подсчета элементов в итерируемом объекте, сводя к минимуму сложность написания пользовательской логики подсчета.

Реализуем этот прием:

from collections import Counter
import re

# Чтение текстового файла
with open("sample_text.txt", "r") as file:
text = file.read()

# Очистка и токенизация текста
cleaned_text: str = re.sub(r'[^\w\s]', '', text.lower().split())

# Использование Counter() для подсчета слов
word_counts: Counter = Counter(cleaned_text)

# Выведение второго по частоте употребления слова
most_commmon = counter.most_common(2) # переданное число обозначает, сколько обычных чисел мы хотим получить (отсчет начинается с 1-n)
print("Second Most Common Word is: ", most_commmon[0]) # вывод в нулевом элементе индекса одного из 2 самых употребимых слов

"""
# Вывод:
Second Most Common Number is: ('data', 82)
"""

Примечание: вы также можете выполнять арифметические операции, легко преобразовывать объект Counter в другие структуры данных, такие как словари, и использовать его полезные методы, такие как element()most_common() и др.

5. Оптимизация кода с помощью мемоизации

Мемоизация — техника, используемая в динамическом программировании для оптимизации временной сложности рекурсивных алгоритмов путем повторного использования вызовов дорогостоящих функций при повторном вводе одних и тех же входных данных.

Классическим примером этого является задача про кроликов, известная как последовательность Фибоначчи.

import time

def memo_fibonacci(num: int, dictionary: dict[int, int]):
if num in dictionary:
return dictionary[num]
else:
dictionary[num] = memo_fibonacci(num-1, dictionary) + memo_fibonacci(num-2, dictionary)
return dictionary[num]

# Фиксация с помощью словаря
dictionary: dict[int, int] = {0: 1, 1: 1}

# Истекшее время
start_time: float = time.time()
# Вызов функции
result: int = memo_fibonacci(48, dictionary)
end_time: float = time.time()
# Расчет истекшего времени
elapsed_time: float = end_time - start_time


print(f"Result: {result}") # Результат: 7778742049
print(f"Elapsed time: {elapsed_time:.6f} seconds") # Истекшее время: 0.000133 секунд

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

6. Предотвращение повторяемости с помощью декораторов

Допустим, вы создаете проект на Python и хотите узнать время выполнения той или иной функции. Конечно, можно использовать функцию time для этой функции (как было показано выше), но что, если у вас десятки или даже сотни функций?

Написание start-time и end-time займет целую вечность. Вместо этого можно создать функцию elapsed_time, которая будет делать то же самое за вас. Нужно будет только добавить @elapsed_time к функции, высчитывающей время.

Что такое декораторы?

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

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

import time

def elapsed_time(func):
def wrapper():
start_time: float = time.time()

func()

end_time: float = time.time() - start_time
print(f"{func.__name__}() took {end_time:.6f} seconds")

return wrapper

@elapsed_time
def some_code():
# Симуляция запуска кода
time.sleep(0.00002)

# Вызов функции
some_code() # some_code() took 0.000009 seconds

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

Примечание: рекомендуется не злоупотреблять декораторами, поскольку они могут затуманивать то, что делает код.

7. Применение dataclass для создания чистых структур данных

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

Изображение автора

Модуль dataclass, появившийся в Python 3.7, является более эффективным способом хранения данных, которые будут передаваться между различными частями программы.

Заметьте: всего в нескольких строках можно создать менее подверженные ошибкам классы данных, без необходимости писать конструктор и несколько других уже реализованных методов вручную.

from dataclasses import dataclass

@dataclass
class Employee:
id_: int
name: str
salary: float

e1 = Employee(id_=1, name='Tusk', salary=69999.99)
print(e1) # Сотрудники(id_=1, name='Tusk', salary=69999.99)

Здесь вывод также эквивалентен стандартному классу Python, реализованному с помощью __repr__.

Примечание: можно также настроить представление класса Employee:

from dataclasses import dataclass

@dataclass
class Employee:
id_: int
name: str
salary: float

def __repr__(self):
return f"{self.name} earns ${self.salary}."

e1 = Employee(id_=1, name='Tusk', salary=69999.99)
print(e1) # Таск зарабатывает $69999.99.

Начните использовать dataclass, чтобы избежать избыточности. Это сделает код более читаемым и удобным для сопровождения.

8. Чистая обработка ввода с помощью match 

Начиная с Python 3.10, структурное сопоставление с образцом (structural pattern matching) было добавлено в виде шаблонов match с соответствующими операторами case.

Допустим, у нас есть класс Point, который представляет точку в двухмерной системе координат. Создадим функцию where_is для обработки случаев, когда пользователь вводит запрос на поиск точки в 2D-плоскости.

Оператор match поочередно сопоставляет значение выражения с каждым блоком оператора case.

from dataclasses import dataclass

# Определение класса с помощью dataclass
@dataclass
class Point:
x: int
y: int

# Сопоставление операторов для обработки разных случаев
def where_is(point):
match point:
case Point(x=0, y=0):
return ("Origin")
case Point(x=0, y=y):
return (f"Y={y}")
case Point(x=x, y=0):
return (f"X={x}")
case Point(x, y):
return("Somewhere else")
# Для фиксации других пользовательских вводов
case _:
return("Not a point")

# Примеры
print(where_is(Point(0, 0))) # Вывод: Origin
print(where_is(Point(0, 10))) # Вывод: Y=10
print(where_is(Point(10, 0))) # Вывод: X=10
print(where_is(Point(10, 10))) # Вывод: Somewhere else
print(where_is("Not a point")) # Вывод: Not a point

Использование операторов match-case позволяет обрабатывать все возможные случаи, обеспечивая полное соответствие шаблону.

9(A). Использование оператора all вместо and

Представьте, что вы создаете систему профилей пользователей и хотите удостовериться, что все обязательные поля в форме заполнены пользователем (не знаю, почему вы не пометили звездочкой поля, «обязательные для заполнения»; сейчас сосредоточимся на операторе all).

Итак, вместо нескольких условий с оператором and, можно использовать функцию all, которая вернет значение True, только когда все элементы в предоставленном итерируемом объекте равны True.

# Пользовательский ввод из формы регистрации
form_data: dict[str, str] = {"name" : "Nikita",
"email": "[email protected]",
"phone": "123478911"}

# Список обязательных полей
required_fields: list[str] = ["name", "email", "phone"]

# Использование оператора all
if all(field in form_data for field in required_fields):
print("All required fields are filled out.")
else:
print("Some required fields are missing or empty.")

"""
# Вывод:
All required fields are filled out.
"""

9(B). Использование оператора any вместо or

Функция any возвращает True, если любой элемент итерируемого объекта равен True.

Например, вам нужно ограничить некоторые разрешения для определенных пользователей, основываясь на конкретных критериях. Для этого можете использовать any вместо нескольких условий с оператором or.

# Список разрешений для пользователя
user_permission: list[str] = ["read", "execute"]

# Проверяем, есть ли у пользователя хотя бы одно из требуемых разрешений
required_permissions: list[str] = ["write", "read", "admin"]

# Использование оператора all
if any(permission in user_permission for permission in required_permissions):
print(f"Since you have required permissions. Access is allowed.")
else:
print("You're a standard user. Not allowed the access.")

"""
# Вывод:
Since you have required permissions. Access is allowed.
"""

Эти примеры показывают, как можно использовать any и all для упрощения условий, которые потребовали бы нескольких операторов or или and соответственно.

И последнее, но не менее важное для каждого программиста во все времена (оставайтесь со мной, если пока не в курсе этого маст-хэва).

10. Сокращение синтаксиса с помощью генераторов коллекций

Генераторы коллекций (comprehensions) — мощный набор инструментов, который Python предоставляет для всех итерируемых типов данных. Он позволяет избежать многострочных циклов, используя однострочные, в зависимости от ситуации.

Рассмотрим каждый вид генераторов отдельно.

10(A). Генераторы списков

Возможности генераторов списков (list comprehensions) продемонстрируем на примере вложенных операторов типа if. 

# Вложенный оператор if и генератор списков
fruits: list[str] = ["apple", "orange", "avacado", "kiwi", "banana"]
basket: list[str] = ["apple", "avacado", "apricot", "kiwi"]

[i for i in fruits if i in basket if i.startswith("a")] # ['apple', 'avacado']

Аналогичным образом можно применять вложенные циклы for, пока они не сделают генератор списка трудночитаемым.

10(B). Генераторы кортежей

Генераторов кортежей (tuple comprehensions) как таковых в Python не существует. Вместо них для создания кортежей используются выражения-генераторы.

# Выражение-генератор, преобразованное в кортеж 
tuple(i**2 for i in range(10))

# (0, 1, 4, 9, 16, 25, 36, 49, 64, 81)

Примечание: как следует из вышеприведенного кода, выражения-генераторы более экономичны по объему памяти, чем генераторы списков.

10(C). Генераторы словарей

Допустим, у вас есть список apple_names, и нужно вывести новый список, содержащий длину каждого элемента списка apple_names.

Конечно, можно использовать в данном случае генератор списков. Но знаете ли вы о возможности применения нотации для создания словаря, называемой генератором словарей (dictionary comprehension)?

# Создание списка apple names
apple_names: list[str] = ["apple", "pineapple", "green apple"]

# Создание словаря с apple names в качестве ключей и их длинами в качестве значений
print({apple_name: len(apple_name) for apple_name in apple_names})

# {"apple": 5, "pineapple": 9, "green apple": 11}

Согласитесь, это более читабельно, чем использование циклов или конструктора dict для создания словаря.

10(D). Генераторы множеств

Генераторы множеств (set comprehensions) можно использовать для фильтрации на основе определенных условий.

# Создание множества с условием
print({i**2 for i in range(1, 11) if i > 5})

# {64, 36, 100, 49, 81}

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

Заключение

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

Полный перечень примеров кода найдете здесь

Применяя эти 10 практик, вы не только будете писать эффективный код, но и повысите свою производительность.

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

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


Перевод статьи Nikita Prasad: 10 Ways to Write Better Python Codes

Предыдущая статьяНесложное WebGPU-программирование с использованием taichi.js