📖 Теория: Математические функции, модули и хранение чисел

⚡ Кратко

Python предоставляет встроенные математические функции и огромную стандартную библиотеку через модули. Для сложных вычислений импортируют math; для точных денежных расчётов — Decimal. Вещественные числа хранятся в двоичном виде, поэтому десятичные дроби типа 0.1 могут быть представлены с погрешностью.

  • abs, max, min, pow, sum, round — встроенные функции.
  • import mathmath.sqrt(), math.pi, math.radians().
  • round() использует банковское округление до чётного.
  • float — 64-битное IEEE 754; Decimal('0.1') даёт точное значение.
  • _name в модуле — внутреннее имя по соглашению: import * его пропускает, явный импорт разрешён.
  • __all__ = [...] задаёт, что именно экспортирует модуль при import *.

Встроенные математические функции

Эти функции доступны всегда, без импорта. Они покрывают базовые потребности: модуль, максимум/минимум, степень, сумму и округление.

📄 examples/builtin_math.py
print(abs(-7))              # 7
print(max(3, 8, 2))         # 8
print(min(3, 8, 2))         # 2
print(pow(2, 3))            # 8
print(sum([1, 2, 3, 4]))    # 10
print(round(3.7))           # 4
print(round(2.5))           # 2  (банковское округление)
print(round(3.5))           # 4
print(round(-1.5))          # -2
⚠️ Важно: round(2.5) возвращает 2, а не 3. Python использует округление до ближайшего чётного (round-half-to-even). Это не ошибка, а стандартное поведение IEEE 754.

Модули и import

Модуль — это файл с расширением .py, содержащий определения функций, переменных и классов. Модули помогают:

  • повторно использовать код;
  • организовывать большие программы;
  • изолировать имена, чтобы функции с одинаковыми названиями не конфликтовали.
📄 examples/import_styles.py
import math
print(math.sqrt(16))        # 4.0

from math import sqrt, pi
print(sqrt(16))             # 4.0
print(pi)                   # 3.141592653589793

import math as m
print(m.sqrt(16))           # 4.0

from math import *          # работает, но не рекомендуется
print(sqrt(16))             # конфликты имён возможны
💡 Совет: предпочитайте import math или from math import sqrt, pi. from math import * засоряет пространство имён и может случайно заменить ваши собственные переменные.

Внутренние имена модуля и __all__

Имя, начинающееся с одного подчёркивания, сообщает другим программистам: «это внутренняя деталь модуля, снаружи её использовать не следует». Это соглашение, а не запрет.

📄 lesson_tools.py
# Обычное публичное имя
course_name = "Python OOP"

# Одно подчёркивание — внутреннее имя модуля
_teacher_note = "Не является частью публичного интерфейса"


def show_topic() -> None:
    """Публичная функция."""
    print("Абстрактные классы")


def _calculate_internal_value() -> int:
    """Внутренняя вспомогательная функция."""
    return 42

Единственное, на что подчёркивание влияет технически, — это импорт через звёздочку:

📄 main.py
from lesson_tools import *

print(course_name)   # работает
show_topic()         # работает

# print(_teacher_note)               # NameError
# print(_calculate_internal_value()) # NameError

При этом явный импорт по имени по-прежнему разрешён — «защиты» здесь нет:

from lesson_tools import _calculate_internal_value
print(_calculate_internal_value())   # 42

import lesson_tools
print(lesson_tools._teacher_note)    # тоже работает

В модуле нет манглирования имён

Двойное подчёркивание ведёт себя в модуле иначе, чем в классе. В модуле имя остаётся как есть:

# secrets_demo.py
__secret = "данные"

# в другом файле:
import secrets_demo
print(secrets_demo.__secret)   # "данные" — имя не изменилось

А внутри класса Python переписывает имя (name mangling), чтобы наследник случайно его не перезаписал:

class Account:
    def __init__(self) -> None:
        self.__balance = 100


account = Account()
# print(account.__balance)         # AttributeError
print(account._Account__balance)   # 100 — реальное имя атрибута

Явный публичный интерфейс: __all__

