Computer Science

Пишем про компьютеры и их строение для программистов, инженеров и компутерщиков. Статьи выходят раз в неделю: по пятницам или субботам, на 5-7 минут чтения.
Планы канала на трелло.
По всем вопросам — в телеграмм.

Ошибки и документация

Обычно в языках программирования существуют четыре типа ошибок: синтаксические, именные, типовые и логические.

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

Синтаксические ошибки

Появляются при нарушении правил языка, например, когда забываем поставить закрывающую скобку или кавычку.

print('cmp)

Кроме ошибки, интерпретатор показывает последовательность вызовов до появления ошибки. Эта последовательность называется трассировкой стека или же stack trace

Файл состоит из одной строчки и не использует библиотеки, поэтому стэк трейс состоит из одной строчки — имени файла и номера линии с ошибкой

Исключения

Ошибки в пайтоне выделяются в отдельный тип данных — исключения. Они вылезают и говорят где и как ошибся программист. У исключений есть разделение по группам: арифметические, системные, файловые, ошибки индексов, ошибки кодировки и другие.
Все актуальные ошибки описаны в статье про исключения на pythonworld.ru

За именные ошибки отвечает исключение NameError. Оно возникает когда мы обращаемся к какому-то объекту, но для программы он не существует.

base = 2
print(base ** to_power)
Добавьте в код переменную to_power с любым числовым значением и он заработает

За типовые ошибки отвечает TypeError. Возникает когда используем объект неправильно. Попытаемся вычислить из строки квадратный корень и интерпретатор остановит программу и скажет, что так нельзя и почему.

from math import sqrt
str = '9'
print(sqrt(str))
Приведите переменную str к числу через int() и код заработает

Логические ошибки

Отладчик не понимает ошибка это или нет, поэтому они самые неприятные. Логические ошибки возникают когда синтаксис программы и типы у переменных указаны правильно, но программа отдаёт неправильный результат.
Для примера, напишем функцию, которая вычисляет и возвращает полупериметр треугольника.

Формула для расчёта: \(p = \frac{(a + b + c)}{2}\)

def semi_perimeter(a, b, c):
    return a + b + c / 2


print(semi_perimeter(3, 4, 5))  # Выведется 9.5

Но это неправильно, в коде переменная с сначала делится на два, потом к результату добавляются переменные a и b. Если использовать скобки, приоритет операций станет правильным и ошибки не будет.

def semi_perimeter(a, b, c):
    return (a + b + c) / 2


print(semi_perimeter(3, 4, 5))  # Выведется 6

Обработка исключений

В программах бывают моменты, когда нужно избежать каких-нибудь особых ситуаций.

Для примера, возьмём деление на ноль. Программист создаёт обработку исключения для участка кода и она будет отлавливать ситуацию с нулём в делителе.
Исключения можно ловить и обрабатывать через конструкцию try→except.
В try пишем что хотим проверить, например какое-нибудь выражение. Потом пишем except и имя исключения, потом что произойдёт если получится исключение.

try:
    # Здесь пишем код который может создать исключение
    a = 5
    b = 3
    a = a / b

except ArithmeticError:
    # Здесь обрабатываем пойманное исключение, у нас это ZeroDivisionError.
    # Прописываем, что выведется при делении на ноль
    print('Не надо так')

Документация

Чтобы делиться своим кодом или продавать, нужно уметь документировать программу. Инструкцию «как, что и куда» прикладывают к репозиторию отдельным файлом README и добавляют инструкции комментариями по коду программы.

Вот так оформлен репозиторий algorithms. Большой README и его перевод на 7 языков

В коде

Строка документации должна стоять самой первой в функции, библиотеки или классе. Документация может однострочная и многострочная.
Блок документации открывается тремя двойными кавычками, закрывается тремя снизу. Чтобы прочитать только документацию, пишем print(Имя_функции_или_библиотеки.__doc__)

def sum_square(a, b):
    """
    Многострочная
    Функция принимает два целых числа a и b.
    Возвращает сумму квадратов a и b
    """
    return a ** 2 + b ** 2


print(sum_square.__doc__)


def sum_square(a, b):
    """Однострочная   Функция принимает два целых числа a и b. Возвращает сумму квадратов a и b"""
    return a ** 2 + b ** 2


print(sum_square.__doc__)

Для примера, напишем функцию для вычисления площади треугольника по формуле Герона.

Формула Герона: \(S = \sqrt {p * (p — a) * (p — b) * (p — c)}\), где \(p = \frac{(a + b + c)}{2}\)



В следующей статье опишем что такое выражения и инструкции, и расскажем про декларативное и императивное программирование.

Строки и составные типы данных

Строки и символы

В программировании строки — это массив отдельных символов. В пайтоне для символьного типа данных существуют только строки, отдельно символов нет. Даже если хочется my_string = ’q’, то получится строка с одним символом внутри.

Пустая строка занимает 49 байт, потому что нужно место для ссылок на эту строку, её адреса в памяти и размере в памяти с символами. Все остальные символы занимают по одному байту. В конце строки добавляется нулевой байт, чтобы показать интерпретатору что она закончилась.

empty_string = str()  # Объявили пустую строку
print(empty_string.__sizeof__())  # Выведется 49, это размер пустой строки в байтах

empty_string = 'cmp'
print(empty_string.__sizeof__())  # Выведется 52, размер пустой строки плюс по байту на каждый символ

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

not_empty_string = 'cmp'


empty_string = str()  # Так создаётся пустая строка: сначала имя, потом функция str().

Индексы

К символам строки можно получать доступ по индексу, их месту в строке.

channel_name = 'Cmp-Sci'
print(channel_name[0])  # Выведется символ 'C'
print(channel_name[2])  # Выведется символ 'p'
Длина строки — семь символов, но считается с нуля, поэтому последний символ с индексом 6

Из строки можно достать несколько символов, это называется срезом.
Для среза можно передать три параметра: начало, количество символов для среза и шаг.
Не указанные параметры пайтон выставляет по дефолту: начало среза — начало строки, размер среза — вся строка, шаг среза — один.

channel_name = 'Cmp-Sci'
print(channel_name[0:2])  # Выведется 'Cm'

'''
Можно указывать отрицательные аргументы, это удобно когда в длинном 
предложении нужно убрать несколько символов с конца.
'''
print(channel_name[0:-2])

# Берём каждый второй символ от начала и до конца строки.
print(channel_name[::2])  # Выведется 'CpSi'

Строки можно складывать и дублировать. Сложение строк называется конкатенацией.

# Сложение строк
first_word = 'Cmp'
second_word = 'Sci'
print(first_word + second_word)  # Выведется 'CmpSci'
print(first_word + 'qq all' + second_word)  # Можно вставлять что-нибудь между слагаемыми в кавычках

# Дублирование строки
print(first_word * 3)  # Выведется 'CmpCmpCmp'

Строки в пайтоне — неизменяемые. Любая операция над строкой создаёт новую строку, а не изменяет старую. Чтобы ненужные переменные не занимали место в оперативке, их удаляет сборщик мусора. Это механизм, который следит за счётчиком ссылок на каждую переменную: на строку нет ссылок — он удаляет её. Можно удалить вручную: del name_variable

number = 5
print(number)  # Выведется пять

del number
print(number)  # Будет ошибка — number больше не существует

Списки

Коллекция из данных разных типов. Мы объявляем список и добавляем туда элементы, но на самом деле туда подставляются адреса на эти переменные.
Чтобы объявить список, пишем имя переменной и элементы списка через запятую в квадратных скобках. Можно сразу написать имя списка и присваивать элементы или сделать список через функцию.

first_list = [1, '3', 'sci']  # **int**овая единица, строковая тройка и 'sci'
first_list = list([1, '3', 'sci'])  # Список через функцию

second_list = list([1, 2, 3, 'a', ['cmp', 'sci'], 12])  # Элементом списка может быть другой список и в нём другой список и так далее

Размер пустого списка — 40 байт, каждый элемент списка добавляет 8 байт.

empty_list = list()
print(empty_list.__sizeof__())  # Выведется 40 — размер пустого списка

empty_list = [2, 3, 'abc']
print(empty_list.__sizeof__())  # Выведется 64 — размер пустого списка плюс три элемента

К спискам применяются срезы, как у строк.

numeric_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numeric_list[::2])  # Выведется [0, 2, 4, 6, 8]

