Перейти к содержанию

Лекция 1. Синтаксис, переменные и типы данных

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

Основные свойства Python

  • Динамическая типизация: тип переменной определяется значением, тип может меняться от присваивания к присваиванию.
  • Регистрозависимый: var, Var, VAR — три разные переменные.
  • Объектно-ориентированный: всё — объект, включая числа и функции.
  • Интерпретируемый: код выполняется построчно интерпретатором (CPython), без отдельной фазы компиляции в машинный код.

Базовые свойства Go отличаются:

  • Статическая типизация: тип переменной известен в момент компиляции, поменять его нельзя.
  • Регистрозависимый (как Python).
  • Компилируемый: go build создаёт исполняемый бинарник.
  • Структурный + интерфейсы (об ООП — в теме 6).

Лексика

Программа состоит из последовательности лексем:

  • комментарии — поясняют код, интерпретатором пропускаются;
  • литералы — записанные значения (5, 3.14, "строка");
  • знаки пунктуации+, *, ,, скобки;
  • идентификаторы — имена переменных, функций, классов;
  • ключевые слова — зарезервированные имена языка.

Комментарии

# Однострочный — начинается с #
x = 5  # inline-комментарий, два пробела перед #

"""
Многострочный комментарий
через тройные кавычки — на самом деле это
литерал строки, который никуда не присваивается.
"""

В Go:

// Однострочный комментарий

/*
Многострочный
блок.
*/

Комментарии должны объяснять почему что-то реализовано так, а не что делает код — код уже это показывает.

Идентификаторы

Правила (Python):

  • состоит из букв (ASCII или Unicode), цифр, _;
  • не начинается с цифры;
  • регистрозависимый (UserName ≠ username);
  • не совпадает с ключевым словом.

Соглашения PEP 8:

  • snake_case для переменных и функций: user_name, read_file;
  • PascalCase для классов: UserAccount;
  • UPPER_SNAKE для констант: MAX_RETRIES = 5;
  • одно подчёркивание в начале (_private) — внутреннее имя;
  • два подчёркивания в начале (__name) — name mangling в классах;
  • __double__ — служебные имена Python (не пишем сами).

В Go:

  • camelCase для приватных, PascalCase для экспортируемых:

    var counter int        // приватный
    var MaxRetries = 5     // экспортируемый
    func process() {}      // приватный
    func Process() {}      // экспортируемый
    
  • регистр имени = модификатор видимости (заглавная = экспорт).

Ключевые слова Python

False    None     True     and      as       assert
async    await    break    class    continue def
del      elif     else     except   finally  for
from     global   if       import   in       is
lambda   nonlocal not      or       pass     raise
return   try      while    with     yield    match  case

Имена в этом списке использовать как идентификаторы нельзя — IDE подсвечивает их цветом.

Для проверки в рантайме:

import keyword
print(keyword.iskeyword("for"))   # True

В Go список короче (25 ключевых слов): break, case, chan, const, continue, default, defer, else, fallthrough, for, func, go, goto, if, import, interface, map, package, range, return, select, struct, switch, type, var.

Синтаксис: отступы

Главная особенность Python — блоки кода обозначаются отступами, а не скобками.

if x > 0:
    x = 0       # тело if (4 пробела)
    y = 0
else:
    x = y = 1

Соглашение PEP 8: 4 пробела на уровень. Можно табы — но смешивать в одном файле нельзя.

В Go блоки — { }, отступы рекомендуются, но не обязательны:

if x > 0 {
    x = 0
    y = 0
} else {
    x, y = 1, 1
}

Конец строки = конец инструкции

a = 1
b = 2
print(a, b)

Точка с запятой не нужна. Можно разделять несколько инструкций в одной строке — но это плохой стиль:

a = 1; b = 2; print(a, b)   # стилистически плохо

В Go точки с запятой ставит компилятор автоматически между строками — писать их руками не нужно (и не принято).

Перенос длинной строки

В Python используют скобки — внутри (), [], {} строки можно продолжать без специальных символов:

total = (some_long_variable_name
         + another_long_one
         + third_one)

