Наталья Кайда 16 сентября 2024

🐍⚙️ 10 способов оптимизации Python-кода

Python ценят за простоту, гибкость и читаемость, но критикуют за невысокую производительность. Эта критика не всегда обоснована: есть несколько эффективных способов значительно повысить скорость Python-приложений, предназначенных для выполнения сложных вычислений и обработки больших объемов данных.
🐍⚙️ 10 способов оптимизации Python-кода

Упаковка переменных

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

В Python для упаковки данных в компактный бинарный формат используется модуль struct:

        import struct
import random
import sys

nums = [random.randint(0, 1000000) for _ in range(5000)]
packed_nums = struct.pack(f'{len(nums)}i', *nums)
original_size_nums = sys.getsizeof(nums)
packed_size_nums = sys.getsizeof(packed_nums)
efficiency = (1 - packed_size_nums / original_size_nums) * 100

print(f'Исходный размер: {original_size_nums} байт')
print(f'Упакованный размер: {packed_size_nums} байт')
print(f'Эффективность сжатия: {efficiency:.2f}%')

unpacked_nums = struct.unpack(f'{len(nums)}i', packed_nums)

for i in range(0, 5000, 100):
    print(f"Проверяем число с индексом {i}:")
    print(f"Исходное число: {nums[i]}, распакованное число: {unpacked_nums[i]}")
    assert nums[i] == unpacked_nums[i], f"Несовпадение чисел по индексу {i}"
print("Все числа совпали")


    

Результат:

        Исходный размер: 41880 байт
Упакованный размер: 20033 байт
Эффективность сжатия: 52.17%
Проверяем число с индексом 0:
Исходное число: 538873, распакованное число: 538873
Проверяем число с индексом 100:
Исходное число: 918169, распакованное число: 918169
Проверяем число с индексом 200:
Исходное число: 663463, распакованное число: 663463
Проверяем число с индексом 300:
Исходное число: 335298, распакованное число: 335298
Проверяем число с индексом 400:
Исходное число: 103470, распакованное число: 103470
Проверяем число с индексом 500:
Исходное число: 537139, распакованное число: 537139
Проверяем число с индексом 600:
Исходное число: 816242, распакованное число: 816242
Проверяем число с индексом 700:
Исходное число: 736703, распакованное число: 736703
Проверяем число с индексом 800:
Исходное число: 134686, распакованное число: 134686
Проверяем число с индексом 900:
Исходное число: 198001, распакованное число: 198001
Проверяем число с индексом 1000:
Исходное число: 833857, распакованное число: 833857
Проверяем число с индексом 1100:
Исходное число: 230153, распакованное число: 230153
Проверяем число с индексом 1200:
Исходное число: 728830, распакованное число: 728830
Проверяем число с индексом 1300:
Исходное число: 641456, распакованное число: 641456
Проверяем число с индексом 1400:
Исходное число: 794241, распакованное число: 794241
Проверяем число с индексом 1500:
Исходное число: 389231, распакованное число: 389231
Проверяем число с индексом 1600:
Исходное число: 455378, распакованное число: 455378
Проверяем число с индексом 1700:
Исходное число: 876660, распакованное число: 876660
Проверяем число с индексом 1800:
Исходное число: 812566, распакованное число: 812566
Проверяем число с индексом 1900:
Исходное число: 468887, распакованное число: 468887
Проверяем число с индексом 2000:
Исходное число: 769358, распакованное число: 769358
Проверяем число с индексом 2100:
Исходное число: 8201, распакованное число: 8201
Проверяем число с индексом 2200:
Исходное число: 977281, распакованное число: 977281
Проверяем число с индексом 2300:
Исходное число: 629243, распакованное число: 629243
Проверяем число с индексом 2400:
Исходное число: 117519, распакованное число: 117519
Проверяем число с индексом 2500:
Исходное число: 229750, распакованное число: 229750
Проверяем число с индексом 2600:
Исходное число: 833149, распакованное число: 833149
Проверяем число с индексом 2700:
Исходное число: 764713, распакованное число: 764713
Проверяем число с индексом 2800:
Исходное число: 174090, распакованное число: 174090
Проверяем число с индексом 2900:
Исходное число: 95317, распакованное число: 95317
Проверяем число с индексом 3000:
Исходное число: 241478, распакованное число: 241478
Проверяем число с индексом 3100:
Исходное число: 197858, распакованное число: 197858
Проверяем число с индексом 3200:
Исходное число: 152379, распакованное число: 152379
Проверяем число с индексом 3300:
Исходное число: 147324, распакованное число: 147324
Проверяем число с индексом 3400:
Исходное число: 561581, распакованное число: 561581
Проверяем число с индексом 3500:
Исходное число: 97425, распакованное число: 97425
Проверяем число с индексом 3600:
Исходное число: 92997, распакованное число: 92997
Проверяем число с индексом 3700:
Исходное число: 106960, распакованное число: 106960
Проверяем число с индексом 3800:
Исходное число: 197652, распакованное число: 197652
Проверяем число с индексом 3900:
Исходное число: 380352, распакованное число: 380352
Проверяем число с индексом 4000:
Исходное число: 732233, распакованное число: 732233
Проверяем число с индексом 4100:
Исходное число: 274363, распакованное число: 274363
Проверяем число с индексом 4200:
Исходное число: 131550, распакованное число: 131550
Проверяем число с индексом 4300:
Исходное число: 628213, распакованное число: 628213
Проверяем число с индексом 4400:
Исходное число: 403623, распакованное число: 403623
Проверяем число с индексом 4500:
Исходное число: 583847, распакованное число: 583847
Проверяем число с индексом 4600:
Исходное число: 697159, распакованное число: 697159
Проверяем число с индексом 4700:
Исходное число: 699888, распакованное число: 699888
Проверяем число с индексом 4800:
Исходное число: 242131, распакованное число: 242131
Проверяем число с индексом 4900:
Исходное число: 463454, распакованное число: 463454
Все числа совпали


    

