Сегодня, когда большая часть 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 практик, вы не только будете писать эффективный код, но и повысите свою производительность.
Читайте также:
- Как разбить текст на абзацы с помощью Python
- Как создать инструмент PGP-шифрования на основе Python
- Моделирование лесных пожаров
Читайте нас в Telegram, VK и Дзен
Перевод статьи Nikita Prasad: 10 Ways to Write Better Python Codes