View on GitHub

Methodics of Python Intro

Practical materials for learning Python

В данной главе мы подробно рассмотрим работу с командной строкой 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  
>>>

Подробнее рассмотрим введённые строки:

Единственный минус интерпретации — обработка всех ошибок в 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  
>>>

Типичное место применения этой операции — циклы по вводу для отслеживания конца ввода или многоступенчатые блоки условий.