- BrainTools - https://www.braintools.ru -
Python часто начинают осваивать с простых скриптов и функций. Пока задачи простые, этого достаточно. Но когда в коде появляются сущности, например, пользователи, книги или машины, взаимодействие с ними строится по другим принципам.
Для этого в Python используют классы. С их помощью описывают, какие данные есть у таких сущностей (объектов) и что с ними можно делать. Это и есть объектно-ориентированный подход — программа строится вокруг объектов и их взаимодействия.
В этой статье мы разберём основы работы с классами и объектами в Python: как они устроены, как их использовать и какие концепции вокруг них стоит знать, даже если вы пока не планируете углубляться в архитектуру.
Python-разработчик в компании BST-Digital. Помогал с написанием статьи.
Класс — это описание свойств и действий для будущих объектов.
Объект (экземпляр класса) хранит данные и свойства, определённые в классе.
Класс можно представить как чертёж, а объект как дом по этому чертежу. Один и тот же чертёж позволяет построить много домов с разной отделкой и наполнением, но с общей структурой. Точно так же класс в Python задаёт общую структуру, а каждый объект хранит свои конкретные значения.
Пример на бытовом уровне: класс User описывает, какие данные есть у пользователя (имя, email) и какие действия он может выполнять. Конкретный пользователь «Катя» — это объект класса User со своим именем и своим email.
С точки зрения [1] Python:
класс — это объект специального типа type, созданный с помощью ключевого слова class;
объект класса создаётся, когда мы «вызываем» класс как функцию.
Давайте посмотрим, как это выглядит в коде.
Представим простую сущность — книгу. У книги есть данные (название и автор) и одно действие — вывести краткое описание. Всё это удобно собрать в одном месте, описав книгу с помощью класса.
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def describe(self):
print(f'"{self.title}", {self.author}')
Строка class Book: объявляет новый класс с именем Book. Всё, что написано ниже с отступом, относится к этому классу — так же, как тело функции относится к def.
Метод __init__ (магический метод, или dunder method — поясню дальше) вызывается автоматически при создании нового объекта. В нём задаётся начальное состояние объекта — данные, с которыми он будет работать, то есть происходит инициализация объекта.
Параметр self — это ссылка на конкретный объект, который создаётся в данный момент. Через него мы сохраняем данные внутрь объекта.
Метод describe описывает действие, которое можно выполнить с объектом книги.
Создание объекта класса выглядит так:
book = Book("Три товарища", "Эрих Мария Ремарк")
book.describe()
В этот момент Python создаёт объект класса Book, сохраняет в нём название и автора и позволяет вызывать его методы.
На этом этапе важно лишь увидеть общую идею: класс задаёт структуру, а конкретные данные и поведение [2] появляются у объекта. Теперь разберём эти элементы по отдельности.
В примере с книгой мы уже использовали атрибуты, даже если не называли их этим словом напрямую.
Когда внутри класса мы пишем self.title или self.author, мы сохраняем данные внутри объекта:
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
Здесь title и author — атрибуты объекта Book. У каждого экземпляра класса они свои:
book1 = Book("Три товарища", "Эрих Мария Ремарк")
book2 = Book("Мастер и Маргарита", "Михаил Булгаков")
print(book1.title) # Три товарища
print(book2.title) # Мастер и Маргарита
Хотя обе переменные book1 и book2 относятся к одному классу Book, данные внутри них разные. Это и есть основная идея: класс задаёт форму, а объект хранит конкретные значения.
Итог: всё, что записывается через self.имя, становится атрибутом объекта.
Атрибуты хранят данные объекта. Методы — это функции класса, которые работают с этими данными и определяют, какие действия может выполнять объект.
Мы уже видели метод describe в примере с книгой:
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def describe(self):
print(f'"{self.title}", {self.author}')
В методах экземпляра первым параметром передаётся объект, с данными которого метод работает. Обычно его называют self. Благодаря этому метод может обращаться к данным именно этого объекта.
Создадим книгу и вызовем метод:
book = Book("Три товарища", "Эрих Мария Ремарк")
book.describe()
Вызов book.describe() работает с данными, которые хранятся внутри Book. Если создать другой объект, метод останется тем же самым, а данные будут другими:
another_book = Book("Мастер и Маргарита", "Михаил Булгаков")
another_book.describe()
Важно, что код метода не меняется — меняется объект, с которым он работает. В этом и состоит смысл методов: одно и то же действие применяется к разным объектам одного класса, используя их собственные данные.
Коротко: атрибуты — это состояние объекта, методы — операции над этим состоянием.
До этого момента мы смотрели на классы как на способ объединять данные и действия. Но в реальном коде классы связаны друг с другом и взаимодействуют между собой.
Для описания таких связей используют три базовых принципа объектно-ориентированного программирования: наследование, инкапсуляцию и полиморфизм. Ниже разберём их на простых примерах.
Иногда в программе нужно работать с объектами, которые во многом похожи друг на друга. Например, у нас могут быть обычные книги и электронные книги. И те и другие имеют название и автора, но у электронной книги есть дополнительные свойства.
Сначала опишем обычную книгу — так же, как мы делали это раньше:
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def describe(self):
print(f'"{self.title}", {self.author}')
Этот класс описывает общую модель: у каждой книги есть название и автор, и у каждой можно вывести краткое описание.
Теперь представим, что нам нужна электронная книга. По сути, это всё та же книга, но с дополнительной информацией — например, с размером файла.
Чтобы не переписывать код заново, в Python можно создать новый класс на основе уже существующего:
class EBook(Book):
def __init__(self, title, author, file_size):
super().__init__(title, author)
self.file_size = file_size
Запись class EBook(Book): означает, что класс EBook создаётся на основе класса Book. Такой класс называется дочерним, а Book — базовым.
Внутри __init__ мы сначала вызываем super().__init__(title, author). Этой строкой задаём: «сначала выполни конструктор базового класса Book и задай общие для книги данные». После этого добавляем новое поле file_size, которое есть только у электронной книги.
Теперь можно создать объект электронной книги:
ebook = EBook("Три товарища", "Эрих Мария Ремарк", 5)
ebook.describe()
Хотя метод describe был объявлен в классе Book, его можно вызывать и у объекта класса EBook. Это работает потому, что электронная книга унаследовала поведение [3] обычной книги.
Наследование позволяет описывать общее поведение и данные в одном классе, а различия — выносить в отдельные классы, не дублируя код.
Инкапсуляция отвечает на вопрос, как именно можно работать с данными объекта.
В Python к атрибутам объекта можно обращаться напрямую — читать их и изменять:
book.title = "Новое название"
Язык это позволяет: в Python нет специальных ключевых слов private или protected, которые жёстко ограничивают доступ к данным. Если атрибут существует, к нему можно обратиться по имени.
На практике этого часто недостаточно. Например, у книги не может быть отрицательного количества страниц, и менять это значение нужно только по определённым правилам.
Поэтому в объектно-ориентированном коде принято отделять внешний интерфейс объекта от его внутреннего состояния.
Посмотрим на пример:
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def set_pages(self, pages):
if pages > 0:
self.pages = pages
Разберём этот код.
title и author — обычные атрибуты, с ними можно работать напрямую. _pages хранит внутреннее состояние объекта — количество страниц.
set_pages — метод, через который предполагается менять это значение. Внутри метода есть проверка корректности данных.
Объект используют так:
book = Book("Три товарища", "Эрих Мария Ремарк", 300)
book.set_pages(350)
Внешний код не меняет _pages. Он вызывает метод, а объект сам отвечает за корректность своего состояния.
Подчёркивание в имени атрибута
(_pages)используется, чтобы обозначить: это внутренняя часть объекта, и работать с ней предполагается через методы класса. Иногда используют два подчёркивания(__pages)— в этом случае Python дополнительно изменяет имя атрибута, чтобы его сложнее было использовать напрямую извне. Смысл остаётся тем же: отделить внутреннее устройство объекта от его внешнего интерфейса.
Мы уже видели, что разные объекты могут быть связаны между собой через наследование. Обычная книга и электронная книга — хороший пример: по сути, это всё ещё книги, но в деталях они могут вести себя по-разному.
При этом с точки зрения кода с ними удобно работать одинаково. Например, получать описание книги, не задумываясь о том, обычная она или электронная.
Сначала опишем обычную книгу:
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def describe(self):
print(f'"{self.title}", {self.author}')
Этот метод выводит базовое описание: название и автора.
Теперь создадим электронную книгу. Она по-прежнему остаётся книгой, но мы хотим, чтобы её описание выглядело иначе. Для этого достаточно переопределить метод describe:
class EBook(Book):
def describe(self):
print(f'Электронная книга: "{self.title}", {self.author}')
Мы не добавляем новых данных и не переписываем конструктор — меняем только поведение метода.
Создадим два объекта и вызовем у них один и тот же метод:
book = Book("Три товарища", "Эрих Мария Ремарк")
ebook = EBook("Мастер и Маргарита", "Михаил Булгаков")
book.describe()
ebook.describe()
Метод вызывается одинаково, но результат отличается, потому что объекты относятся к разным классам.
В этом суть полиморфизма: один и тот же метод используется одинаковым образом, но ведёт себя по-разному в зависимости от типа объекта.
В Python есть методы, которые мы обычно не вызываем напрямую. Их запускает сам интерпретатор — в определённых ситуациях, когда объект используется тем или иным образом.
Такие методы называют специальными. С ними мы уже сталкивались в этой статье: метод __init__, который выполняется при создании объекта, — один из них.
Эти методы также называют магическими — потому что со стороны кажется, будто они «срабатывают сами». На самом деле за этим стоит вполне понятная логика [4]. А в англоязычных источниках часто встречается название dunder-методы — от double underscore, так как имена этих методов начинаются и заканчиваются двумя подчёркиваниями.
Дальше разберём несколько самых распространённых специальных методов и посмотрим, как именно Python их использует.
__init__ вызывается в тот момент, когда мы создаём новый объект класса. Его задача — задать начальное состояние объекта: сохранить данные, с которыми он будет дальше работать.
Представим, что мы описываем персонажа в игре. У него есть имя и количество здоровья. Когда персонаж появляется, эти значения должны быть заданы сразу:
class Character:
def __init__(self, name, health):
self.name = name
self.health = health
Теперь создадим персонажа:
hero = Character("Archer", 100)
В этот момент Python автоматически вызывает метод __init__. Значения "Archer" и 100 передаются в параметры name и health, а затем сохраняются внутри объекта в атрибутах self.name и self.health.
После этого объект hero уже содержит данные:
print(hero.name) # Archer
print(hero.health) # 100
Каждый раз, когда создаётся новый объект Character, метод __init__ выполняется заново и заполняет данные именно этого объекта, которые были переданы.
После создания объекта часто возникает вопрос: как понять, что именно в нём хранится. Например, когда мы выводим объек�� в консоль или просто хотим быстро посмотреть его состояние.
Возьмём уже знакомый пример с персонажем, у которого есть имя и показатель здоровья. Если сейчас попробовать вывести объект через print (hero), Python выдаст техническую информацию о типе объекта и его адресе в памяти [5], вроде:
<__main__.Character object at 0x10c8f3d90>
Чтобы задать осмысленное текстовое представление объекта, в классе используют специальный метод __str__.
Добавим его в наш класс:
class Character:
def __init__(self, name, health):
self.name = name
self.health = health
def __str__(self):
return f"{self.name} (здоровье: {self.health})"
Здесь мы описали, какую строку должен вернуть объект, когда Python пытается представить его в текстовом виде.
Теперь, если снова выполнить print (hero), мы получим результат:
Archer (здоровье: 100)
Таким образом, метод __str__ позволяет один раз описать, как объект должен выглядеть в тексте, и после этого Python будет использовать это описание автоматически — в print, логах и других похожих ситуациях.
Иногда рядом с
__str__можно встретить метод__repr__. Он тоже возвращает строку, но используется в других ситуациях.
__str__отвечает за то, как объект выглядит при обычном выводе — например, когда мы используем
__repr__используется, когда Python показывает объект в служебных контекстах: в отладке, в интерактивной консоли или внутри коллекций.Если метод
__str__не определён, Python использует__repr__вместо него.На практике
__str__делают коротким и удобным для чтения, а__repr__— более точным описанием объекта.
В Python есть встроенная функция len(), которая возвращает длину строки, списка или словаря. Она может работать и с объектами пользовательских классов, если мы явно зададим это поведение.
По умолчанию Python не знает, что считать длиной такого объекта, поэтому для этого в классе определяют специальный метод __len__.
Допустим, у персонажа есть инвентарь:
class Character:
def __init__(self, name, items):
self.name = name
self.items = items
Логично ожидать, что len(hero) будет возвращать количество предметов в инвентаре. Чтобы это работало, добавим метод __len__:
class Character:
def __init__(self, name, items):
self.name = name
self.items = items
def __len__(self):
return len(self.items)
Метод __len__ должен возвращать целое число. Здесь мы просто используем длину списка items.
Теперь вызов len() работает:
hero = Character("Archer", ["лук", "стрелы", "зелье"])
print(len(hero)) # 3
После __len__ логично перейти к сравнению объектов — потому что это следующий типичный сценарий взаимодействия с ними.
Иногда нам нужно понять, равны ли два объекта. Например, представим, что у двух персонажей в игре одинаковые характеристики, и мы хотим это проверить.
Если сравнить их напрямую через hero1 == hero2, Python по умолчанию считает объекты разными, даже если данные внутри одинаковые. Он сравнивает не содержимое, а то, являются ли это одни и те же объекты в памяти.
Чтобы объяснить Python, по каким признакам объекты считаются равными, в классе определяют специальн��й метод __eq__.
Например, описываем, что два персонажа считаются равными, если у них совпадают имя и здоровье:
class Character:
def __init__(self, name, health):
self.name = name
self.health = health
def __eq__(self, other):
return (
self.name == other.name and
self.health == other.health
)
Теперь сравнение будет корректным:
hero1 = Character("Archer", 100)
hero2 = Character("Archer", 100)
print(hero1 == hero2) # True
Таким образом, можем проверять равенство не «по месту в памяти», а по важным для нас характеристикам.
Мы посмотрели на самые распространённые специальные методы, но в Python их десятки — каждый отвечает за отдельный сценарий взаимодействия с объектами.
Вот ещё несколько, с которыми вы наверняка столкнётесь:
__iter__ — позволяет проходить по объекту в цикле for;
__getitem__ — отвечает за доступ к элементам по индексу (obj[i]);
__enter__ и __exit__ — позволяют объекту работать с конструкцией with (например, как файл);
__add__, __sub__, __mul__ — переопределяют арифметические операции между объектами.
Полный список специальных методов есть в официальной документации [6].
Например, если вы делаете класс, который должен вести себя как коллекция, смотрите раздел Emulating container types. Для поддержки арифметики и операторов сравнения — Emulating numeric types.
Мы разобрались, как устроены классы, какие данные они хранят и как объекты ведут себя в типичных для Python ситуациях. Этого уже достаточно, чтобы использовать классы в прикладном коде.
Дальше имеет смысл хотя бы обозначить несколько инструментов, которые часто встречаются в реальных проектах. Мы не будем разбирать их подробно — это отдельные темы, каждая из которых тянет на собственную статью. Здесь важно другое: знать, что такие возможности существуют и зачем они вообще нужны, чтобы узнавать их в коде и понимать общую картину.
Ниже — краткий обзор двух таких механизмов: абстрактных классов и классов данных.
При проектировании программы часто нужно не просто описать отдельные объекты, а задать общие правила для группы похожих классов.
Например, в игре может быть много разных персонажей — лучник, маг, воин. Они отличаются, но у всех есть базовые свойства: имя, здоровье и умение атаковать.
Мы можем создать общий класс Character, чтобы описать, какие данные и методы должны быть у всех персонажей. Но что, если этот класс сам по себе не должен существовать? Нам нужен шаблон, от которого будут наследоваться другие, но который нельзя использовать напрямую.
Для этого в Python есть абстрактные классы.
Они задаются с помощью модуля abc (от abstract base class — «абстрактный базовый класс»). В нём есть специальный декоратор @abstractmethod, которым отмечают методы, обязательные для переопределения в наследниках.
Как это выглядит:
from abc import ABC, abstractmethod
class Character(ABC):
def __init__(self, name, health):
self.name = name
self.health = health
@abstractmethod
def attack(self):
pass
Теперь, если попробовать создать Character напрямую, Python не позволит:
hero = Character("Archer", ["лук", "стрелы", "зелье"])
print(len(hero)) # 3
Но если мы создаём наследника, который реализует метод attack, всё работает:
class Archer(Character):
def attack(self):
print(f"{self.name} стреляет из лука!")
hero = Archer("Archer", 100)
hero.attack()
# Archer стреляет из лука!
Теперь мы получим такой результат:
hero = Character("Archer", ["лук", "стрелы", "зелье"])
print(len(hero)) # 3
Итог: с помощью абстрактных классов можно заранее задать, что обязательно должно быть в будущих классах, и не бояться, что кто-то забудет про нужный метод. Такой подход помогает держать код в порядке, особенно когда проект растёт.
Бывает, нам нужен класс, который просто хранит данные — без сложной логики и десятков методов.
Например, в игре может быть множество предметов: зелья, оружие, доспехи. У каждого есть несколько свойств: название, цена, вес.
Такой класс обычно состоит только из конструктора и пары атрибутов:
class Item:
def __init__(self, name, price, weight):
self.name = name
self.price = price
self.weight = weight
Проблема в том, что подобный шаблон приходится повторять [7] снова и снова. И чтобы этого избежать, в Python добавили декоратор @dataclass — он создаёт стандартные методы автоматически.
С его помощью тот же класс можно записать так:
from dataclasses import dataclass
@dataclass
class Item:
name: str
price: int
weight: float
Теперь Python сам создаёт конструктор (__init__), методы сравнения (__eq__), представление в виде строки (__repr__) и другие полезные детали.
Класс становится короче, но полностью рабочим:
sword = Item("Меч", 1200, 3.5)
potion = Item("Зелье", 250, 0.5)
print(sword)
# Item(name='Меч', price=1200, weight=3.5)
print(sword == potion)
# False
Итог: dataclass удобен, когда нужно хранить много однотипных данных. Вместо длинных повторяющихся конструкторов вы описываете только поля — остальное Python делает сам.
Попробуем посмотреть на классы не как на синтаксис Python, а как на способ описывать реальные вещи. Возьмём простую и знакомую ситуацию — поездку на поезде.
У нас есть поезд, вагоны и пассажиры. Каждый из этих объектов что-то хранит и что-то умеет делать.
Начнём с самого простого — пассажира:
class Passenger:
def __init__(self, name, seat):
self.name = name
self.seat = seat
Здесь мы просто фиксируем данные. Каждый объект Passenger — это конкретный человек с конкретным местом.
Создадим пассажира:
p1 = Passenger("Анна", 12)
Теперь у объекта p1 внутри есть свои данные:
print(p1.name) # Анна
print(p1.seat) # 12
Теперь добавим вагон.
Вагон — это не просто набор данных. Он содержит пассажиров и должен уметь с ними работать.
class Carriage:
def __init__(self, number):
self.number = number
self.passengers = []
Мы явно храним список пассажиров внутри вагона. Пока вагон пустой.
Добавим метод, который сажает пассажира в вагон:
class Carriage:
def __init__(self, number):
self.number = number
self.passengers = []
def add_passenger(self, passenger):
self.passengers.append(passenger)
Посадим пассажира в вагон:
carriage = Carriage(5)
carriage.add_passenger(p1)
Теперь пассажир стал частью вагона. Это важная идея ООП: объекты могут содержать другие объекты.
Чтобы понять, что происходит внутри вагона, добавим удобный вывод:
class Carriage:
def __init__(self, number):
self.number = number
self.passengers = []
def add_passenger(self, passenger):
self.passengers.append(passenger)
def __str__(self):
return f"Вагон {self.number}, пассажиров: {len(self.passengers)}"
Теперь Python сам знает, как показывать объект:
print(carriage)
# Вагон 5, пассажиров: 1
На этом этапе у нас есть простая, но рабочая модель. Эту структуру можно расширять — добавлять новые классы и правила, не переписывая уже существующий код.
В работе с классами важно не столько знание синтаксиса, сколько понимание того, зачем они вообще нужны. Классы позволяют описывать программу не как последовательность шагов, а как модель предметной области (по сути мир программы) — с сущностями, их состоянием и правилами взаимодействия.
Именно с этой точки зрения имеет смысл смотреть на ООП в Python: как на способ структурировать код так, чтобы его было проще расширять, читать и поддерживать по мере роста проекта. Поэтому дальше стоит изучать не новые приёмы сами по себе, а то, как классы применяют в реальных системах.
Чтобы расти, нужно выйти из привычной зоны и сделать шаг к переменам. Можно изучить новое, начав с бесплатных занятий:
мастер-класса «Искусственный интеллект в разработке [8]»;
записи открытой встречи «Старт в профессии инженера данных [9]» совместно с НИУ ВШЭ;
курса «1С‑программист: первые шаги в профессию [10]»;
вебинара «Атаки на сетевое оборудование: как нейтрализовать угрозы [11]» совместно с НИУ ВШЭ;
курса «Бизнес-аналитик: первые шаги в профессии [12]».
Или можно стать востребованным сотрудником и открыть открыть бóльшие перспективы в карьере с профессиональным обучением [13]:
на курсе «Fullstack-разработчик на Python [14]» с программой трудоустройства и изучением ИИ;
на практическом курсе «Вайб-кодинг [15]»;
на программе «Нейросети для бизнеса и управленцев [16]»;
на онлайн-курсе «Аналитика данных с МФТИ [17]»;
на проекте по повышению квалификации «Руководитель проектов в области искусственного интеллекта [18]» при участии МФТИ.
Автор: kirakirap
Источник [19]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/23586
URLs in this post:
[1] зрения: http://www.braintools.ru/article/6238
[2] поведение: http://www.braintools.ru/article/9372
[3] поведение: http://www.braintools.ru/article/5593
[4] логика: http://www.braintools.ru/article/7640
[5] памяти: http://www.braintools.ru/article/4140
[6] официальной документации: https://docs.python.org/3/reference/datamodel.html
[7] повторять: http://www.braintools.ru/article/4012
[8] Искусственный интеллект в разработке: https://netology.ru/free-lessons/ai-protiv-spama?utm_source=habr&utm_medium=externalblog&utm_campaign=bhe_ai-protiv-spama_oz_habr_brand_article-23122025_media
[9] Старт в профессии инженера данных: https://netology.ru/free-lessons/razvitie-karery-inzhenera-dannyh?utm_source=habr&utm_medium=externalblog&utm_campaign=bhe_razvitie-karery-inzhenera-dannyh_oz_habr_brand_article-23122025_media
[10] 1С‑программист: первые шаги в профессию: https://netology.ru/programs/1c-programmist-pervie-shagi-v-professiu?utm_source=habr&utm_medium=externalblog&utm_campaign=re_it_onecfree_bou_habr_brand_article-23122025_media
[11] Атаки на сетевое оборудование: как нейтрализовать угрозы: https://netology.ru/free-lessons/oz-vshe-pfsr?utm_source=habr&utm_medium=externalblog&utm_campaign=bhe_oz-vshe-pfsr_oz_habr_brand_article-23122025_media
[12] Бизнес-аналитик: первые шаги в профессии: https://netology.ru/programs/business-analytics-free?utm_source=habr&utm_medium=externalblog&utm_campaign=re_it_banfree_bou_habr_brand_article-23122025_media
[13] обучением: http://www.braintools.ru/article/5125
[14] Fullstack-разработчик на Python: https://netology.ru/programs/fullstack-python-dev?utm_source=habr&utm_medium=externalblog&utm_campaign=re_it_fpy_ou_habr_brand_article-23122025_media
[15] Вайб-кодинг: https://netology.ru/programs/ai-coding?utm_source=habr&utm_medium=externalblog&utm_campaign=up_it_aivibe_ou_habr_brand_article-23122025_media
[16] Нейросети для бизнеса и управленцев: https://netology.ru/programs/ai-manager?utm_source=habr&utm_medium=externalblog&utm_campaign=up_bip_aiman_ou_habr_brand_article-23122025_media
[17] Аналитика данных с МФТИ: https://netology.ru/programs/professiya-analitik-dannyh-mfti?utm_source=habr&utm_medium=externalblog&utm_campaign=bhe_bhelds_ou_habr_brand_article-23122025_media
[18] Руководитель проектов в области искусственного интеллекта: https://netology.ru/programs/rukovoditel-proektov-v-oblasti-iskusstvennogo-intellekta-dpo-mfti?utm_source=habr&utm_medium=externalblog&utm_campaign=bhe_bhelpmii_ou_habr_brand_article-23122025_media
[19] Источник: https://habr.com/ru/companies/netologyru/articles/979196/?utm_source=habrahabr&utm_medium=rss&utm_campaign=979196
Нажмите здесь для печати.