Ускорение обработки файлов

В приложениях, для которых критически важна производительность, необходимо хранить часто используемые данные в памяти и минимизировать операции чтения/записи на диск, чтобы обеспечить высокую скорость извлечения информации. Модуль mmap позволяет работать с файлами на диске, как если бы они находились в оперативной памяти:

        import mmap

def print_specific_lines(filename, lines_to_print):
    with open(filename, "r+b") as f:
        mmapped_file = mmap.mmap(f.fileno(), 0)        
        lines = mmapped_file.read().decode('utf-8').splitlines()
        
        for i in range(99, len(lines), 100):
            if len(lines[i]) != 0:
                print(f"Строка {i + 1}: {lines[i]}")
        

        for i in range(499, len(lines), 500):
            if len(lines[i]) != 0:
                print(f"Строка {i + 1}: {lines[i]}")
        
        mmapped_file.close()

print_specific_lines("alice.txt", [100, 500])


    

Результат — вывод каждой 100-й и каждой 500-й непустой строки файла:

        Строка 400: being drowned in my own tears! That _will_ be a queer thing, to be
Строка 500: “Ahem!” said the Mouse with an important air, “are you all ready? This
Строка 700: little door, had vanished completely.
Строка 1000: good reason, and as the Caterpillar seemed to be in a _very_ unpleasant
Строка 1100: rearing itself upright as it spoke (it was exactly three inches high).
Строка 1200: “I—I’m a little girl,” said Alice, rather doubtfully, as she remembered
Строка 1500: a Hatter: and in _that_ direction,” waving the other paw, “lives a
Строка 1600: “Then it wasn’t very civil of you to offer it,” said Alice angrily.
Строка 2000: their faces, and the pattern on their backs was the same as the rest of
Строка 2200: the question, and they repeated their arguments to her, though, as they
Строка 2400: she was out of sight: then it chuckled. “What fun!” said the Gryphon,
Строка 2600: “Back to land again, and that’s all the first figure,” said the Mock
Строка 2700: “They were obliged to have him with them,” the Mock Turtle said: “no
Строка 2900: and she could even make out that one of them didn’t know how to spell
Строка 3000: “I’m a poor man, your Majesty,” the Hatter began, in a trembling voice,
Строка 3200: “What’s in it?” said the Queen.
Строка 3300: “All right, so far,” said the King, and he went on muttering over the
Строка 3400: their simple joys, remembering her own child-life, and the happy summer
Строка 3500: 1.E.1. The following sentence, with active links to, or other
Строка 3700: Section 4. Information about Donations to the Project Gutenberg
Строка 500: “Ahem!” said the Mouse with an important air, “are you all ready? This
Строка 1000: good reason, and as the Caterpillar seemed to be in a _very_ unpleasant
Строка 1500: a Hatter: and in _that_ direction,” waving the other paw, “lives a
Строка 2000: their faces, and the pattern on their backs was the same as the rest of
Строка 3000: “I’m a poor man, your Majesty,” the Hatter began, in a trembling voice,
Строка 3500: 1.E.1. The following sentence, with active links to, or other

    