Функции для списков

Для удобного редактирования или сортировки у списков есть встроенные функции.
Чтобы использовать функцию списка нужно вызвать ее из списка через точку: list_name.function().

list.append(element) добавляет element в конец списка list
list.insert(index, element) добавляет element на место index в списке list. Остальные элементы сдвигаются вправо
list.index(element) возвращает индекс первого элемента со значением element
list.count(element) возвращает количество element
list.remove(element) удаляет element
list.del(index) удаляет элемент по месту index
list.sort() сортирует список list по возрастанию. Строки сортируются по первой букве
list.reverse() переворачивает список list
list.pop() без аргументов — удаляет последний элемент, с аргументом — удаляет элемент по его индексу
len(list) возвращает длину списка list
max(list) возвращает максимальный элемент в списке list
min(list) возвращает минимальный элемент в списке list
element in list проверяет наличие элемента: True — есть в списке, False — нету

Последние четыре функции — общие. Их можно применять не только к спискам, ещё к строкам, массивам, кортежам и другим составным типам.

Кортежи

Это неизменяемый список, с меньшим потреблением памяти. Объявляются так же, но элементы в круглых скобках.

not_empty_tuple = ('a', 1, ('Cmp', 'Sci'))  # Элементом кортежа может быть другой кортеж
empty_tuple = tuple()  # Пустой кортеж