Список __all__ задаёт, какие имена уедут при from module import *. Это способ явно объявить публичный интерфейс модуля:

📄 lesson_tools.py
__all__ = ["course_name", "show_topic", "_special_function"]

course_name = "Python OOP"
internal_value = 100


def show_topic() -> None:
    print("Итераторы")


def _special_function() -> None:
    print("Имя с подчёркиванием экспортировано намеренно")

Теперь from lesson_tools import * импортирует ровно три имени из списка. Обратите внимание на два следствия:

  • internal_value не импортируется, хотя подчёркивания в имени нет — __all__ перекрывает правило по умолчанию;
  • _special_function импортируется, несмотря на подчёркивание — потому что автор явно включил его в список.
💡 Что запомнить: _name — внутреннее имя по соглашению; from module import * обычно пропускает такие имена; явный импорт по имени не запрещён; в модуле манглирования нет; __all__ задаёт публичный интерфейс. В рабочем коде import * лучше не использовать вовсе.

Стандартная библиотека Python

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

  • os — работа с операционной системой и файлами.
  • sys — доступ к параметрам интерпретатора.
  • random — случайные числа.
  • math — математические функции.
  • datetime — даты и время.
  • json — формат JSON.
  • re — регулярные выражения.

Модуль math

Модуль math расширяет возможности встроенных функций корнями, факториалом, округлениями, константами и тригонометрией.

📄 examples/math_module.py
import math

print(math.sqrt(25))        # 5.0
print(math.pow(2, 3))       # 8.0  (всегда float)
print(math.factorial(5))    # 120
print(math.ceil(4.2))       # 5    (округление вверх)
print(math.floor(4.9))      # 4    (округление вниз)
print(math.pi)              # 3.141592653589793
print(math.e)               # 2.718281828459045

angle_deg = 90
angle_rad = math.radians(angle_deg)
print(math.sin(angle_rad))  # 1.0
⚠️ Углы в тригонометрии: math.sin, math.cos и math.tan принимают угол в радианах, а не в градусах. Используйте math.radians() для перевода.

Биты и байты

Бит — минимальная единица информации: 0 или 1. Байт состоит из 8 бит и может хранить:

  • беззнаковое число от 0 до 255;
  • знаковое число от -128 до 127.
📄 examples/binary_42.py
number = 42
binary = bin(number)        # '0b101010'
print(f"{number} в двоичной системе: {binary}")
# Восьмибитная запись: 00101010

Хранение чисел в памяти

Целые числа int в Python имеют произвольную точность: они могут расти, пока хватает оперативной памяти. Во многих других языках целые фиксированной ширины:

  • 32-бит int: от -231 до 231 - 1.
  • 64-бит int: от -263 до 263 - 1.

Вещественные числа float в Python занимают 64 бита (8 байт) по стандарту IEEE 754: 1 бит знака, 11 бит экспоненты и 52 бита мантиссы. Диапазон примерно ±10308.

Точность float

Десятичные дроби не всегда точно представляются в двоичном виде. Классический пример:

📄 examples/float_precision.py
a = 0.1 + 0.2
print(a)                    # 0.30000000000000004
print(a == 0.3)             # False

# Безопасное сравнение
print(round(a, 2) == 0.3)   # True
print(math.isclose(a, 0.3, rel_tol=1e-9))  # True
💡 Правило: никогда не сравнивайте float на точное равенство, если значения получены в результате вычислений. Используйте math.isclose() или округление.

Модуль Decimal

Для финансовых и других точных вычислений используйте Decimal. Важно передавать строку, а не float, иначе погрешность уже будет внесена.

📄 examples/decimal_money.py
from decimal import Decimal

print(Decimal('0.1') + Decimal('0.2'))   # 0.3
print(Decimal('0.1') + Decimal('0.2') == Decimal('0.3'))  # True

# Неправильно: float уже неточен
print(Decimal(0.1) + Decimal(0.2))       # 0.300000000000000016653...
Проверить по документации: подробнее о модулях math и decimal читайте в официальной документации Python: math — Mathematical functions, decimal — Decimal fixed point and floating point arithmetic.