Экономия памяти с array.array

Бытует мнение, что array.array работает значительно быстрее, чем обычный list. Это не так: в современных версиях Python список list максимально оптимизирован, и по производительности превосходит array.array в ходе выполнения абсолютного большинста операций — как видно по приведенному ниже примеру, list уступает только в случае с сериализацией. Но вот памяти массивы array.array действительно используют меньше — почти в два раза:

        import array
import time
import sys

arr = array.array('i', range(1000000))
lst = list(range(1000000))

# Сравнение размера в памяти
print(f"Размер array.array: {sys.getsizeof(arr)} байт")
print(f"Размер list: {sys.getsizeof(lst)} байт")

# Сравнение скорости доступа
def access_test(container):
    start = time.time()
    for _ in range(1000000):
        _ = container[500000]
    return time.time() - start

print(f"Время доступа для array.array: {access_test(arr):.6f} сек")
print(f"Время доступа для list: {access_test(lst):.6f} сек")

# Сравнение скорости числовых операций
def numeric_operation_test(container):
    start = time.time()
    for i in range(len(container)):
        container[i] *= 2
    return time.time() - start

arr_copy = array.array('i', arr)
lst_copy = list(lst)

print(f"Время числовых операций для array.array: {numeric_operation_test(arr_copy):.6f} сек")
print(f"Время числовых операций для list: {numeric_operation_test(lst_copy):.6f} сек")

# Сериализация
import pickle

start = time.time()
pickle.dumps(arr)
print(f"Время сериализации array.array: {time.time() - start:.6f} сек")

start = time.time()
pickle.dumps(lst)
print(f"Время сериализации list: {time.time() - start:.6f} сек")
    

Результат:

        Размер array.array: 4091948 байт
Размер list: 8000056 байт
Время доступа для array.array: 0.230873 сек
Время доступа для list: 0.112998 сек
Время числовых операций для array.array: 0.622823 сек
Время числовых операций для list: 0.385629 сек
Время сериализации array.array: 0.008033 сек
Время сериализации list: 0.030163 сек

    
🐍 Библиотека питониста
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста»
🐍🎓 Библиотека Python для собеса
Подтянуть свои знания по Python вы можете на нашем телеграм-канале «Библиотека Python для собеса»
🐍🧩 Библиотека задач по Python
Интересные задачи по Python для практики можно найти на нашем телеграм-канале «Библиотека задач по Python»

Внутренние функции

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

        import string
import logging

# Настройка логирования
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# Внутренняя функция для очистки строки от знаков препинания
def _clean_string(input_string):
    translator = str.maketrans('', '', string.punctuation)
    cleaned_string = input_string.translate(translator)
    return cleaned_string

def count_characters(input_string):
    logger.info(f"Вызвана функция count_characters с аргументом: {input_string}")
    try:
        cleaned_string = _clean_string(input_string)
        result = len(cleaned_string)
        logger.info(f"Функция count_characters вернула результат: {result}")
        return result
    except Exception as e:
        logger.error(f"Ошибка в функции count_characters: {e}")
        return 0  