Кортежи занимают меньше памяти, чем списки. Пустой кортеж занимает 24 байта, а список — 40 байт. Размер элемента такой же — 8 байт.

empty_tuple = tuple()
print(empty_tuple.__sizeof__())  # Выведется 24 — размер пустого кортежа

empty_tuple = (1, 2, 'Cmp-Sci')
print(empty_tuple.__sizeof__())  # Выведется 48 — размер пустого кортежа + три элемента

Множества

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

alpha_set = {'a', 'b', 'c', 'd'}
string_set = set('hello')  # все функции пишутся с круглыми скобками
empty_set = set()

frozen_set = frozenset()  # Создали неизменяемое множество

Объявить пустое множество можно только через функцию, иначе получится словарь.
У множеств создано семнадцать встроенных функций, но мы их изучим, если будем что-то делать с множествами.
Чтобы добавить элемент element во множество set: set.add(element), чтобы удалить: set.remove(element).

Цикл for для строк, списков, кортежей и множеств

Цикл for в параметры прокрутки принимает последовательность элементов.
В i записывается ноль, а концом цикла будет число элементов в строке, списке, кортеже или множестве.

numeric_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

# Вывод всех элементов списка
for i in numeric_list:
    print(i) # Выведутся все элементы: от 1 до 15



В следующей статье будет разбор исключений, видов ошибок и их отслеживание.

Типы данных, условия и циклы

В пайтоне 12 основных типов данных: целые, дробные, комплексные числа, логические переменные, байтовые строки и массивы, строки, списки, кортежи, изменяемые и неизменяемые коллекции, и словари. Сейчас разберём целые числа, дробные числа и логические переменные, а в следующей статье разберём последние шесть.
Байтовые строки и массивы будем изучать и использовать в дальнейших статьях.

Любые данные в памяти это последовательность бит: заполненных или пустых. От типа данных зависит порядок хранения. Одинаковая последовательность разных типов данных может хранить разную информацию.

print(chr(68))  # => На экран выведется символ 'D'
print(ord('D'))  # => На экран выведется число 68

Целые числа — тип int

Представляет любое целое число и занимает 24 байта в памяти. Мы писали статью про память компьютера в статьях перед ассемблером.
Пайтон умеет работать с большими числами, например он вычислит 2^63, без ручного расширения памяти. В с++ надо было бы менять тип с intа с вместимостью значения до 2 147 483 647 на unsigned long long int с вместимостью до квадриллиона, число с восемнадцатью нулями.
Это свойство называется длинной арифметикой и она ограничена только объёмом памяти в компьютере.

Дробные числа — тип float

Представляет дробное число и занимает 24 байт в памяти. В языках программирования дробные числа обычно называют числами с плавающей точкой. Плавающая точка — потому что компьютер ставит точку в зависимости от представления числа в системе счисления.

В первом видео, сложение трёх дробей 0.1 считается без сторонних библиотек. Из-за разных систем счисления возникают такие неточности. Для простых программ они незаметны, а для вычислений траекторий ракет не подходят.
Поэтому во втором видео, мы пользуемся библиотекой точных чисел decimal. Показываем откуда будем брать функцию — from decimal и что будем брать — import Decimal.