users = [
    "Аня",
    "Боря",
    "Ваня",
]

Или явный перенос через \ (используется реже):

with open("/path/to/input") as input_file, \
        open("/path/to/output", "w") as output_file:
    output_file.write(input_file.read())

Простые типы данных

Целые числа (int)

В Python 3 целые числа неограниченной разрядности:

big = 2 ** 1000  # работает, никакого переполнения

Операции:

Оператор Описание
+, -, * сложение, вычитание, умножение
/ деление (всегда float: 5 / 2 == 2.5)
// целочисленное деление (5 // 2 == 2)
% остаток (5 % 2 == 1)
** возведение в степень (2 ** 10 == 1024)
abs(x) модуль
divmod(x, y) возвращает (x // y, x % y)
pow(x, y, [z]) x ** y (опционально по модулю z)

В Go целые — фиксированной разрядности:

Тип Размер Диапазон
int8 / uint8 1 байт -128..127 / 0..255
int16 / uint16 2 байта
int32 / uint32 4 байта
int64 / uint64 8 байт
int / uint размер слова платформы (32 или 64 бит)
byte алиас uint8
rune алиас int32 (Unicode code point)

Из-за фиксированной разрядности возможно переполнение (overflow): int8(127) + 1 == -128. Для длинной арифметики используют math/big.

Системы счисления

# Литералы в разных системах
0b10011    # двоичная — 19
0o23       # восьмеричная — 19
0x13       # шестнадцатеричная — 19
0x1F       # 31
1_000_000  # подчёркивания как разделители (читаемее)

# Преобразование
int("19")          # 19
int("10011", 2)    # 19 (из двоичной)
int("FF", 16)      # 255 (из hex)
bin(19)            # '0b10011'
hex(255)           # '0xff'
oct(8)             # '0o10'

В Go то же самое:

const a = 0b10011  // 19
const b = 0o23     // 19
const c = 0x13     // 19
const d = 1_000_000

n, _ := strconv.ParseInt("FF", 16, 64) // 255
s := strconv.FormatInt(255, 16)        // "ff"

Вещественные числа (float)

В Python float — это IEEE 754 double (64 бита).

x = 3.14
y = 1.5e-3   # 0.0015
z = 1.5E10   # 1.5 × 10^10

Главная ловушка — неточность:

0.1 + 0.1 + 0.1
# 0.30000000000000004 — не 0.3!

Для финансовых расчётов используют decimal.Decimal:

from decimal import Decimal

Decimal("0.1") + Decimal("0.1") + Decimal("0.1")
# Decimal('0.3') — точно

Или fractions.Fraction для точной арифметики дробей:

from fractions import Fraction
Fraction(1, 3) + Fraction(1, 3)  # Fraction(2, 3)

В Go тоже есть float32 / float64 с теми же проблемами; для точности — math/big.Float или decimal-пакеты из экосистемы (shopspring/decimal).

Комплексные числа

В Python они встроены:

z = 1 + 2j
print(z.real, z.imag)   # 1.0 2.0
print(abs(1 + 2j))      # 2.23606...

В Go: complex64 / complex128:

z := complex(1, 2)   // 1+2i
fmt.Println(real(z), imag(z))

Строки (str)

В Python 3 строки — Unicode. Литералы:

s1 = 'apostrophe'
s2 = "quotes"
s3 = '''многострочная
строка'''
s4 = """тоже многострочная"""

# Сырая строка — экранирование отключено
path = r"C:\Users\new\folder"   # обратные слэши не интерпретируются

# Байтовая строка
b = b"bytes"                     # тип bytes, не str

# F-строка с интерполяцией (см. лекцию 4 темы 2)
name = "Аня"
greeting = f"Привет, {name}!"

Экранированные последовательности внутри обычных строк:

Последовательность Значение
\n перевод строки
\t табуляция
\r возврат каретки
\\ обратный слэш
\' апостроф
\" кавычка
\xhh байт с кодом hh
\uhhhh Unicode символ (16-bit)
\Uhhhhhhhh Unicode символ (32-bit)

Базовые операции:

"spam" + "eggs"   # "spameggs" — конкатенация
"ha" * 3          # "hahaha" — повторение
len("spam")       # 4
"spam"[0]         # 's' — индексация
"spam"[-1]        # 'm' — отрицательные индексы от конца
"spam"[1:3]       # 'pa' — срез
"spam"[::-1]      # 'maps' — реверс

В Go строки — это immutable []byte (UTF-8):

s := "Привет"
fmt.Println(len(s))        // 12 — БАЙТ! Не символов.
fmt.Println(s[0])          // 208 — байт, не код символа.

// Для итерации по «символам» (рунам) — for range:
for i, r := range s {
    fmt.Printf("%d: %c\n", i, r)
}

Это важное отличие — подробнее в лекции 4 о кодировках.

Булевы значения

flag = True
empty = False

# Truthy / falsy — что считается ложью при if:
# False, None, 0, 0.0, "", [], (), {}, set() — falsy
# Всё остальное — truthy

if []:           # не выполнится — пустой список falsy
    print("never")
if [0]:          # выполнится — непустой список truthy
    print("yes")

В Go bool — отдельный тип, 0/nil/пустые строки не считаются ложью:

var x int
if x {  // ОШИБКА компиляции: non-bool x used as if condition
    ...
}
if x != 0 {  // правильно
    ...
}

None

Специальное значение — «ничего», аналог null в других языках:

result = None

if result is None:     # проверка через is, не ==
    print("нет результата")

В Go nil похож, но допустим только для указателей, слайсов, мап, каналов, функций, интерфейсов:

var p *int
if p == nil {
    fmt.Println("nil pointer")
}

Объявление переменных

В Python переменные объявляются простым присваиванием:

x = 5
name = "Аня"
items = [1, 2, 3]

# Множественное присваивание
a, b, c = 1, 2, 3

# Swap без временной переменной
a, b = b, a

С Python 3.6 можно (и желательно) добавлять аннотации типов:

x: int = 5
name: str = "Аня"
items: list[int] = [1, 2, 3]

В Go объявление более строгое:

// Полная форма
var x int = 5
var name string = "Аня"

// С выводом типа
var x = 5
var name = "Аня"

// Краткая форма (только внутри функций)
x := 5
name := "Аня"

// Множественное
a, b, c := 1, 2, 3
a, b = b, a

Не использованная переменная в Go — ошибка компиляции. В Python — ничего не происходит (только linter может предупредить).

Операторы

Арифметические

Оператор Python Go Пример
Сложение + + 2 + 3
Вычитание - - 5 - 2
Умножение * * 4 * 3
Деление / (всегда float) / (тип сохраняется) 5 / 22.5 (Py), 2 (Go, если int)
Целочисленное деление // / для int 5 // 22
Остаток % % 5 % 21
Степень ** нет (есть math.Pow) 2 ** 10

Сравнения

Оператор Значение
== равно
!= не равно
< / > меньше / больше
<= / >= меньше или равно / больше или равно
is / is not тождество (один и тот же объект)
in / not in принадлежность (для строк, списков, словарей и т.д.)

== сравнивает значения, isидентичность объектов (id(a) == id(b)):

a = [1, 2, 3]
b = [1, 2, 3]
c = a

a == b     # True (значения равны)
a is b     # False (разные объекты)
a is c     # True (один и тот же объект)

is None — каноническая проверка на None. Никогда не используйте == None.

Цепочки сравнений

В Python (но не в Go) можно писать:

if 0 < x < 100:           # тоже, что 0 < x and x < 100
    print("в диапазоне")

Логические

Python Go Значение
and && логическое И
or \|\| логическое ИЛИ
not ! логическое НЕ

and и or в Python — короткозамкнутые и возвращают значение, а не bool:

x = None or "default"   # "default"
y = 0 or "x"            # "x" — 0 falsy
z = "a" and "b"         # "b" — оба truthy, вернёт последний

В Go &&/|| тоже короткозамкнутые, но всегда возвращают bool.

Побитовые

Оператор Значение
& побитовое И
\| побитовое ИЛИ
^ побитовое исключающее ИЛИ (XOR)
~ побитовая инверсия
<< сдвиг влево
>> сдвиг вправо
a = 0b1100  # 12
b = 0b1010  # 10

a & b   # 0b1000 — 8
a | b   # 0b1110 — 14
a ^ b   # 0b0110 — 6
~a      # -13 (инверсия + знаковый бит)
a << 2  # 48
a >> 1  # 6

Аналогично в Go (но без оператора ~ — используют ^x):

a := 0b1100
b := 0b1010

c := a & b
d := a ^ 0xFF  // инверсия младших 8 бит

Присваивание с операцией

Оператор
+=, -=, *=, /=, //=, %=, **=
&=, \|=, ^=, <<=, >>=
n = 10
n += 5    # n = 15
n *= 2    # n = 30

В Python нет ++ и --. В Go есть, но только как statement, не как выражение:

n := 10
n++       // ok
// x := n++  // ОШИБКА: n++ — не выражение

Walrus := (Python 3.8+)

«Моржовый оператор» — присваивание внутри выражения:

if (n := len(data)) > 100:
    print(f"слишком много элементов: {n}")

В Go := — это вообще объявление переменной с выводом типа (не путать).

Приоритет операторов

От наивысшего к наинизшему (Python):

  1. **
  2. +x, -x, ~x (унарные)
  3. *, /, //, %
  4. +, - (бинарные)
  5. <<, >>
  6. &
  7. ^
  8. |
  9. ==, !=, <, >, <=, >=, is, is not, in, not in
  10. not
  11. and
  12. or

При сомнениях — ставьте скобки, это короче, чем потом разбираться с багом.

Кодировка исходника

По стандарту PEP 3120 файлы Python — UTF-8. Это значит, что в коде можно писать русские идентификаторы (хотя это плохой стиль), комментарии на любом языке, строки с любыми Unicode-символами:

# Это файл в UTF-8
имя_пользователя = "Аня"          # технически работает
print("Π = 3.14")                  # тоже работает

В Go исходники тоже UTF-8.

Структура файла

Типичный Python-файл:

#!/usr/bin/env python3
"""Краткое описание модуля.

Подробное описание, если нужно.
"""

from __future__ import annotations  # для современного типизирования

import os
import sys
from pathlib import Path

import requests       # сторонние пакеты

from .utils import helper  # локальные импорты

MAX_RETRIES = 5
DEFAULT_TIMEOUT = 30


def main() -> int:
    """Точка входа."""
    return 0


if __name__ == "__main__":
    sys.exit(main())

PEP 8 рекомендует:

  • две пустые строки между функциями верхнего уровня и классами;
  • одна пустая строка между методами класса;
  • импорты — в начале файла, в трёх группах (stdlib, сторонние, локальные).

Сравнение Python ↔ Go

Аспект Python Go
Типизация динамическая статическая
Аннотации типов опциональны (x: int) обязательны
Размер int неограничен фиксированный (32/64)
Деление / всегда float целое если оба int
Логические операторы and/or/not &&/||/!
Возвращают bool? or/and — нет, значение да
++ / -- нет есть (только statement)
Цепочки сравнений 0 < x < 100 нет
Walrus := да := — объявление переменной
Блоки отступы { }
Точки с запятой не нужны вставляет компилятор
Неиспользуемая переменная warning ошибка компиляции

Контрольные вопросы

  • Чем динамическая типизация Python отличается от статической в Go? В чём плюсы и минусы каждой?
  • Почему в Python 0.1 + 0.1 + 0.1 != 0.3 и как этого избежать в финансовых расчётах?
  • В чём разница между == и is? Когда применять каждый?
  • Что делает or и and в Python — возвращают bool или значение? Приведите пример.
  • Почему n++ в Python не работает, и зачем это сделано?
  • Какие есть способы записать число 19 как целочисленный литерал?
  • Чем строка в Python отличается от строки в Go по внутреннему представлению?
  • Какое значение имеет walrus-оператор := в Python и почему его не было в более ранних версиях?
  • Что такое PEP 8 и зачем разработчику его читать?
  • В чём смысл if __name__ == "__main__": — и есть ли аналог в Go?