def count_words(input_string):
    logger.info(f"Вызвана функция count_words с аргументом: {input_string}")
    try:
        cleaned_string = _clean_string(input_string)
        result = len(cleaned_string.split())
        logger.info(f"Функция count_words вернула результат: {result}")
        return result
    except Exception as e:
        logger.error(f"Ошибка в функции count_words: {e}")
        return 0  

def reverse_string(input_string):
    logger.info(f"Вызвана функция reverse_string с аргументом: {input_string}")
    try:
        cleaned_string = _clean_string(input_string)
        result = cleaned_string[::-1]
        logger.info(f"Функция reverse_string вернула результат: {result}")
        return result
    except Exception as e:
        logger.error(f"Ошибка в функции reverse_string: {e}")
        return ""  


input_data = input()
print("Оригинальная строка:", input_data)
print("Количество символов:", count_characters(input_data))
print("Количество слов:", count_words(input_data))
print("Строка в обратном порядке:", reverse_string(input_data))


    

Результат:

        Оригинальная строка: Привет, мир!!! Это - текстовая строка (для демонстрации).
2024-08-28 21:12:09,353 - INFO - Вызвана функция count_characters с аргументом: Привет, мир!!! Это - текстовая строка (для демонстрации).
2024-08-28 21:12:09,353 - INFO - Функция count_characters вернула результат: 49
Количество символов: 49
2024-08-28 21:12:09,353 - INFO - Вызвана функция count_words с аргументом: Привет, мир!!! Это - текстовая строка (для демонстрации).
2024-08-28 21:12:09,353 - INFO - Функция count_words вернула результат: 7
Количество слов: 7
2024-08-28 21:12:09,353 - INFO - Вызвана функция reverse_string с аргументом: Привет, мир!!! Это - текстовая строка (для демонстрации).
2024-08-28 21:12:09,353 - INFO - Функция reverse_string вернула результат: иицартсномед ялд акортс яавотскет  отЭ рим тевирП
Строка в обратном порядке: иицартсномед ялд акортс яавотскет  отЭ рим тевирП
    

Декораторы

Декораторы позволяют расширить поведение функции, не изменяя ее исходный код:

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

Декораторы часто используют для:

  • Логирования — добавляют записи о вызовах функции, ее аргументах и результатах.
  • Кэширования — сохраняют промежуточные результаты работы функции, чтобы избежать повторных вычислений.
  • Проверки прав пользователя перед выполнением функции.
  • Измерения времени выполнения функции.

Этот пример имитирует запросы к внешнему API и показывает, как можно использовать декораторы для кэширования данных и измерения времени выполнения:

        import time
from functools import lru_cache, wraps
import random

# Декоратор для измерения времени выполнения
def measure_time(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} выполнена за {end - start:.2f} сек")
        return result
    return wrapper

# Имитация внешнего API с задержкой
def slow_api_request(user_id):
    time.sleep(2)
    return {
        "id": user_id,
        "name": f"User {user_id}",
        "last_login": time.time(),
        "score": random.randint(1, 100)
    }

# Функция без кэширования
@measure_time
def get_user_data(user_id):
    print(f"Запрос данных для пользователя {user_id}")
    return slow_api_request(user_id)

# Функция с кэшированием
@measure_time
@lru_cache(maxsize=100)
def get_user_data_cached(user_id):
    print(f"Запрос данных для пользователя {user_id}")
    return slow_api_request(user_id)

def run_test():
    user_ids = [1, 2, 3, 1, 2, 1, 1, 3]  
    print("Без кэширования:")
    for user_id in user_ids:
        get_user_data(user_id)

    print("\nС кэшированием:")
    for user_id in user_ids:
        get_user_data_cached(user_id)

run_test()
    

Результат:

        Без кэширования:
Запрос данных для пользователя 1
get_user_data выполнена за 2.00 сек
Запрос данных для пользователя 2
get_user_data выполнена за 2.00 сек
Запрос данных для пользователя 3
get_user_data выполнена за 2.00 сек
Запрос данных для пользователя 1
get_user_data выполнена за 2.00 сек
Запрос данных для пользователя 2
get_user_data выполнена за 2.00 сек
Запрос данных для пользователя 1
get_user_data выполнена за 2.00 сек
Запрос данных для пользователя 1
get_user_data выполнена за 2.00 сек
Запрос данных для пользователя 3
get_user_data выполнена за 2.00 сек

С кэшированием:
Запрос данных для пользователя 1
get_user_data_cached выполнена за 2.00 сек
Запрос данных для пользователя 2
get_user_data_cached выполнена за 2.00 сек
Запрос данных для пользователя 3
get_user_data_cached выполнена за 2.00 сек
get_user_data_cached выполнена за 0.00 сек
get_user_data_cached выполнена за 0.00 сек
get_user_data_cached выполнена за 0.00 сек
get_user_data_cached выполнена за 0.00 сек
get_user_data_cached выполнена за 0.00 сек
    

Готовые библиотеки

Многие начинающие питонисты пренебрежительно относятся к использованию готовых библиотек, — считают это читерством. И совершенно зря: высокопроизводительные библиотеки (например, NumPy и pandas) написаны на C и оптимизированы для максимальной производительности на низком уровне: они эффективно используют память, ресурсы CPU, применяют векторизацию и т.д. В последнее время суперскоростные библиотеки для Python (polars, к примеру) пишут на Rust, у которого еще больше преимуществ, чем у C. Появляются альтернативы для NumPy и pandas, которые могут использовать возможности GPU (например, CuPy) . Всегда, когда это возможно, стоит пользоваться готовыми библиотеками, особенно для ресурсоемких вычислений — это гораздо эффективнее, чем стандартный Python:

        import numpy as np
import timeit

def generate_random_data(size):
    return np.random.rand(size)

# Функция для вычисления суммы квадратов с помощью NumPy
def sum_squares_numpy(arr):
    return np.sum(arr**2)

# Функция для вычисления суммы квадратов с помощью обычного Python-цикла
def sum_squares_python(arr):
    result = 0
    for num in arr:
        result += num**2
    return result

def test_performance():
    size = 5000000
    arr = generate_random_data(size)
    print(f"\nВычисление для массива размером {size}:")
    
    # Вычисляем время выполнения для NumPy
    start_time = timeit.default_timer()
    sum_squares_numpy(arr)
    numpy_time = timeit.default_timer() - start_time
    
    # Вычисляем время выполнения для Python
    start_time = timeit.default_timer()
    sum_squares_python(arr)
    python_time = timeit.default_timer() - start_time
    
    print(f"\nВремя выполнения с NumPy: {numpy_time:.6f} секунд")
    print(f"Время выполнения с Python: {python_time:.6f} секунд")

test_performance()
    

Результат:

        Вычисление для массива размером 5000000:

Время выполнения с NumPy: 0.085850 секунд
Время выполнения с Python: 1.128655 секунд
    

Короткое замыкание

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

  • year % 4 == 0 — это условие проверяется первым, так как оно отсеивает большинство не високосных лет. Если год не делится на 4, функция сразу возвращает False.
  • year % 100 != 0 — если год прошел первую проверку, мы проверяем, не делится ли он на 100. Это условие встречается реже, чем первое. Если год не делится на 100, он точно високосный, и мы можем вернуть True.
  • year % 400 == 0 — это самое редкое условие, большинство лет до него не доберутся.
        def is_leap_year(year):
    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
    

Освобождение памяти

Python автоматически управляет сборкой мусора — когда объекты выходят из зоны видимости (например, функция заканчивает работу), все ненужные переменные удаляются, высвобождая оперативную память. Но в приложениях, которые обрабатывают очень большие объемы данных, стоит удалять ненужные объекты сразу, принудительно вызывая сборщик мусора:

        import os
import psutil
import gc

def memory_usage():
    process = psutil.Process(os.getpid())
    return process.memory_info().rss / (1024 * 1024)  # в МБ

print(f"Использование памяти до: {memory_usage():.1f} МБ")

large_data = [i for i in range(10000000)]
print(f"Использование памяти после создания: {memory_usage():.1f} МБ")

