<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 19:57:41 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="Dugc1xvo4F3oVYJeQIoqd-U3xS0XxcngSpTSMRCIcEPhOdfg6ZZNPV4WpsZMhdoAJT7ohx_yN0L3dEhlQo-XLQ";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Точки | Python: Составные данные</title>
<meta name="description" content="Точки / Python: Составные данные: Начнем проектировать примитивную графическую библиотеку с создания специальных данных, которые будут моделировать точки на плоскости">
<link rel="canonical" href="https://ru.hexlet.io/courses/python-compound-data/lessons/points/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Точки">
<meta property="og:title" content="Python: Составные данные">
<meta property="og:description" content="Точки / Python: Составные данные: Начнем проектировать примитивную графическую библиотеку с создания специальных данных, которые будут моделировать точки на плоскости">
<meta property="og:url" content="https://ru.hexlet.io/courses/python-compound-data/lessons/points/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="Jou_h_31jRwyG_a6EgQj5lLSXk42A62uGCm4TVQJ6ffJWnSwD4sgfIRY0iIeC9ORkttz5D40UwylySIZBg4OmQ" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc1OCwicHVyIjoiYmxvYl9pZCJ9fQ==--023ea18f500b1c4c91617fa96bbc52df8395da39/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Software%20engineer-bro.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T19:57:41.807Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"CzgWiKqGyk0btCgSFfQjnPYcGdDJ4VBAsgKpQSx5FRDk6d2_WPhnLa33DIoZ-9PrNhU0esHWruIP4jMVfn7yfg","topics":[{"id":33147,"title":"Добрый день! В начиная с 9-го вопроса у вас синтаксические ошибки в конструкторе. Вместо mew_point, должно быть new_point. Данное название конструктора вводит в заблуждение, если перевести на русский, то получается мяу_точка.\n\n mew_point = make_point(get_x(point1), get_y(point2))\n\n\n","plain_title":"Добрый день! В 9 вопросе у вас синтаксическая ошибка в конструкторе. Вместо mewpoint, должно быть newpoint. mew_point = make_point(get_x(point1), get_y(point2)) ","creator":{"public_name":"Алексей Данильченко","id":227505,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":72245,"body":"Алексей, здравствуйте! Ошибки я поправил. Спасибо!","topic_id":33147}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":36737,"title":"Так и не смог побороть ошибку \n\n```\nWPS334 Found reversed complex compare\n```","plain_title":"Так и не смог побороть ошибку WPS334 Found reversed complex compare ","creator":{"public_name":"Денис Новиков","id":79758,"is_tutor":false},"comments":[{"creator":{"public_name":"Денис Новиков","id":79758,"is_tutor":false},"id":80214,"body":"**Aleksei Pirogov**, Спасибо, да. в доке у них тоже это указано, мог и сам посмотреть)\nА вообще есть какая либо лучшая практика, когда надо сделать много elif? например больше 5. Много return? ","topic_id":36737},{"creator":{"public_name":"Aleksei Pirogov","id":72206,"is_tutor":true},"id":80257,"body":"Хороший линтер сам поругает, когда в `if-elif` станет слишком много веток и когда в функции будет слишком много `return`: линтеры для того и нужны, чтобы не заставлять программиста считать количество `return` :)\n\nУ нас в практиках в качестве линтера используется [Wemake Python Styleguie](https://wemake-python-stylegui.de). Он очень суров, но справедлив :)","topic_id":36737},{"creator":{"public_name":"Aleksei Pirogov","id":72206,"is_tutor":true},"id":80210,"body":"У вас используется сравнение вида `a > b > c`. Но такой порядок сравнения для человека часто контринтуитивен, вот линтер и жалуется на это. Расположите сравниваемые значения в порядке возрастания: `c < b < a`.","topic_id":36737}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":33528,"title":"Почему линтер здесь ругается на конструкцию elif `Found implicit complex compare elif`\n\nОстальные предупреждения линтера, я так понимаю к решению не относятся?\n\n[моё решение](https://ru.hexlet.io/code_reviews/164239)","plain_title":"Почему линтер здесь ругается на конструкцию elif Found implicit complex compare elif Остальные предупреждения линтера, я так понимаю к решению не относятся? моё решение (https://ru.hexlet.io/code_reviews/164239) ","creator":{"public_name":"Vyacheslav Kotov","id":156120,"is_tutor":false},"comments":[{"creator":{"public_name":"Vyacheslav Kotov","id":156120,"is_tutor":false},"id":73263,"body":"**Сергей К.**, спасибо, немного прояснило.\n\nПолучается на решение учителя линтер тоже ругается или в подобных случаях этим можно принебречь? ","topic_id":33528},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":73293,"body":"Решение учителя я также отрефакторил. Чтобы его увидеть, нужно обновить упражнение с помощью кнопки Сброс.","topic_id":33528},{"creator":{"public_name":"Vyacheslav Kotov","id":156120,"is_tutor":false},"id":73295,"body":"**Сергей К.**, спасибо, теперь стало понятно что к чему. Единственное, пока несколько непривычно от такой расстановки значений.","topic_id":33528},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":73302,"body":"В Python составные сравнения принято так писать.","topic_id":33528},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":73182,"body":"Добрый день! Посмотрите описание ошибки [здесь](https://github.com/wemake-services/wemake-python-styleguide/blob/526d9e039683bc0fded40ffbd67d6e4e1fd01092/wemake_python_styleguide/violations/consistency.py#L1320). Думаю, станет всё понятно.\n\nОстальной код я поправил.","topic_id":33528}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":60166,"title":"Что не так? Я не понимаю вывода тестов. Я не понимаю, зачем модуль math.\nhttps://ru.hexlet.io/code_reviews/483520","plain_title":"Что не так? Я не понимаю вывода тестов. Я не понимаю, зачем модуль math. https://ru.hexlet.io/code_reviews/483520 ","creator":{"public_name":"Юрий Данилов","id":48757,"is_tutor":false},"comments":[{"creator":{"public_name":"Юрий Данилов","id":48757,"is_tutor":false},"id":126979,"body":"**Юрий Данилов**, Блин, не посмотрел, что на get_quadrant список функций не заканчивается, надо же так опростоволоситься.","topic_id":60166},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":127006,"body":"Это нормально :) Главное – справились!","topic_id":60166}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":36540,"title":"Второй пример из Readme:\n```\n>>> point2 = points.make(1, -5)\n>>> get_quadrant(point2)\n 4\n```\nИмелось ввиду наверное points.make(1, **-5**)\n\nТак же отнюдь не тривиально, что модуль points содержит метод make() - а не make_point() допустим. Было ли где то это описано и я упустил сей момент? Или это такая изюминка задания?)","plain_title":"Второй пример из Readme: ``` point2 = points.make(1, -5) get_quadrant(point2) 4 ``` Имелось ввиду наверное points.make(1, -5) Так же отнюдь не тривиально, что модуль points содержит метод make() - а не make_point() допустим. Может были ли где то это описано и я упустил? Или это такая изюминка задания?) ","creator":{"public_name":"Vladislav Sadretdinov","id":182771,"is_tutor":false},"comments":[{"creator":{"public_name":"Aleksei Pirogov","id":72206,"is_tutor":true},"id":79924,"body":"Пример поправили. В коде я сделал комментарий, сообщающий, какие функции можно в модуле `points` взять.\n\nА `make`, раз уж речь о ней, так называется, чтобы не было лишнего дублирования при квалифицированном импорте: `points.make` вместо `points.make_point`.","topic_id":36540}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":33159,"title":"Добрый день ! \nПрошу помочь, не получается пройти тест. Не знаю можно ли сюда писать код, если понадобится, то напишу.: \n```\nmake: Entering directory '/usr/src/app'\nenv PYTHONPATH=./vendor:src pytest src tests\n.F. [100%]\n=================================== FAILURES ===================================\n__________________________ test_get_symmetrical_point __________________________\n\n def test_get_symmetrical_point():\n for coords, expected_coords in [\n [(10, 10), (-10, -10)],\n [(-10, -10), (10, 10)],\n [(10, -10), (-10, 10)],\n ]:\n symmetrical_point = hexlet.points.to_string(\n> points.get_symmetrical_point(hexlet.points.make(*coords))\n )\n\ntests/test_points.py:28: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n/usr/local/lib/python3.6/dist-packages/hexlet/points/__init__.py:33: in to_string\n return repr((get_x(point), get_y(point)))\n/usr/local/lib/python3.6/dist-packages/hexlet/points/__init__.py:23: in get_x\n return pairs.car(point)\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\npair = (-10, -10)\n\n def car(pair: Pair) -> A:\n \"\"\"Return a first element of pair.\"\"\"\n> return pair(True)\nE TypeError: 'tuple' object is not callable\n\n/usr/local/lib/python3.6/dist-packages/hexlet/pairs/__init__.py:27: TypeError\n=========================== short test summary info ============================\nFAIL tests/test_points.py::test_get_symmetrical_point\n1 failed, 2 passed in 0.06 seconds\nMakefile:2: recipe for target 'test' failed\nmake: Leaving directory '/usr/src/app'\nmake: *** [test] Error 1\n```","plain_title":"Добрый день ! Прошу помочь, не получается пройти тест. Не знаю можно ли сюда писать код, если понадобится, то напишу.: ``` make: Entering directory '/usr/src/app' env PYTHONPATH=./vendor:src pytest src tests .F. [100%] =================================== FAILURES =================================== __________________________ testgetsymmetricalpoint _________________________ def test_get_symmetrical_point(): for coords, expected_coords in [ [(10, 10), (-10, -10)], [(-10, -10), (10, 10)], [(10, -10), (-10, 10)], ]: symmetrical_point = hexlet.points.to_string( points.get_symmetrical_point(hexlet.points.make(*coords)) ) tests/test_points.py:28: /usr/local/lib/python3.6/dist-packages/hexlet/points/init.py:33: in tostring return repr((getx(point), gety(point))) /usr/local/lib/python3.6/dist-packages/hexlet/points/init.py:23: in getx return pairs.car(point) pair = (-10, -10) def car(pair: Pair) -> A: \"\"\"Return a first element of pair.\"\"\" return pair(True) E TypeError: 'tuple' object is not callable /usr/local/lib/python3.6/dist-packages/hexlet/pairs/init.py:27: TypeError =========================== short test summary info ============================ FAIL tests/testpoints.py::testgetsymmetricalpoint 1 failed, 2 passed in 0.06 seconds Makefile:2: recipe for target 'test' failed make: Leaving directory '/usr/src/app' make: *** [test] Error 1 ``` ","creator":{"public_name":"Алексей Данильченко","id":227505,"is_tutor":false},"comments":[{"creator":{"public_name":"Aleksei Pirogov","id":72206,"is_tutor":true},"id":75698,"body":"**Igor Yakunin**, ваш вопрос не относится к вопросу выше. В другой раз создавайте новый, а дописывайте в существующий вопрос о чём-то другом (чужой к тому же).\n\nЛинтер говорит, что у вас получилась слишком сложная для восприятия конструкция: в одном выражении и \"больше\", и \"меньше\". Нужно сделать так, чтобы оба сравнения были \"одного направления\".","topic_id":33159},{"creator":{"public_name":"Igor Yakunin","id":254640,"is_tutor":false},"id":75696,"body":"```\n/usr/src/app/src/points.py:13:8: WPS333 Found implicit complex compare\nif (a1 < 0) and (a2 > 0):\n```\nПочему-то жалуется на условие. Хотя рядос стоят еще 3 сравнения и там всё нормально.","topic_id":33159},{"creator":{"public_name":"Aleksei Pirogov","id":72206,"is_tutor":true},"id":72145,"body":"В данном упражнении вы не должны создавать точки сами (а у вас точка \"самодельная\" -- кортеж). При решении этого упражнения вы должны использовать абстракцию \"точек\" из модуля `points` (для этого в исходнике присутствует импорт `from hexlet import points`), а именно функции `points.make`, `points.get_x` и `points.get_y`.","topic_id":33159}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":66725,"title":"В вопросе 12 теста к этому уроку не тот ответ считается правильным!\n\n","plain_title":"В вопросе 12 теста к этому уроку не тот ответ считается правильным! ","creator":{"public_name":"Evgeny Tevelevich","id":36137,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":140377,"body":"Спасибо! Исправил.","topic_id":66725},{"creator":{"public_name":"Evgeny Tevelevich","id":36137,"is_tutor":false},"id":140169,"body":"**Sergey K.**, \n```\nСоздадим несколько точек:\n\npoint1 = make_point(25, -50)\npoint2 = make_point(-70, 35)\n\nnew_point = make_point(get_y(point1) + 10, -get_x(point2))\n\nВыберите вариант, который это выражение выведет на экран:\n\nprint(f\"({get_x(new_point)}, {get_y(new_point)})\"\n```","topic_id":66725},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":140129,"body":"Уточните, пожалуйста, о каком вопросе вы говорите. На сайте они выводятся в случайном порядке.","topic_id":66725}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":30107,"title":"В одном из тестов для вывода используется console.log. А в следующем тесте неправильный ответ (Тоже самое было в PHP:Составные данные - https://ru.hexlet.io/topics/29963):\n\n point1 = make_point(25, -50)\n point2 = make_point(-70, 35)\n mew_point = make_point(get_y(point1) + 10, -get_x(point2))","plain_title":"В одном из тестов для вывода используется console.log. А в следующем тесте неправильный ответ (Тоже самое было в PHP:Составные данные - https://ru.hexlet.io/topics/29963): point1 = make_point(25, -50) point2 = make_point(-70, 35) mew_point = make_point(get_y(point1) + 10, -get_x(point2)) ","creator":{"public_name":"Антон Буренков","id":27811,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":65261,"body":"Ага, спасибо! Вывод в консоль убрал. А тесты сейчас корректные были :)","topic_id":30107}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}},{"id":83794,"title":"Друзья, прошу помощи.\nЗадания точки, разбираю обратный ответ от тестов и не понимаю в чем проблема.\nТесты падают потому что имена не могут разрешить. В чем я ошибся?\nЛинтер пишет \n> /usr/src/app/tests/test_points.py:33:35: F821 undefined name 'get_symmetrical_point'\n/usr/src/app/tests/test_points.py:42:12: F821 undefined name 'calculate_distance'\n\nИмена c функциями совпадают.\n\n\nhttps://ru.hexlet.io/code_reviews/904815","plain_title":"Друзья, прошу помощи. Задания точки, разбираю обратный ответ от тестов и не понимаю в чем проблема. Тесты падают потому что имена не могут разрешить. В чем я ошибся? Линтер пишет /usr/src/app/tests/testpoints.py:33:35: F821 undefined name 'getsymmetricalpoint' /usr/src/app/tests/testpoints.py:42:12: F821 undefined name 'calculate_distance' Имена c функциями совпадают. https://ru.hexlet.io/code_reviews/904815 ","creator":{"public_name":"Anton Nikolaev","id":589050,"is_tutor":false},"comments":[{"creator":{"public_name":"Artyom Kropp","id":381127,"is_tutor":true},"id":169938,"body":"Добрый день, Антон. Попробуйте перезапустить упражнение, нажав кнопку \"Сброс\" справа на панели. Только не забудьте сохранить перед этим решение. Кстати, проверил у себя ваше решение и оно успешно прошло.","topic_id":83794}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Точки","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":904,"slug":"python_compound_data_points_exercise","name":null,"state":"active","kind":"exercise","language":"python","locale":"ru","has_web_view":false,"has_test_view":false,"reviewable":true,"readme":"### src/points.py\n\nРеализуйте следующие функции для работы с точками:\n\n* `get_quadrant` — функция, которая вычисляет квадрант, где находится точка.\nНиже приведена схема, показывающая номера квадрантов на плоскости.\n\n ```\n +\n 2 | 1\n |\n +----------------+\n |\n 3 | 4\n +\n ```\n\n ```python\n point1 = points.make(1, 5)\n get_quadrant(point1) # 1\n point2 = points.make(1, -5)\n get_quadrant(point2) # 4\n ```\n\n Если точка не принадлежит ни одному квадранту (т.е., если она лежит хотя бы на одной из осей координат), то функция должна возвращать `None`:\n\n ```python\n point1 = points.make(0, 7)\n get_quadrant(point1) is None # True\n point2 = points.make(2, 0)\n get_quadrant(point2) is None # True\n ```\n\n* `get_symmetrical_point` — функция, возвращающая новую точку, симметричную относительно начала координат.\nТакая симметричность означает, что меняются знаки у `x` и `y`.\n\n ```python\n points.to_string(get_symmetrical_point(points.make(1, 5))) # '(-1, -5)'\n ```\n\n* `calculate_distance` — функция, вычисляющая расстояние между точками по формуле: `d = √((x₂−x₁)²+(y₂−y₁)²)`\n\n ```python\n calculate_distance(\n points.make(3, 2),\n points.make(-1, -1))\n )\n # 5.0\n ```\n","prepared_readme":"### src/points.py\n\nРеализуйте следующие функции для работы с точками:\n\n* `get_quadrant` — функция, которая вычисляет квадрант, где находится точка.\nНиже приведена схема, показывающая номера квадрантов на плоскости.\n\n ```\n +\n 2 | 1\n |\n +----------------+\n |\n 3 | 4\n +\n ```\n\n ```python\n point1 = points.make(1, 5)\n get_quadrant(point1) # 1\n point2 = points.make(1, -5)\n get_quadrant(point2) # 4\n ```\n\n Если точка не принадлежит ни одному квадранту (т.е., если она лежит хотя бы на одной из осей координат), то функция должна возвращать `None`:\n\n ```python\n point1 = points.make(0, 7)\n get_quadrant(point1) is None # True\n point2 = points.make(2, 0)\n get_quadrant(point2) is None # True\n ```\n\n* `get_symmetrical_point` — функция, возвращающая новую точку, симметричную относительно начала координат.\nТакая симметричность означает, что меняются знаки у `x` и `y`.\n\n ```python\n points.to_string(get_symmetrical_point(points.make(1, 5))) # '(-1, -5)'\n ```\n\n* `calculate_distance` — функция, вычисляющая расстояние между точками по формуле: `d = √((x₂−x₁)²+(y₂−y₁)²)`\n\n ```python\n calculate_distance(\n points.make(3, 2),\n points.make(-1, -1))\n )\n # 5.0\n ```\n","has_solution":true,"entity_name":"Точки"},"units":[{"id":3108,"name":"theory","url":"/courses/python-compound-data/lessons/points/theory_unit"},{"id":3109,"name":"quiz","url":"/courses/python-compound-data/lessons/points/quiz_unit"},{"id":3110,"name":"exercise","url":"/courses/python-compound-data/lessons/points/exercise_unit"}],"links":[{"id":423973,"name":"Система координат","url":"https://ru.wikipedia.org/wiki/Система_координат"}],"ordered_units":[{"id":3108,"name":"theory","url":"/courses/python-compound-data/lessons/points/theory_unit"},{"id":3109,"name":"quiz","url":"/courses/python-compound-data/lessons/points/quiz_unit"},{"id":3110,"name":"exercise","url":"/courses/python-compound-data/lessons/points/exercise_unit"}],"id":1474,"slug":"points","state":"approved","name":"Точки","course_order":110,"goal":"Начнем проектировать примитивную графическую библиотеку с создания специальных данных, которые будут моделировать точки на плоскости","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"В этом уроке мы начнем проектирование библиотек для работы с графическими примитивами (точками, отрезками), а также фигурами (кругами, прямоугольниками). Естественно, проще всего начать с основы — библиотеки для работы с точками. Для этого давайте вспомним координатную плоскость и то, как определяются на ней точки.\n\n\n\nВ этом примере мы видим плоскость, на которой расположены две оси (ось абсцисс X и ось ординат Y) и точки. При этом обычно точка описывается двумя параметрами: первым указывается координата x (абсцисса), вторым — координата y (ордината). Например, точка D описывается как (2, 4), точка C — (1, 0) и так далее. Координаты могут быть отрицательными. Оси делят плоскость на четыре части, которые называются квадрантами. На рисунке точка D лежит в первом квадранте, точки A, G, E — во втором, в третьем и в четвертом квадрантах соответственно.\n\nКогда мы говорим о точках, мы на самом деле имеем в виду так называемые __абстрактные данные__. Это означает, что мы не делаем никаких предположений (кроме самых необходимых) для того, чтобы рассуждать в терминах этих данных (в нашем случае — точек).\n\nВспомним любой раздел школьной математики. Когда мы оперируем какими-то понятиями, мы вообще не думаем, как они должны быть представлены — мы мыслим в них на абстрактном уровне. Это позволяет нам строить законы, делать выводы и предположения о том, как будут вести себя данные, как они друг с другом комбинируются. При этом нам не нужна никакая реализация, не нужен ни язык программирования, ни компьютер. Более того, нам вообще необязательно графически изображать эти данные — мы можем представлять это в голове и при этом делать выводы, имеющие практическое значение.\n\nПри этом __конкретные данные__ определяются условиями прикладного использования — например, особенностями разных языков программирования. Зачастую библиотеки снаружи выглядят одинаково и оперируют одними и теми же понятиями, но внутри устроены совершенно по-разному. Естественно, при манипуляциях с такими библиотеками необходимо оставаться в рамках понятийного аппарата тех абстрактных данных, с которыми мы работаем. Клеем между абстрактными данными и конкретными данными выступает **интерфейс** — это набор функций, обычно разделяемых на конструкторы и селекторы. Конструкторы позволяют из набора данных строить составной объект, а с помощью селекторов из составного объекта извлекают его части. В нашем случае, если говорить о точках, селекторы позволяют извлекать координаты x и y. Вот как выглядит интерфейс нашей библиотеки:\n\n```python\nx = 5\ny = -7\n\n# Конструктор\npoint = make_point(x, y)\n\n# Селекторы\nget_x(point)\nget_y(point)\n\nto_string(point) # (5, -7)\n```\n\nВ коде выше видно, что мы определили функцию `make_point()`, которая принимает на вход два числа (`x` и `y`) и возвращает точку с такими координатами. Кроме этого, есть два основных селектора `get_x()` и `get_y()`, которые возвращают соответствующие координаты из точки.\nПри проектировании библиотеки мы используем стратегию «мечтать не вредно», потому что еще не знаем, как она будет устроена внутри. Это, по большому счету, не имеет значения, потому что мы пользуемся абстрактными данными и оперируем терминами нашей предметной области, а не конкретной реализации. Поэтому мы можем сосредоточиться на сути, отложив принятие решения о деталях.\n\nКроме основных селекторов, нами определена функция `to_string()`, которая возвращает текстовое представление точки и может быть использована для отладки.\n\nДавайте посмотрим, какие манипуляции можно производить над точками, имея базовый интерфейс:\n\n```python\ndef symmetrical_point(point):\n return make_point(get_x(point), -get_y(point))\n\n\npoint = make_point(10, 10)\n\nsymmetrical_point(point) # (10, -10)\n```\n\nВ примере выше определена функция `symmetrical_point()`, которая возвращает точку, симметричную заданной относительно оси X. Например, для точки c координатами (10, 10) функция вернет точку с координатами (10, -10). Функция устроена следующим образом: с помощью селекторов мы извлекаем координаты, инвертируем координату Y и создаем новую точку с помощью конструктора `make_point()`.\nБольшинство операций с точками состоят в том, чтобы извлечь координаты, произвести с ними необходимые изменения и создать новые точки. С помощью этого нехитрого алгоритма можно реализовать массу других функций, например:\n\n```python\npoint = make_point(3, 4)\npoint2 = make_point(0, 0)\n\nget_quadrant(point) # 1\nget_distance(point, point2) # 5\n```\n\nФункция `get_quadrant()` определяет квадрант, в котором расположена точка, а функция `get_distance()` вычисляет расстояние между заданными точками. При этом мы оперируем понятием \"точки\", а обращение к составным частям этих точек происходит внутри и скрыто от наших глаз.\n\nПодведем итог:\n\nАбстрактными данными у нас является точка, которая характеризуется двумя значениями (координатами) `x` и `y`.\n\nКонкретные данные: на текущий момент мы не знаем, как реализованы точки (что не мешает нам пользоваться библиотекой в рамках нашей предметной области).\n\nИнтерфейс:\n\n```\n# Конструктор\nmake_point(<x>, <y>)\n\n# Селекторы\nget_x(<point>)\nget_y(<point>)\n```\n\n---\n\n## Внутренняя реализация\n\n```python\npoint = make_point(4, 5)\n\nprint(get_x(point))\nprint(get_y(point))\n\nprint(point) # don't do it\nprint(to_string(point))\nprint(get_quadrant(point))\n```\n\nЕсли вас мучает вопрос, что находится внутри переменной `point`, скажу сразу — мы ответим на него\nв следующих уроках. Для работы с точками не нужно знать их устройство. Когда мы решаем геометрические задачи,\nто легко оперируем понятием отрезка, круга и других фигур. С помощью формул мы можем находить площадь,\nрассчитывать расстояние между точками, проводить касательные и делать многое другое.\n\nКаждое из перечисленных выше понятий абстрактно, не имеет прямого воплощения в реальной жизни и\nможет применяться как к описанию детских игрушек (круглый мяч), так и египетских пирамид. Причем\nв каждом конкретном случае нас будут интересовать разные детали. В одних случаях важны только\nразмеры, в других — расположение относительно некоей системы координат, в третьих — цвет\nфигуры.\n\nВ некоторых задачах при работе с точками могут отсутствовать координаты, но имеются цвет или скорость\nмерцания. Тогда набор функций для работы с подобными точками будет кардинально отличаться от нашего набора.\n\nТочки, которые мы рассматриваем в этом курсе, интересны для нас только как место на координатной\nплоскости, задаваемое парой чисел — координат. Важно, что любая пара чисел однозначно определяет\nточку на этой плоскости, и существование двух разных точек с одинаковыми координатами невозможно.\n\nТочки — это всего лишь один из примеров того, как мы пытаемся выразить в коде\nнашу предметную область, в данном случае — геометрию. Подобная абстракция\nиспользуется в исходном коде графических редакторов. В финансовых приложениях\nиспользуется понятие \"деньги\". Деньги можно выразить просто числом, но\nэтого наверняка будет недостаточно, потому что помимо численного значения,\nдля выражения денежной суммы необходимо как минимум указать еще и валюту.\n\nМожно сказать, что в коде выше создан свой собственный, пользовательский тип данных.\nВ отличие от примитивных типов данных, таких как числа и строки, наш новый тип является _составным_ —\nотсюда и понятие **составные данные**. Внутри себя он хранит набор значений,\nно мы оперируем им как единым целым.\n\nРеальные прикладные приложения, по большей части, оперируют именно такими данными. Например,\nв Хекслете это уроки, курсы, практики, пользователи, наставники, топики в комьюнити, счета и сотни других\nпонятий. Все они гораздо более сложные, чем те же точки. В одном пользователе могут находиться десятки\nпараметров.\n\nНевозможно заранее определить типы данных для представления\nвсего сущего на земле. Все, что требуется от языка — предоставить возможность определять свои\nсобственные типы и операции над ними. И об этом мы поговорим в следующем уроке.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":3099,"name":"theory","url":"/courses/python-compound-data/lessons/intro/theory_unit"}],"links":[],"ordered_units":[{"id":3099,"name":"theory","url":"/courses/python-compound-data/lessons/intro/theory_unit"}],"id":1469,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Познакомимся с курсом, его целями и структурой. Рассмотрим понятие «составные данные» и поймем смысл их создания и использования","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"## Абстракция\n\n<figure>\n <blockquote class=\"blockquote\">\n <p>Абстракция — один из главных способов борьбы со сложностью реального мира.</p>\n </blockquote>\n <figcaption class=\"blockquote-footer\">\n Стив Макконнелл - <cite title=\"Source Title\">Совершенный код</cite>\n </figcaption>\n</figure>\n\nДо сих пор мы работали только с примитивными типами данных, такими как строки и числа. В этом курсе произойдет переход на новый уровень, и большая часть работы сосредоточится вокруг **составных данных**.\n\nВы могли подумать, что на этот раз мы будем осваивать словари. И если бы мы не были Хекслетом, то все было бы именно так. Но у нас немного другие планы на ближайшие курсы. Есть вещи важнее массивов, и с них мы и начнем. К массивам обратимся позже.\n\nНачиная с этого курса, мы погружаемся в **парадигму декларативного программирования**. Вас ждут функции как объекты первого рода, функции высшего порядка, неизменяемые данные (константы), рекурсия и другие интересные вещи. К сожалению, словари при стандартном использовании плохо сочетаются с этими концепциями, а так же позволяют срезать углы там, где не надо. Это сводит на нет весь эффект от обучения. Поэтому основой для составных данных станет так называемая **пара** — структура данных, некогда популярная во многих языках программирования. В отличие от массивов, ее нельзя изменять. В любой ситуации потребуется создать новую пару на основе предыдущей, и обойти этот механизм невозможно. Подобное ограничение оставляет только один способ работы — функциональный.\n\n*Как вы увидите позже, точно так же можно (и зачастую нужно) работать и со словарями. К тому моменту, когда мы начнем их использовать, вы уже будете готовы к такому способу работы.*\n\nОдна из самых важных тем в программировании — **абстракция**. Чем больше кодовая база, тем больше абстракций используется, либо создается в ней. Значительная часть времени разработчика тратится на моделирование предметной области и реализацию ее в коде, а также в ее дальнейшей поддержке и развитии. Как правило, этому вопросу совсем не уделяют времени, но именно от умения моделировать зависит качество вашего кода, насколько просто будет работать с ним, понимать и модифицировать его.\n\nПредставьте, что вам необходимо автоматизировать работу отдела продаж (создать CRM). С чего вы начнете? А начать стоит с [онтологии](https://ru.wikipedia.org/wiki/%D0%9E%D0%BD%D1%82%D0%BE%D0%BB%D0%BE%D0%B3%D0%B8%D1%8F_(%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0)).\n\nВ следующих курсах вы увидите, как, благодаря некоторым особым свойствам, пара может стать основой для создания более сложных структур данных, таких как списки, множества и даже деревья. Другими словами, мы заочно познакомимся с разными структурами данных и поработаем с ними.\n\nПо названию этого курса можно подумать, что мы будем говорить о таких типах данных в python, как список или словарь. Это совсем не так. В этом курсе мы используем python, как язык, на котором пишем код, но сам курс относится в целом к программированию. Мы изучим такое понятие, как абстракция данных. Эта общая концепция никак не связана конкретно с python и является важным механизмом абсолютно во всех языках программирования. В отличие от предыдущих курсов мы вообще не введем ни одной новой синтаксической конструкции. В этом и есть наше отличие: мы учим вас мыслить, писать код и программировать, что сильно отличается от того, что существует сейчас на рынке.\n\nИтак, давайте начнем.\n\n### Что такое составные данные?\n\n**Составные данные** используются в следующих случаях. Когда мы пишем программы,\nто чаще всего пытаемся моделировать достаточно сложные процессы и явления,\nпротекающие часто в реальной жизни. Для этого мы можем использовать составные\nвычислительные объекты, которые включают в себя несколько различных частей.\nЭто позволяет лучше моделировать все явления реальной среды, того, с чем мы\nвзаимодействуем. И язык программирования должен предоставлять возможность создавать механизмы для создания так называемых составных данных, то есть из разных кусков собирать отдельный вычислительный объект.\n\n### Зачем это нужно?\n\nПри работе со сложными вычислительными объектами благодаря составным данным,\nкак мы уже говорили, мы можем поднимать так называемый понятийный уровень. Мы будем работать на более высоком уровне абстракции и строить нашу программу и производить вычисления в терминах наших вычислительных объектов. Давайте рассмотрим это на конкретном примере, который известен всем со школы — это точки на плоскости.\n\nУ нас есть координатная плоскость, и мы можем строить на ней точки. При этом любая точка обладает двумя характеристиками — это ее координаты `x` и `y`, то есть абсцисса и ордината. В данном примере мы используем то, что уже известно — простые числовые значения, и описываем две точки. Одна из них — `x1 / y1`, вторая — `x2 / y2`. После этого вычисляем точку посередине:\n\n```python\nx1 = 3\ny1 = 5\n\nx2 = -2\ny2 = 10\n\n# Точка посередине\nx3 = middle_x(x1, x2)\ny3 = middle_y(y1, y2)\n```\n\nМы считаем, что мы строим отрезок между двумя этими точками и находим точку, которая лежит посередине. То, как это работает, не имеет значения. Главное, что вы можете себе это представить. Мы оперируем понятием *точка*. При этом понимаем, что она состоит из двух простых типов, то есть двух чисел. По сути появляется такая вещь как пара. И в общем-то нам удобно оперировать такими терминами.\n\nНо при написании кода, если у нас не существует понятия составные данные, то, как видите, нам приходится производить вычисления независимо. Т.е. сначала вычислить `х3`, после этого нам надо вычислить `y3`. Это происходит потому, что мы не оперируем понятием точка. У нас нет такого составного вычислительного объекта. И все, что мы можем в наших функциях, это принимать простые параметры и возвращать точно такие же простые параметры. Получается, что одно действие технически разбивается на два. Согласитесь, это крайне неудобно. Особенно, когда ваша программа становится достаточно большой и начинает оперировать большим количеством сложных объектов.\n\nТочки, кстати говоря — это еще достаточно простые объекты, которые включают в себя буквально 2 параметра. Представьте, что будет, когда мы начнем использовать более сложные объекты и фигуры. Возьмем для примера квадрат. Он включает в себя 4 точки — это уже 8 параметров, потому что в каждой точке 2 параметра. Работать на таком уровне абстракции, используя только примитивные типы и примитивные данные, мы просто бы не смогли. Наше мышление очень сильно бы этому сопротивлялось. Код получался бы очень громоздкий и не очень интуитивный.\n\nВ идеале нам бы хотелось работать так, как показано в этой строчке:\n\n```python\n# Compound Data\nmiddle_point = middle(point1, point2)\n```\n\nМы отдаем в функцию две точки (`point1` и `point2`), и нам возвращается точка посередине. Как она устроена и что там внутри — это отдельный вопрос. Главное, что мы оперируем этим понятием, как единым целым. Возможность строить составные объекты данных позволяет нам использовать технику, которая называется абстракция данных.\n\n**Абстракция данных** — это метод отделения частей программы,\nкоторые реализуют представление объектов данных.\n\nПри использовании точки в предыдущем примере это обозначает, что нас не очень волнует, как внутри устроена точка. Мы можем просто оперировать только этим понятием. Соответственно, определять то, как непосредственно внутри она выглядит, мы можем совершенно в другом месте. Это делает код модульным и дает огромные возможности по модификации и поддержке кода. Например, мы можем изменить представление данных на более эффективное, более удобное в данный момент и не переписывать всю программу. Нужно будет переписать только ту часть, где именно происходит определение того, как данные структурированы внутри.\n\nАбстракция данных приводит нас к такому понятию, как **барьеры абстракции**, когда мы можем строить многоуровневые слои, позволяющие изолировать разные части и разные уровни системы друг от друга. Об этом будет отдельный урок, где мы подробно поговорим, как работает абстракция данных, и как строятся барьеры абстракции.\n\nКроме этого в любом языке программирования нужен некий клей, который позволит из простых данных строить более сложные. И как мы увидим в дальнейшем, для этого даже не нужны специальные операции. Строить составные объекты можно используя только возможности функций, что еще больше стирает разницу между функциями и данными. Это можно было уже заметить, потому что функции определяются точно так же как данные. Функции являются полноправными данными, так называемыми объектами первого рода. Этому будет посвящен целый отдельный урок.\n\n### Графические примитивы\n\nНа протяжении всего курса мы будем строить графические примитивы и небольшую библиотеку для работы с примитивами на плоскости. Начнем с точек, поработаем с кругами, научимся делать различные отрезки и фигуры.\n\n### Рациональные числа\n\nКроме этого попробуем построить простую библиотеку для работы с рациональными числами. Это числа, у которых есть числитель и знаменатель. Определение, конечно, не строго математическое, но вы на интуитивном уровне все с ними знакомы. Рациональное число — достаточно простой, но составной объект, который включает в себя пару чисел.\n\n### Преимущества Hexlet\n\n- Осмысление\n- СИКП\n- Не используем существующие структуры данных\n- Функции высшего порядка\n- Неизменяемость (Функциональный стиль)\n\nПо опыту предыдущих курсов очень часто у людей возникают вопросы: \"А почему именно так?\", \"Как\nмне это поможет в практике?\" и \"Я хочу быстрее начать писать продакшн код во фреймворках\". Да, мы не просто учим синтаксису. Мы хотим изменить ваше мышление и развить критическое мышление, чтобы вы понимали причинно-следственные связи. Это поможет вам мыслить абстракциями, а не полагаться только на синтаксис языка. Это позволит писать вам модульные программы, которые легко читать, поддерживать, развивать, и они будут выполнять то, что нам нужно. Это наша основная идея, то, что хочет дать и несет Хекслет. Возможно, вы уже заметили это по каким-то вещам, которые мы рассказываем или делаем.\n\nНаш курс во многом построен на так называемом СИКПе — курсе, который велся и ведется до сих пор в большом количестве университетов мира. Расшифровывается, как структура и интерпретация компьютерных программ. Курс был придуман в Массачусетском технологическом институте (MIT). Это университет номер один в мире IT-технологий, который выпускал и выпускает лучших специалистов в этой области. СИКП — достаточно большой, сложный, но крайне важный курс. По нему выпущена книга, которая уже десятки лет считается книгой номер один среди обучающей литературы по программированию. В любом случае всем ее рекомендуем. Здесь мы ее активно используем, потому что она дала нам много материала и пищи для размышлений. Книга была выпущена в 1985 году, а сам курс велся еще раньше (с начала 80-х). Но, как вы увидите в дальнейшем, несмотря на то, что меняются технологии, меняются фреймворки, меняется все вокруг, но базовые вещи, так же как во многом и математика, в общем-то не меняется и не поменяется. Не гонитесь за новым модным, изучайте то, на чем все это базируется.\n\nКурс не использует существующие структуры данных. Мы сказали об этом в самом начале. Здесь не будет ничего про массивы, объекты или какие-то другие возможные способы комбинирования данных. Это отвлечет нас от основной идеи. От понимания сути вопроса. Наши функции в этом курсе приобретут совершенно новый оттенок и заиграют новыми красками. Мы познакомимся с функциями высшего порядка и увидим, что функции — это точно такие же данные, которые можно передавать в другие функции, возвращать из функций и делать с ними совершенные чудеса.\n\nВ курсе мы будем работать с неизменяемыми данными, использовать функциональный стиль программирования. Этот достаточно важный аспект позволяет нам сосредоточиться только на самом вопросе, который разбирается в курсе, и оставить вопрос изменения состояния и связанных с этим проблем в стороне. В Основах программирования мы уже немного говорили о том, какие проблемы привносят переменные и изменения их состояний. В дальнейшем будет отдельный большой курс, посвященный работе с состояниями и всеми особенностями, которые привносят возможность использовать переменные.\n"},"id":188,"slug":"python-compound-data","challenges_count":3,"name":"Python: Составные данные","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"На этом курсе вы изучите идею составных данных. Вы узнаете больше о концепциях создания сложных типов данных из простых и о парадигме декларативного программирования. В итоге научитесь создавать абстракции и изолировать разные части программы. Знания из этого курса помогают программистам моделировать необходимую предметную область, писать более читаемый и модульный код.","kind":"additional","updated_at":"2026-01-20T11:45:09.591Z","language":"python","duration_cache":29880,"skills":["Создавать код, который легко читать и понимать что он делает","Научиться создавать удобные абстракции и скрывать внутреннюю реализацию данных","Определять границу между слоями приложения так, чтобы поддерживать высокий уровень модульности (независимости разных частей) кода"],"keywords":["моделирование данных","барьеры абстракции","замыкание"],"lessons_count":7,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NzI3OCwicHVyIjoiYmxvYl9pZCJ9fQ==--d7a4e52ff43a80f8fb59fe7d2a8fb11c1036cc74/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":37,"slug":"python-sicp","title":"СИКП на Python","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":4150,"duration_in_months":1},"id":62,"slug":"python-sicp","title":"СИКП на Python","subtitle":"Навык понимать код на фундаментальном уровне, уверенно проходить собеседования и решать сложные задачи","subtitle_for_lists":"Изучите Python на глубоком уровне для решения сложных задач","locale":"ru","current":true,"duration_in_months_text":"1 месяц","stack_slug":"python-sicp","price_text":"от 3 900 ₽","duration_text":"1 месяц","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc1OCwicHVyIjoiYmxvYl9pZCJ9fQ==--023ea18f500b1c4c91617fa96bbc52df8395da39/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Software%20engineer-bro.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/python-compound-data/lessons/points/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Python: Составные данные</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Точки</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Точки","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Python: Составные данные"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>В этом уроке мы начнем проектирование библиотек для работы с графическими примитивами (точками, отрезками), а также фигурами (кругами, прямоугольниками). Естественно, проще всего начать с основы — библиотеки для работы с точками. Для этого давайте вспомним координатную плоскость и то, как определяются на ней точки.</p>
<p><img style="--image-object-fit:contain;width:auto" class="m_9e117634 mantine-Image-root" src="/rails/active_storage/blobs/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NzI3OSwicHVyIjoiYmxvYl9pZCJ9fQ==--0550e609a1368addaa908819393b81e7e90a7ea3/plane.png" alt="Прямоугольная система координат на плоскости" loading="lazy"/></p>
<p>В этом примере мы видим плоскость, на которой расположены две оси (ось абсцисс X и ось ординат Y) и точки. При этом обычно точка описывается двумя параметрами: первым указывается координата x (абсцисса), вторым — координата y (ордината). Например, точка D описывается как (2, 4), точка C — (1, 0) и так далее. Координаты могут быть отрицательными. Оси делят плоскость на четыре части, которые называются квадрантами. На рисунке точка D лежит в первом квадранте, точки A, G, E — во втором, в третьем и в четвертом квадрантах соответственно.</p>
<p>Когда мы говорим о точках, мы на самом деле имеем в виду так называемые <strong>абстрактные данные</strong>. Это означает, что мы не делаем никаких предположений (кроме самых необходимых) для того, чтобы рассуждать в терминах этих данных (в нашем случае — точек).</p>
<p>Вспомним любой раздел школьной математики. Когда мы оперируем какими-то понятиями, мы вообще не думаем, как они должны быть представлены — мы мыслим в них на абстрактном уровне. Это позволяет нам строить законы, делать выводы и предположения о том, как будут вести себя данные, как они друг с другом комбинируются. При этом нам не нужна никакая реализация, не нужен ни язык программирования, ни компьютер. Более того, нам вообще необязательно графически изображать эти данные — мы можем представлять это в голове и при этом делать выводы, имеющие практическое значение.</p>
<p>При этом <strong>конкретные данные</strong> определяются условиями прикладного использования — например, особенностями разных языков программирования. Зачастую библиотеки снаружи выглядят одинаково и оперируют одними и теми же понятиями, но внутри устроены совершенно по-разному. Естественно, при манипуляциях с такими библиотеками необходимо оставаться в рамках понятийного аппарата тех абстрактных данных, с которыми мы работаем. Клеем между абстрактными данными и конкретными данными выступает <strong>интерфейс</strong> — это набор функций, обычно разделяемых на конструкторы и селекторы. Конструкторы позволяют из набора данных строить составной объект, а с помощью селекторов из составного объекта извлекают его части. В нашем случае, если говорить о точках, селекторы позволяют извлекать координаты x и y. Вот как выглядит интерфейс нашей библиотеки:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">x = 5
y = -7
# Конструктор
point = make_point(x, y)
# Селекторы
get_x(point)
get_y(point)
to_string(point) # (5, -7)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В коде выше видно, что мы определили функцию <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">make_point()</code>, которая принимает на вход два числа (<code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">x</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">y</code>) и возвращает точку с такими координатами. Кроме этого, есть два основных селектора <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">get_x()</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">get_y()</code>, которые возвращают соответствующие координаты из точки.
При проектировании библиотеки мы используем стратегию «мечтать не вредно», потому что еще не знаем, как она будет устроена внутри. Это, по большому счету, не имеет значения, потому что мы пользуемся абстрактными данными и оперируем терминами нашей предметной области, а не конкретной реализации. Поэтому мы можем сосредоточиться на сути, отложив принятие решения о деталях.</p>
<p>Кроме основных селекторов, нами определена функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">to_string()</code>, которая возвращает текстовое представление точки и может быть использована для отладки.</p>
<p>Давайте посмотрим, какие манипуляции можно производить над точками, имея базовый интерфейс:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">def symmetrical_point(point):
return make_point(get_x(point), -get_y(point))
point = make_point(10, 10)
symmetrical_point(point) # (10, -10)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>В примере выше определена функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">symmetrical_point()</code>, которая возвращает точку, симметричную заданной относительно оси X. Например, для точки c координатами (10, 10) функция вернет точку с координатами (10, -10). Функция устроена следующим образом: с помощью селекторов мы извлекаем координаты, инвертируем координату Y и создаем новую точку с помощью конструктора <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">make_point()</code>.
Большинство операций с точками состоят в том, чтобы извлечь координаты, произвести с ними необходимые изменения и создать новые точки. С помощью этого нехитрого алгоритма можно реализовать массу других функций, например:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">point = make_point(3, 4)
point2 = make_point(0, 0)
get_quadrant(point) # 1
get_distance(point, point2) # 5</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">get_quadrant()</code> определяет квадрант, в котором расположена точка, а функция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">get_distance()</code> вычисляет расстояние между заданными точками. При этом мы оперируем понятием "точки", а обращение к составным частям этих точек происходит внутри и скрыто от наших глаз.</p>
<p>Подведем итог:</p>
<p>Абстрактными данными у нас является точка, которая характеризуется двумя значениями (координатами) <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">x</code> и <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">y</code>.</p>
<p>Конкретные данные: на текущий момент мы не знаем, как реализованы точки (что не мешает нам пользоваться библиотекой в рамках нашей предметной области).</p>
<p>Интерфейс:</p>
<code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight"># Конструктор
make_point(<x>, <y>)
# Селекторы
get_x(<point>)
get_y(<point>)</code>
<hr/>
<h2 id="heading-2-1">Внутренняя реализация</h2>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">point = make_point(4, 5)
print(get_x(point))
print(get_y(point))
print(point) # don't do it
print(to_string(point))
print(get_quadrant(point))</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Если вас мучает вопрос, что находится внутри переменной <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">point</code>, скажу сразу — мы ответим на него
в следующих уроках. Для работы с точками не нужно знать их устройство. Когда мы решаем геометрические задачи,
то легко оперируем понятием отрезка, круга и других фигур. С помощью формул мы можем находить площадь,
рассчитывать расстояние между точками, проводить касательные и делать многое другое.</p>
<p>Каждое из перечисленных выше понятий абстрактно, не имеет прямого воплощения в реальной жизни и
может применяться как к описанию детских игрушек (круглый мяч), так и египетских пирамид. Причем
в каждом конкретном случае нас будут интересовать разные детали. В одних случаях важны только
размеры, в других — расположение относительно некоей системы координат, в третьих — цвет
фигуры.</p>
<p>В некоторых задачах при работе с точками могут отсутствовать координаты, но имеются цвет или скорость
мерцания. Тогда набор функций для работы с подобными точками будет кардинально отличаться от нашего набора.</p>
<p>Точки, которые мы рассматриваем в этом курсе, интересны для нас только как место на координатной
плоскости, задаваемое парой чисел — координат. Важно, что любая пара чисел однозначно определяет
точку на этой плоскости, и существование двух разных точек с одинаковыми координатами невозможно.</p>
<p>Точки — это всего лишь один из примеров того, как мы пытаемся выразить в коде
нашу предметную область, в данном случае — геометрию. Подобная абстракция
используется в исходном коде графических редакторов. В финансовых приложениях
используется понятие "деньги". Деньги можно выразить просто числом, но
этого наверняка будет недостаточно, потому что помимо численного значения,
для выражения денежной суммы необходимо как минимум указать еще и валюту.</p>
<p>Можно сказать, что в коде выше создан свой собственный, пользовательский тип данных.
В отличие от примитивных типов данных, таких как числа и строки, наш новый тип является <em>составным</em> —
отсюда и понятие <strong>составные данные</strong>. Внутри себя он хранит набор значений,
но мы оперируем им как единым целым.</p>
<p>Реальные прикладные приложения, по большей части, оперируют именно такими данными. Например,
в Хекслете это уроки, курсы, практики, пользователи, наставники, топики в комьюнити, счета и сотни других
понятий. Все они гораздо более сложные, чем те же точки. В одном пользователе могут находиться десятки
параметров.</p>
<p>Невозможно заранее определить типы данных для представления
всего сущего на земле. Все, что требуется от языка — предоставить возможность определять свои
собственные типы и операции над ними. И об этом мы поговорим в следующем уроке.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/python-sicp?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">1 месяц</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">СИКП на Python</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите Python на глубоком уровне для решения сложных задач</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6Mzc1OCwicHVyIjoiYmxvYl9pZCJ9fQ==--023ea18f500b1c4c91617fa96bbc52df8395da39/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Software%20engineer-bro.png" alt="СИКП на Python" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/python-compound-data/lessons/points/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 7</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/python-compound-data/lessons/points/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>