Хочешь уверенно проходить IT-интервью?

Мы понимаем, как сложно подготовиться: стресс, алгоритмы, вопросы, от которых голова идёт кругом. Но с AI тренажёром всё гораздо проще.
💡 Почему Т1 тренажёр — это мастхэв?
- Получишь настоящую обратную связь: где затык, что подтянуть и как стать лучше
- Научишься не только решать задачи, но и объяснять своё решение так, чтобы интервьюер сказал: "Вау!".
- Освоишь все этапы собеседования, от вопросов по алгоритмам до диалога о твоих целях.
Зачем листать миллион туториалов? Просто зайди в Т1 тренажёр, потренируйся и уверенно удиви интервьюеров. Мы не обещаем лёгкой прогулки, но обещаем, что будешь готов!
Реклама. ООО «Смарт Гико», ИНН 7743264341. Erid 2VtzqwP8vqy
Упаковка переменных
Упаковка переменных — это процесс минимизации использования памяти за счет объединения нескольких элементов данных в одну структуру. Эта техника особенно важна в сценариях, где время доступа к памяти сильно влияет на производительность, например, при обработке больших объемов связанных данных: упаковка позволяет значительно ускорить процесс с помощью эффективного использования кэша процессора.
В 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 можно разделять функции на внутренние и публичные для оптимизации производительности и удобства использования. Внутренние функции обычно используются только внутри модуля, где они определены, и оптимизированы для быстроты и эффективности. Публичные функции, напротив, предназначены для внешнего использования и могут включать дополнительную проверку данных, логирование и другие механизмы, обеспечивающие удобство и безопасность использования:
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 предлагает:
- Пошаговое руководство для новичков
- Практические задания для закрепления знаний
- Введение в ключевые библиотеки и инструменты
Комментарии