Махинации над типами и числами

Пайтон динамический язык и в нём не нужно проставлять типы данных.
Но тип может поменяться в двух случаях: явным — это надо программисту и не явным — в результате операций над данными.

Явный перевод

Это нужно программисту на будущее, например, чтобы данные в отчёте были без дробных частей или чтобы достать число из строки.
Перевод из типа в тип делается так: пишется нужный тип и в скобках что нужно переводить.

int(3.14)  # 3. При переводе в целочисленный тип, дробная часть исчезает. 
int(3.99)  # 3
float(3)  # 3.0 При переводе в дробные числа, к числу дописывается ноль.

Неявный перевод

Это когда тип меняется после какой-то операции. Табличка работает только для числовых типов.

Сложение int 2 + float 2.3 Результат float
Вычитание int 2  float 2.3 Результат float
Умножение int 2 * float 2.3 Результат float
Деление с остатком int 2 / float 2.3 Результат float
Деление без остатка int 2 // float 2.3 Результат float
Остаток от деления int 2 % float 2.3 Результат float
Возведение в степень int 2 ** float 2.3 Результат float

Логические переменные

Нужны для проверки правдивости условий. У логической переменной существуют два состояния: True и False.

  • Истина или 1 — переменная правдива. Это работает и при присваивании: logic = True
    и logic = 1 — одинаковые выражения.
  • Ложь или 0 — в переменной ложь. logic = False и logic = 0 — одинаковые выражения.

Однако, когда мы присваиваем logic = 1, тип logic — intовый. Так происходит из-за динамического распознавания в пайтоне. Когда мы захотим использовать logic в логических операциях, тип операции будет bool, а результат intовый.

Логические переменные называют Булевы в честь Джорджа Буля — математика, который ввёл понятие логических переменных.

Логические выражения

Мы уже писали про логические выражения и маски в статье про команды для абстрактного языка.

Основные логические операции: не, и, или, взаимоисключающее или. Они работают только с логическими переменными.

  • Не — меняет значение переменной на противоположное. Было a = True, после команды not станет False.
Переменная A Команда Результат
True not False
False not True
  • И — логическое умножение, по-умному конъюнкция. Две переменные: если обе True — получается True, всё остальное False.
Переменная A Команда Переменная B Результат
True and True True
True and False False
False and True False
False and False False
  • Или — логическое сложение или же дизъюнкция. Две переменные: если одна из них True — получается True, а если обе переменные False — получается False.
Переменная A Команда Переменная B Результат
True or True True
True or False True
False or True True
False or False False
  • Взаимоисключающее или — проверка на совпадение или сложение по модулю два. Если значения отличаются — получается True, а если совпадают — False.
Переменная A Команда Переменная B Результат
True ^ True False
True ^ False True
False ^ True True
False ^ False False

Условия

Условия в языках программирования решают вопрос выбора. Всего условных оператора три

  • if — если что-то, то оно того. В скобках пишем условие: если оно истинно, то код выполняется, если нет — интерпретатор его игнорирует.
  • elif — тот же if, но с другим условием, например нулевое значение для дискриминанта. Пишется только если уже использовался if.
  • else — случай, если условия не подошли ifам и elifам. Для дискриминанта это отрицательное значение.

Для примера, решим квадратное уравнение.

x² + 6x + 9 = 0

На нашем ГитЛабе лежит работающий код, можно скопировать и проверить.

Циклы

Они нужны для выполнения монотонной работы: обработка массивов, заполнение полей, проверки и других цикличных штук.
В пайтоне два вида циклов — по счётчику и с условием.

По счётчику

Объявляется командой for и еще двумя частями: счётчиком и параметрами прокрутки.
Счётчик работает только через переменную и она объявляется локально: когда цикл закончится, её имя можно брать для других задач.

Границы объявляются через команду range c одним, двумя или тремя аргументами

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

С условием

Объявляется командой while с условием выполнения в скобах, «Будешь работать, пока солнце не зайдёт.»
В скобках лежит логическое выражение и если написать условие, которое никогда не станет ложно, то программа зависнет.



В следующей статье разберём строки и списковые типы данных.

Ранее Ctrl + ↓