del large_data
gc.collect()

print(f"Использование памяти после удаления: {memory_usage():.1f} МБ")
    

Результат:

        Использование памяти до: 20.3 МБ
Использование памяти после создания: 402.8 МБ
Использование памяти после удаления: 21.5 МБ
    

В этом примере del используется в сочетании с gc.collect() потому, что del уменьшает счетчик ссылок на объект: когда счетчик достигает нуля, объект становится доступным для сборщика мусора.

Короткие сообщения об ошибках

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

        try:
    result = 10 / 0
except ZeroDivisionError:
    print("Err: Div/0")
    

Векторизация вместо циклов и списковых включений

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

        import random
import time
import numpy as np

def generate_matrix(size):
    return [[random.randint(-100, 100) for _ in range(size)] for _ in range(size)]

def matrix_operations_with_list_comprehension(A, B):
    size = len(A)
    C = [[A[i][j] * B[j][i] for j in range(size)] for i in range(size)]
    
    D = [[sum(C[i][k] * B[k][j] for k in range(size)) for j in range(size)] 
         for i in range(size)]
    
    E = [[D[i][j] + D[j][i] for j in range(size)] for i in range(size)]
    
    return E

def matrix_operations_with_loops(A, B):
    size = len(A)
    C = [[0] * size for _ in range(size)]
    D = [[0] * size for _ in range(size)]
    E = [[0] * size for _ in range(size)]
    
    for i in range(size):
        for j in range(size):
            C[i][j] = A[i][j] * B[j][i]
    
    for i in range(size):
        for j in range(size):
            D[i][j] = 0
            for k in range(size):
                D[i][j] += C[i][k] * B[k][j]
    
    for i in range(size):
        for j in range(size):
            E[i][j] = D[i][j] + D[j][i]
    
    return E

def matrix_operations_with_numpy(A, B):
    A = np.array(A)
    B = np.array(B)
    C = A * B.T
    D = C @ B
    E = D + D.T
    return E.tolist()

def test_performance():
    size = 400
    print(f"\nВычисления для матриц размером {size}x{size}:")

    A = generate_matrix(size)
    B = generate_matrix(size)

    start_time = time.time()
    E_loop = matrix_operations_with_loops(A, B)
    loops_time = time.time() - start_time

    start_time = time.time()
    E_list = matrix_operations_with_list_comprehension(A, B)
    list_time = time.time() - start_time
    
    start_time = time.time()
    E_numpy = matrix_operations_with_numpy(A, B)
    numpy_time = time.time() - start_time

    print(f"\nВремя выполнения с обычными циклами: {loops_time:.6f} секунд")
    print(f"Время выполнения со списковыми включениями: {list_time:.6f} секунд")
    print(f"Время выполнения с NumPy: {numpy_time:.6f} секунд")

test_performance()

    

Результат:

        Вычисления для матриц размером 400x400:

Время выполнения с обычными циклами: 18.304441 секунд
Время выполнения со списковыми включениями: 15.638463 секунд
Время выполнения с NumPy: 0.134936 секунд
    

Подведем итоги

Python — интерпретируемый язык, и по этой причине он всегда будет уступать в скорости низкоуровневым компилируемым языкам типа Rust. Однако считать его безнадежно медленным тоже не стоит:

  • Как показывают приведенные выше примеры, использование памяти и сложные вычисления в Python-приложениях можно значительно оптимизировать.
  • Есть возможность совместного использования Python и Rust (PyO3).
  • Производительность самого Python постоянно повышается — версия 3.11 работала на 10-60% быстрее, чем 3.10; модуль asyncio в версии 3.12 получил 75% прирост скорости.

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

***

Вы уже видели, как важны оптимизация и производительность в Python. Но прежде чем погрузиться в сложные техники, важно освоить базовые навыки программирования. Курс «Основы программирования на Python» от Proglib Academy предлагает:

  • Пошаговое руководство для новичков
  • Практические задания для закрепления знаний
  • Введение в ключевые библиотеки и инструменты

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