В данной главе мы подробно рассмотрим работу с командной строкой Python, обсудим особенности его работы и объекты.
Быстрый поиск
Интерпретатор командной строки
В данной главе разговор пойдёт об очевидном свойстве Python быть интерпретатором командной строки.
Как интерфейс командой строки, Python предлагает классическую форму организации интерактивной среды программирования REPL — Read-Eval-Print Loop. Как и в других REPL-системах, здесь поток управления и поток данных объединены: на один выходной интерфейс выводятся как данные, так и диагностические сообщения, и, соответственно, с одного входного интерфейса передаются как команды, так и данные.
Прелесть работы с такими интерактивными интерпретаторами языков программирования очевидна. Из свойств самого интерпретатора (IRL-обработка потока ввода и выполнение команд) следует, что формат работы через построчный ввод в командную строку не ограничивает возможностей языка. Более того, к этим возможностям добавляются фишки по работе с самой командной строкой — история команд, их редактирование, поиск по истории и т.д.
Командная строка также позволяет наглядно изучить последовательность операций интерпретатора (лексический анализ, синтаксический анализ, семантический анализ, исполнение):
>>> qwe qwe qwe
File "<python-input-0>", line 1
qwe qwe qwe
^^^
SyntaxError: invalid syntax
>>> qwe
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
qwe
NameError: name 'qwe' is not defined
>>>
>>> 4+5
9
>>>
Подробнее рассмотрим введённые строки:
- qwe qwe qwe — лексический анализатор считал эти элементы корректно, посчитав их лексемами идентификаторов (переменных); синтаксический анализатор же распознал некорректность введённой структуры (не может существовать строки с двумя подряд идущими переменными), соответствующая ошибка SyntaxError сообщает о нарушении
- qwe — лексический анализатор распознал одну переменную, синтаксически конструкция из одной переменной корректна (в случае написания программ в файле такая строка не выполнит ничего, в случае режима командной строки автоматически вернётся значение выражения, т.е. просто значение переменной); при выполнении семантического анализа для вывода интерпретатор проверяет, в какую область памяти для вывода значения ему необходимо будет обратиться, и здесь натыкается на проблему — несуществующую переменную, о чём и говорит ошибка NameError
- 4+5 — Для этого выражения корректно отрабатывают все анализаторы, после чего оно просто выполняется и возвращает значение суммы двух чисел
Единственный минус интерпретации — обработка всех ошибок в RunTime. Справляться с этим помогает только грамотная дисциплина разработки или статическая типизация, но от реального Python даже MyPy находится слишком далеко.
Вообще вариантов реализации Python довольно много. Говоря о надстройках над cmdline-интерпретатором нельзя не упомянуть ptpython, bpython, ipython. Если же погружаться в реализации, то основной Python написан на Си, однако существуют и другие варианты: Brython — Python на JavaScript, работающий в браузере, PyPy — Python? написанный на Python. Активно развиваются встроенные интерпретаторы для работы на микроконтроллерах.
Объекты и выражения с ними
Объекты в Python это буквально всё. При выполнении команд они создаются и удаляются по мере использования или работы со связанными с ними именами (об этом будет подробнее сказано в следующем разделе).
>>> 300 + 200
500
>>>
В данном примере создано и удалено 3 объекта. Ссылок на них нигде добавлено не было, поэтому после использования память от них очищается.
Данный пример показал очевидное наличие целочисленных объектов в Python. Вещественных чисел, как и во всех ЯП, естественно, не существует (поскольку действительно бесконечные числа создать невозможно), есть лишь их рациональная модель, позволяющая совершать операции с какой-то точностью. Именно из-за рационализации могут возникать неожиданные результаты вычислений:
>>> 300 + 200.
500.0
>>> 1.1 + 2.2
3.3000000000000003
>>>
Однако Python позволяет хранить числа любого размера, не ограничиваясь фиксированными границами, как в Cи.
>>> 20**20
104857600000000000000000000
>>> 20**2000
11481306952742545242328332011776819840223177020886952004776427368257662613923703138566594863165062699184459646389874627734471189608630553314259313561666531853912998914531228000068877914824004487142892699006348624478161546364638836394731702604046635397090499655816239880894462960562331164953616422197033268134416890898445850560237948480791405890093477650042900271670662583052200813223628129176126788331720659899539641812702177985840404215985318325154088943390209192055495778358967203916008195721663058275538042558372601552834878641943205450891527578388262517543552880082284277081796545376218485114902937600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
>>>
Благодаря этому работа в вещественными числами возможна с практически любой точностью при применении специальных типов данных, например, fractions.Fration, позволяющего работать с обычными дробями.
>>> from fractions import Fraction
>>> A = Fraction(1, 3)
>>> A
Fraction(1, 3)
>>> A + 3
Fraction(10, 3)
>>> float(A+3)
3.3333333333333335
>>>
Как и множество других ЯП, Python кроме арифметических операций поддерживает битовые (|, &, <<, >>), а также операции сравнения. Из особых операций в Python из коробки идёт @ — матричное умножение. В базовых объектах поддержки __matmul__ (метода, отвечающего за эту операцию) нет, она добавлена для дополнительных модулей и «на вырост», если в базовый Python добавят объекты с поддержкой этой операции.
Кроме классических объектов Python поддерживает работу с последовательностями. Подробное их обсуждение будет в будущих главах, сейчас же заметим, что для них есть отдельный класс операций, например, взятие среза последовательности. Среди последовательностей отдельное место занимают строки, о которых подробнее будет рассказано также в будущих главах.
>>> "qwertyui"
'qwertyui'
>>> "qwertyui" + "gfds"
'qwertyuigfds'
>>> "qwertyui" * 4
'qwertyuiqwertyuiqwertyuiqwertyui'
>>> ("qwertyui" * 4)[3:10]
'rtyuiqw'
>>>
>>> ("as", 5, "qwert", 3.3)
('as', 5, 'qwert', 3.3)
>>> ("as", 5, "qwert", 3.3)[2:4:2]
('qwert',)
>>>
Ещё один интересный для рассмотрения набор объектов — составные объекты. Сюда входят как некоторые последовательности, так и, например, множества и словари. Для этих объектов в Python поддерживается специальная операция проверки принадлежности объектов in.
>>> a = ("as", 5, "qwert", 3.3)
>>> 5 in a
True
>>> 345 in a
False
>>>
Равенство и идентичность объектов
Рассмотрим эти два понятия поближе. В Python все объекты можно сравнивать не только по соответствию значений (операция ==, проверка равенства), но и по совпадению самих объектов, к которым ведётся обращение. Для этого используется специальный оператор is, который сравнивает уникальные идентификаторы объектов (сам идентификатор можно получить функцией id() ):
>>> a = b = 12345
>>> a == b
True
>>> a is b
True
>>> id(a), id(b)
(139889244431632, 139889244431632)
>>>
Равенство объектов, вообще говоря, не гарантирует их идентичность. Исключением являются лишь некоторые константные объекты, всегда находящиеся в памяти.
>>> c = 12345
>>> a == c
True
>>> a is c
False
>>> id(a), id(b), id(c)
(139889244431632, 139889244431632, 139889244430800)
>>>
Особенности типов и функций
При разговоре о типах необходимо напомнить, что в Python строгая типизация. Типы присущи объектам, а не переменным, указывающих на эти объекты. Для исследования типов объектов существует специальная функция type(), возвращающая конструктор объекта-аргумента.
>>> type(345)
<class 'int'>
>>> type(345.)
<class 'float'>
>>>
Сам объект, который возвращает type, тоже имеет тип — <class 'type'>. Подробнее про такую рекурсию типов мы поговорим в главе про метаклассы Python, а пока убедимся, что возвращаемый объект действительно может выступать в роли конструктора для объектов:
>>> c = type(345.)
>>> c
<class 'float'>
>>> type(c)
<class 'type'>
>>> c(234)
234.0
>>>
На данной особенности типов построена работа функций в Python. Они представляют из себя именованные алгоритмы и работают для любых объектов, которые способны выполнить указанные операции.
Рассмотрим операцию умножения:
>>> 3 * 8
24
>>>
В процессе семантического анализа этого выражения определяется наличие у первого операнда специального метода __mul__, отвечающего за умножение этого объекта на другой. Если второй операнд подходит в качестве аргумента к этому методу, выражение корректно исполняется, иначе операция возвращает NotImplemented, обозначающее невозможность проведения данной операции с указанным аргументом.
>>> (3).__mul__(8)
24
>>> (3).__add__(8)
11
>>> (3).__add__("qwer")
NotImplemented
>>>
Поскольку некоторые операции между объектами некоммутативны (например, строку на число умножить можно, а число на строку — нет), при анализе выражений с ними Python проверяет возможность «обращения операции», для которых используются отдельные методы «правых операций».
>>> a = 4
>>> b = "QWE"
>>> a * b
'QWEQWEQWEQWE'
>>> a.__mul__(b)
NotImplemented
>>> dir(b)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith','expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
>>> b.__rmul__(a)
'QWEQWEQWEQWE'
>>>
Аналогично работают функции, являющиеся, по сути, множеством операций над полученными операндами. Благодаря этому функции ограничены не в типах принимаемых объектов, а в методах аргументов при непосредственном вызове функции с ними.
>>> def addm(a, b):
... return a + b * 2
...
>>> addm(2, 3)
8
>>> addm("2", "3")
'233'
>>> addm((2, 3, 4), (5,6,7))
(2, 3, 4, 5, 6, 7, 5, 6, 7)
>>> addm((2, 3, 4), [4,5, 6])
Traceback (most recent call last):
File "<python-input-54>", line 1, in <module>
addm((2, 3, 4), [4,5, 6])
~~~~^^^^^^^^^^^^^^^^^^^^^
File "<python-input-50>", line 2, in addm
return a + b * 2
~~^~~~~~~
TypeError: can only concatenate tuple (not "list") to tuple
>>>
Функции также являются объектами Python. Объекты, поддерживающие метод __call__, срабатываемый по круглым скобочкам (<object>(…args…)), называются callable-объектами. Функции как раз относятся к таким наряду, например, с объектами классов.
>>> from random import randrange
>>> flist = addm, randrange
>>> flist
(<function addm at 0x7f3a80bc3ce0>, <bound method Random.randrange of <random.Random object at 0x55ad3c
45ec80>>)
>>> flist[0]
<function addm at 0x7f3a80bc3ce0>
>>> flist[1](2, 5)
4
>>> flist[1](2, 5)
4
>>> flist[1](2, 5)
2
>>> flist[1](2, 5)
4
>>> flist[1](2, 5)
4
>>> flist[1](2, 5)
>>> callable(addm)
True
>>> callable(a)
False
>>>
Связывание объектов
Начать этот раздел необходимо с повторения уже ставшей очевидной истины: в Python переменных в общепринятом понимании не существует. Здесь это не фиксированные именованные области памяти, а лишь ссылки на создающиеся в памяти объекты. Данная операция присвоения имени объектам называется связывание. Объекты связываются именами аналогично жёстким ссылкам в файловой системе: у объекта может быть множество связей, при удалении всех объект в какой-то момент времени удаляется при очистке кеша.
>>> a = b = (1, 2, 5, 8)
>>> a
(1, 2, 5, 8)
>>> b
(1, 2, 5, 8)
>>> a is b
True
>>>
>>> c = a
>>> c is b
True
>>>
>>> dir()
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
>>> a = 123
>>> dir()
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a']
>>> b = a
>>> dir()
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b']
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': '_pyrepl', '__loader__': None, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/usr/lib/python3.13/_pyrepl/__main__.py', '__cached__': '/usr/lib/python3.13/_pyrepl/__pycache__/__main__.cpython-313.pyc', 'a': 123, 'b': 123}
>>>
Явно просмотреть количество ссылок на один и тот же объект можно с помощью специализированных функций. Главное, не забыть, что при их вызове создаётся ещё одна связь в виде переданного аргумента.
>>> import sys
>>> a = b = 100500
>>> sys.getrefcount(a)
3
>>> del(a)
>>> sys.getrefcount(b)
2
>>> a = b
>>> del b
>>> sys.getrefcount(a)
2
>>>
Одной из важных фишек Python является множественное связывание. Поскольку связывание представляет из себя просто закрепление ссылки за объектом, нет никакой проблемы единовременно произвести перезакрепление ссылок нескольких объектов. В частности, это сильно упрощает работу с передачей данных между переменными; классическая задача «поменять местами два числа» становится элементарной:
>>> a = 3
>>> b = 2
>>> a
3
>>> b
2
>>> a, b = b, a
>>> a
2
>>> b
3
>>>
>>> a, b, c = range(5, 8)
>>> a
5
>>> b
6
>>> c
7
>>>
Множественное связывание позволяет работать с множественными возвращаемыми значениями функций. Также поддерживается связывание с возможностью упаковки избыточных (или недостающих ко количеству) элементов в списки.
>>> def fun(a, b):
... return a+2, b*3
...
>>> fun(3, 4)
(5, 12)
>>> a, b, *t, c = range(5, 18)
>>> a
5
>>> b
6
>>> c
17
>>> t
[7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
>>>
До этого момента мы работали с исключительно константными объектами. С данными объектами оператор присваивания работает исключительно для связывания. Другую логику работы обеспечивают модифицируемые объекты, частным представителем которых является список — модифицируемая последовательность.
Для него оператор присваивания может использоваться не только для связывания объектов, но и для присваивания:
>>> a = [1, 2, 3]
>>> b = a
>>> b[2] = 100500
>>> a
[1, 2, 100500]
>>>
Для модифицируемых объектов есть отдельный набор операций аугментации вида ∀=, позволяющий именно изменять объекты, а не пересвязывать имена:
Шутка☺
>>> a = 'dva'
>>> a += 'dva'
>>> a
'chetire'
>>>
Рассмотрим пример работы операции += с константным и модифицируемым объектом. Изменение значения в первом случае сопровождается изменением объекта, а во втором случае объект остаётся тем же:
>>> a = 1
>>> id(a)
140200318538544
>>> a += 3
>>> a
4
>>> id(a)
140200318538640
>>> a = [1, 2, 3]
>>> id(a)
140200301580544
>>> a += [4]
>>> a
[1, 2, 3, 4]
>>> id(a)
140200301580544
>>>
Обычные операции даже с модифицируемыми объектами вызывают создание новых и пересвязывание имён:
>>> a = b = [1, 2, 3]
>>> a = a + [4]
>>> a is b
False
>>> a
[1, 2, 3, 4]
>>> b
[1, 2, 3]
>>>
Особое место в вопросе связывания занимает операция связывания := (т.н. «walrus operator»), позволяющая проводить связывание объектов внутри выражений.
>>> a = (b := 3) +1
>>> a
4
>>> b
3
>>>
Типичное место применения этой операции — циклы по вводу для отслеживания конца ввода или многоступенчатые блоки условий.