1. Лекция 2
28 сентября 2018 г.
Заметили ошибку или есть предложение? Напишите на почту: romansdidnotcrucify@gmail.com
Содержание
2. Функции
Внезапно, мы начнём говорить о функциях ещё до того, как возникла тема условных операторов и циклов. Так сделано в учебнике, на котором основан курс, и вообще идея интересная: рассказывать про повторное использование кода до того, как рассказывать о том, как этот код писать. Тем более что мы с вами уже знакомы с понятием пространства имён, а что такое функция в питоне как не динамическое создание пространства имён?
2.1. Особенности понятия функции в Python
В большинстве языков программирования функцией (в большей или меньшей степени) считается некая математизированная абстракция, преобразование данных одного конкретного типа в данные другого конкретного типа. В питоне функция - нечто совсем другое.
В питоне функция - это именованная запись алгоритма. (выполняемого над объектами)
Что это за объекты - вообще говоря, всё равно; лишь бы эти операции на них выполнялись. Вспоминаем утиную (неявную динамическую) типизацию.
2.2. (Встроенные) функции
Некоторые из них мы с вами уже видели. Например, функция print:
Она выводит через пробел какие-нибудь питоновские объекты:
Обратите внимание, что, как и полагается в питоне, любой объект может преобразован в строку и выведен, даже функция:
Мы уже знаем и другие примеры функций:
Как видите, min - тоже функция, причём встроенная (built-in). Она лежит в каком-то волшебном пространстве имён:
1 >>> dir(__builtins__) # dir(), напоминаю, показывает содержимое пространства имён. __builtins__ - ну очень волшебный namespace
2 ['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
3
4 # Видим, что здесь и min, и print (ищите ближе к концу).
5 # Это, кстати, пространство имён встроенных объектов, которые есть всегда.
6 # Добрую половину из них занимают исключения (см. верхнюю часть вывода выше).
7 # Изрядная часть того, что есть внизу - преобразования типов к разным встроенным питоновским типам.
8 # Остаются как раз те функции, про которые мы с вами разговариваем.
9
Обсуждали в прошлый раз help - это тоже функция, как и dir:
1 >>> help()
2
3 Welcome to Python 3.6's help utility!
4
5 If this is your first time using Python, you should definitely check out
6 the tutorial on the Internet at https://docs.python.org/3.6/tutorial/.
7
8 Enter the name of any module, keyword, or topic to get help on writing
9 Python programs and using Python modules. To quit this help utility and
10 return to the interpreter, just type "quit".
11
12 To get a list of available modules, keywords, symbols, or topics, type
13 "modules", "keywords", "symbols", or "topics". Each module also comes
14 with a one-line summary of what it does; to list the modules whose name
15 or summary contain a given string such as "spam", type "modules spam".
16
17 help> q
18
19 You are now leaving help and returning to the Python interpreter.
20 If you want to ask for help on a particular object directly from the
21 interpreter, you can type "help(object)". Executing "help('string')"
22 has the same effect as typing a particular string at the help> prompt.
23 >>> dir # Посмотрим на тип dir
24 <built-in function dir>
25
Есть функции преобразования числа в различные представления (обратите внимание, что возвращается строка):
Замечу, что работает и обратное преобразование:
2.3. Callables (вызываемые объекты)
В питоне возможность вызывать нечто не прибита гвоздями к тому, что это нечто - функция. Например, преобразование типов - вызываемый объект, однако оно не является функцией; это класс:
Для проверки того, что объект вызываемый (callable), что его можно вызвать, применяется специальная функция callable:
Кстати, знаете какого типа используемое выше type?
Это класс. Из чего следует довольно забавный факт: type() возвращает нам не строчку с именем типа, а сам тип:
Позже этот факт нам очень пригодится при изучении объектной модели питона.
2.4. Как задаётся функция
А очень просто - указывается ключевое слово def, за ним - имя функции, затем в скобках через запятую - формальные параметры, и двоеточие:
1 >>> def fun(a, b): # Тело пока не завезли
Затем на последующих строках делается обязательный отступ в 4 пробела. Он обозначает, что мы всё ещё находимся в теле функции. Уберём в строчке отступ - из тела функции вышли. Поставили не 4 пробела - получим ошибку. Допишем функцию:
1 >>> def fun(a, b): # Частая ошибка программистов на C/C++, изучающих python - забывать про двоеточие
2 ... return a*2 + b # Обратите внимание, подсказка изменилась с '>>>' на '...'; интерпретатор понимает, что мы продолжаем вводить команду
3 ... # Не ставим пробелов, просто жмём Enter
4 >>> dir() # Убедимся, что fun теперь есть в пространстве имён
5 ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'c', 'fun']
6 >>> fun # И что это функция
7 <function fun at 0x0000025B8E53E268>
8 >>> type(fun) # В свою очередь, "функция" в питоне - это класс...
9 <class 'function'>
10 >>> fun(3,7) # Наша новая функция вполне работает
11 13
12
2.5. Что происходит на самом деле
Рассказывать подробно, что такое формальные и фактические параметры, я не буду. Вот как устроена работа с ними в питоне:
- объявили функцию;
- в момент, когда мы в неё входим, создаётся локальное пространство имён (по умолчанию оно пустое, содержит только некоторые вспомогательные штуки);
- поскольку у нашей функции два параметра, в этом пространстве имён сразу заводятся два имени - a и b;
- пока происходит выполнение функции, в этом локальном пространстве доступны как глобальные имена, объявленные извне функции, так и локальные (в данном случае - a и b);
когда возникает необходимость выполнить return (выйти из функции), подготавливается возвращаемое значение (результат выражения справа от return). Это значение - единственный из локальных объектов, который выживет после окончания функции; (естественно, если на остальные локальные объекты не было ссылок извне);
имена локальных объектов (a и b) удалятся, и сами локальные объекты, если на них не было ссылок извне, удалятся тоже.
Единственным гарантированно выжившим объектом - возвращаемым функцией значением - потом можно пользоваться:
Фактическим параметром функции может выступать и выражение - ведь его результат - обычный питоновский объект:
Возвращаемым функцией значением может быть произвольный объект, составной тоже. Поэтому
Как вернуть из функции несколько объектов? - Верните их кортеж:
2.6. Баг или фича
Во время лекции я допустил очепятку в описании функции swap; повторю её здесь, чтобы продемонстрировать лишний раз динамическую природу языка.
1 >>> def swap(a,b):
2 ... return b. a # Я поставил точку вместо запятой
3 ...
4 >>> swap(3,8)
5 Traceback (most recent call last):
6 File "<stdin>", line 1, in <module>
7 File "<stdin>", line 2, in swap
8 AttributeError: 'int' object has no attribute 'a' # И функция не заработала, потому что у '8' нет поля 'a', а оно требуется в fun
Однако давайте, забегая вперёд, создадим такой объект, для которого функция работает:
В зависимости от того, какая у вас реальная обстановка в пространстве имён (в том числе в пространстве имён объекта, который вы передали в функцию), функция может работать, а может и не работать.
2.7. Распаковка кортежей
Возвращаемый функцией кортеж можно распаковать, т.е. разобрать на те части, из которых он составлен:
Впрочем, использовать в этом примере swap было вовсе не обязательно, ведь благодаря множественному связыванию можно просто написать:
2.8. И ещё пару слов об утиной типизации
Посмотрим, как работает утиная типизация, на примере описанной нами функции fun:
1 >>> fun(1,2) # Числа можно умножать на число и складывать
2 4
3 >>> fun("Jo", "'s bizarre adventure") # Строки можно умножать на число и складывать
4 "JoJo's bizarre adventure"
5 >>> fun(5, "S") # А вот складывать число и строку уже нельзя
6 Traceback (most recent call last):
7 File "<stdin>", line 1, in <module>
8 File "<stdin>", line 2, in fun
9 TypeError: unsupported operand type(s) for +: 'int' and 'str'
При этом не забываем, что типизация в питоне - строгая! В любой момент про каждый объект мы можем точно сказать, какого он типа (хоть и не можем этого сделать до выполнения программы).
Для выполнения какой-либо операции требуется лишь, чтобы эта операция была определена для выбранных объектов. Если умножение строки на число работает, то это потому, что оно определено. Если сложение строк работает, то это потому, что оно определено.
При этом сделать так, чтобы операция была выполнима над данными объектами (и делала то, что нужно!) - задача программиста.
Есть у программиста дисциплина программирования - всё работает. Нет дисциплины - валится exception.
2.9. О текстовых редакторах и запуске программ
Когда вы изучаете какой-то язык, вредно и не стоит использовать развесистые среды программирования, которые многое сами за тебя делают, всё тебе подсказывают и т.д.. Они нужны для другого - для быстрой разработки.
Поэтому для обучения берите любой редактор плоского текста, лучше - с подсветкой синтаксиса и какими-нибудь дополнительными средствами выразительности относительно того языка, который вы будете использовать.
Я, конечно, предпочитаю редактор vim. Однако и помимо него есть много разных опций.
Во-первых, есть редактор (и, по совместительству, среда разработки), который идёт сразу вместе с питоном - IDLE (назван в честь Эрика Айдла, главного актёра "Летающего цирка Монти Пайтона"). В linux, скорее всего, у вас будет название idle3.
По умолчанию запускается нечто вроде командной строки. Она чуть менее настоящая, чем собственно питоновская.1 Зато она умеет раскрашивать. И копировать текст при выделении.
Если вы не понимаете интуитивно, как пользоваться интерфейсом, посмотрите вот этот кусочек лекции.
Обратите внимание, что при написании программы в виде отдельного файла, а не просто в виде команд в интерпретаторе, я буду использовать оператор print: здесь вы уже не увидите просто так, если напишете какое-то выражение, последний залипший объект.
Посмотреть его в командной строке можно с помощью команды "_":
Если вы, упаси бог, работаете с виндой [как редактор этого текста], при сохранения файла в IDLE внимательно смотрите, куда сохраняете файл - иначе сохраните его в такое глубокое место, что потом не найдёте.
Как выполнить программу? Запустите всё тот же интерпретатор питона и в качестве параметра передайте ему путь к программе (в примере ниже предполагается, что вы уже перешли в терминале в ту директорию, куда сохранили файл first.py):
Такой способ запуска программы довольно удобен (почему - обсудим позже), но, если вы просто пишете небольшую программу, для запуска можно воспользоваться встроенной опцией IDLE (сохраните файл и нажмите F5).
Дополнительное удобство IDLE - после выполнения программы мы остаёмся в том пространстве имён, которое программа сгенерировала:
Соответственно, дальше можно в интерпретаторе IDLE после выполнения программы что-нибудь вручную поотлаживать.
То же самое можно сделать в стандартном интерпретаторе питона, если запустить его с ключом -i (в этом случае сначала выполнится программа, а потом мы попадём в интерпретатор с пространством имён, заполненным программой):
2.10. Правила видимости
Поговорим о глобальных переменных. Есть такая функция globals, которая выводит нам словарь (это такая структура данных в python, аналогичная map в C++), каждый элемент которого - пара, где левая часть - имя переменной, а правая - объект, на который это имя ссылается:
1 >>> globals() # Хотим посмотреть, что лежит в глобальном пространстве имён
2 {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}
3 >>> a = 345 # Объявили глобальную переменную a
4 >>> globals() # И сейчас увидим её в глобальном namespace
5 {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'a': 345}
6
globals() описывает глобальное пространство (внезапно), а locals() - пространство имён, которое заводится при входе в функцию и удаляется при выходе из неё.
Добавим два раза печать locals():
И увидим, что второй print отличается от первого наличием переменной c:
Локальные имена не пересекаются с глобальными:
Сравните с кодом, в котором в функции fun мы указываем, что с - глобальное имя:
Чтобы залезть из функции в глобальное пространство имён (использовать объект, имя которого объявлено вне функции), нужно либо указать, что мы хотим использовать глобальное имя (как сделано в примере выше - с помощью global), либо соответствующее имя не должно присутствовать в локальном пространстве имён. Поиск осуществляется сначала в локальном пространстве имён, и только потом - в глобальном:
1 def fun(a, b):
2 global c
3 print(locals())
4 c = a*2 + b + d # В локальном пространстве имён d не будет найдено - его здесь попросту нет
5 print(locals())
6 return c
7
8 a=b=c=d=100500 # Поэтому d будет взято из глобального пространства имён
9 print(fun(12, 45)) # Обратите внимание, вызов функции происходит уже после инициализации d, иначе были бы проблемы
10 print(a,b,c)
Важный пример: добавим в функцию локальное имя d, но уже после того, как d используется в выражении. Тогда, если интерпретатор увидит где-то в функции имя d, оно будет считаться локальным.
Однако локальная переменная на момент вычисления выражения у нас здесь не инициализирована, и мы получим ошибку выполнения:
Таким образом, сам факт связывания имени переменной и объекта внутри функции означает, что это имя - локальное.
Кроме того,
2.10.1. Архиважное замечание
Когда мы определяем функцию, мы просматриваем её на предмет того, какие имена в ней локальные, а какие - глобальные. T.e. namespace заполняется заранее.
Поэтому в примере выше мы поняли, что d - локальная переменная, ещё до того, как интерпретатор дошёл до строчки d = 8. Такое поведение - одно из тонких отличий логики работы python от других языков.
2.11. Замечательный ресурс, чтобы разобраться, что происходит
Ресурс называется Python Tutor и позволяет пошагово увидеть происходящее в программе, в том числе состояние пространств имён. Разбор примера выше можно посмотреть здесь. Взглянуть на это хотя бы один раз точно стоит.
2.12. Документация
В любой составной объект питона можно вставить т.н. строку2 документации. Под составными объектами имеются в виду:
- функция (в т.ч. метод класса);
- модуль;
- класс.
Всё, что нужно сделать - в самом начале тела, к примеру, функции добавить строку с тем текстом, который вы хотите:
1 def fun(a, b):
2 """This is a dummy function on a,b. # Тройные кавычки позволяют задать объект типа "строка", состоящий из нескольких строк (в смысле, последовательностей символов)
3 It does nothing valuable!""" # Строку документации принято указывать в тройных кавычках, даже если ваш текст состоит из одной строки - это облегчает
4 c = a*2 + b # поддерживание вашего кода, поскольку строку документации в любой момент можно расширить или сократить, не думая о том,
5 return c # сколько строк теперь занимает текст
6
7 a=b=c=d=100500
8 print(fun(12, 45))
9 print(a,b,c)
Как это работает: строка, залипшая в пространстве имён функции, приезжает в эту функцию в поле под названием __doc__. После этого с ней можно работать как напрямую, так и через help():
1 $ python3 -i first.py
2 69
3 100500 100500 100500
4 >>> dir() # Убедимся, что функция fun осталась в пространстве имён
5 ['__annotations__', '__builtins__', '__cached__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b', 'c', 'd', 'fun']
6 >>> dir(fun) # И что в её пространстве имён есть имя __doc__
7 ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
8 >>> fun.__doc__ # Напечатаем его значение напрямую
9 'This is a dummy function on a,b.\n It does nothing valuable!'
10 >>> help(fun) # Help! I need somebody! (help(fun), not just anybody)
11 Help on function fun in module __main__:
12
13 fun(a, b)
14 This is a dummy function on a,b.
15 It does nothing valuable!
16
То есть просто повесив строчку в начале функции, мы получили вполне увесистый help.
Документирование в питоне - очень весёлое и ненапряжное занятие. Когда будем говорить про модули, увидим это с потрясающей чёткостью.
2.13. Распаковка и запаковка параметров
2.13.1. Распаковка
Единственная известная нам пока последовательность - это кортеж. Мы даже знаем, что их можно распаковывать.
>>> 1,2,3 # Создадим кортеж (1, 2, 3) # Так кортежи выглядят при выводе на печать в Python >>> c = 1,3,5,67 # Создаём другой кортеж >>> f,g,h,j=c # И с помощью операции множественного связывания распаковываем его >>> f 1 >>> g 3 >>> h 5 >>> j 67 >>> type(c) <class 'tuple'> # Кортеж собственной персоной >>> c (1, 3, 5, 67)
Абсолютно та же картина - при передаче параметров в функцию. Нужно лишь указать с помощью символа *, что кортеж нужно распаковать:
Вопрос: насколько питону это тяжело? Запаковывать, распаковывать все эти параметры?
Ответ: вообще не тяжело. Функция в любом случае получает кортеж. Этот кортеж приходит при вызове, и только после этого функция начинает его именовать соответствующими именами.
2.13.2. Запаковка
Запаковка, т.е. обратная к распаковке операция, ещё удобнее:
1 def fun(*args): # Мы говорим функции, что передаваемые ей параметры - это элементы кортежа args, т.е. как бы запаковываем переданные нам параметры в кортеж
2 print(args, type(args)) # Убедимся, что на вход пришёл кортеж
3
4 fun() # Мы написали функцию с произвольным количеством параметров
5 fun(1,2,3,4,5)
6 fun("Thomas") # Да ещё и любого типа
Вы можете делать и кое-что поинтереснее:
Строка - тоже последовательность! А операция распаковки возможна для любой последовательности:
2.14. Если у вас нет return
То подразумевается, что return есть - в конце функции - и возвращает он значение None:
3. Генераторы
Что если я захочу написать такой кусок кода, который будет реализовывать не один алгоритм при каждом вызове (как функция), а разные при разных вызовах?
Один раз вызвал - выполнился один алгоритм, посчиталось какое-то значение. Второй раз старик закинул невод - пришёл невод с одной тиной. Третий раз закинул невод - а там золотая рыбка...
То есть что если я хочу написать повторно входимую функцию, выполняющую при первом входе - один кусок кода, при втором - второй, при третьем - третий, и так сколько-то раз?
Это очень интересная штука, потому что даёт нам в руки инструмент под названием вычислимая последовательность.
Встаёт всего лишь два вопроса:
- Сколько раз? (мы можем вызвать повторно входимую функцию);
- Каков вообще протокол работы с повторной функцией, как это в языке организовать?
3.1. Как создать
В питоне это выглядит довольно прозрачно. Чтобы описать генератор (т.е. повторно входимую функцию), напишите обычную функцию, только вместо return используйте yield:
1 >>> def gen(n):
2 ... yield n # Используем yield вместо return
3 ... yield n//2
4 ... yield "ALL"
5 ...
6 >>> gen # gen - обычная функция
7 <function gen at 0x0000020A9A1BE1E0>
8 >>> g=gen(5)
9 >>> g # Но возвращает она не искомое значение, а некий generator object
10 <generator object gen at 0x0000020A9A02E5C8>
11 >>> next(g) # По которому мы можем, используя функцию next(), итерироваться, и результатом уже и будут различные искомые значения
12 5
13 >>> next(g)
14 2
15 >>> next(g)
16 'ALL'
17
3.2. Ну сколько можно?
Ответ очень простой: пока в тексте вашей функции, использующей yield, не встретится return, или пока не закончится текст функции (что, как мы знаем, тоже вызов return, только неявный). После этого при попытке получить следующее значение возникнет исключение !StopIteration:
3.3. Одноразовость
Генератор - это такой одноразовый пистолет с несколькими зарядами (возможно, с очень большим их количеством). При каждом следующем вызове next выполняется весь код от места, где остановились при прошлом вызове, до следующего yield (который определяет, что именно вызов next вернёт на этот раз). Дошли до вызова return в функции (явного или неявного), получили !StopIteration - всё, нужен новый пистолет генератор.
Посмотрим это на примере более подробной программы:
1 $ python3 -i gen.py # Напоминаем, пользуемся ключом -i, чтобы после выполнения программы попасть в интерпретатор с тем namespace, который остался в результате
2 >>> dir() # Функция gen после интерпретации файла есть в глобальном пространстве имён
3 ['__annotations__', '__builtins__', '__cached__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'gen']
4 >>> g=gen(55) # Получаем наш генератор
5 >>> g # Убеждаемся, что это он
6 <generator object gen at 0x000001C3C174E5C8>
7 >>> a=next(g) # И начинаем вызывать его: первый раз
8 First
9 >>> a
10 55
11 >>> a=next(g) # Второй
12 Second
13 >>> a
14 110
15 >>> a=next(g) # Третий
16 Third
17 >>> a
18 -1
19 >>> a=next(g) # Кина не будет: электричество кончилось
20 Traceback (most recent call last):
21 File "<stdin>", line 1, in <module>
22 StopIteration
23 >>> a=next(g) # Да, генератор - одноразовый объект
24 Traceback (most recent call last):
25 File "<stdin>", line 1, in <module>
26 StopIteration
27 >>> g=gen("QQ") # Поэтому, если хочешь продолжить, придётся получить новый
28 >>> a=next(g) # И вот новый генератор можно использовать столько же раз, сколько и старый
29 First
30 >>> a
31 'QQ'
32 >>> a=next(g) # Два
33 Second
34 >>> a
35 'QQQQ'
36 >>> a=next(g) # Три
37 Third
38 >>> a
39 -1
40 >>> a=next(g) # Генератор исчерпал себя
41 Traceback (most recent call last):
42 File "<stdin>", line 1, in <module>
43 StopIteration
3.4. Генератор - это последовательность! (вычислимая)
Казалось бы, пользовались генератором, получили StopIteration, больше не пользуемся. Плохо.
Но нет! Любой циклический обход последовательности в питоне ровно так и устроен - пользуемся генератором, возвращающим элементы последовательности, пока не получим исключение из-за того, что больше элементов нет.
По сути, генератор и есть последовательность, которая может наравне с другими последовательностями участвовать в различных операциях. Давайте, например, распакуем генератор, как уже делали с кортежами и строками:
Ещё раз, для закрепления: приведённая выше функция gen не возвращает при своём вызове никакое из интересующих нас значений. Она возвращает генератор, generator object, с помощью которого мы можем перебирать точки входа функции (эти точки - соответственно, начало функции, точка сразу после первого yield, точка сразу после второго yield и т.д.).
Можно даже передавать генератор в функцию, пользуясь распаковкой:
1 $ python3 -i gen.py
2 First # В процессе распаковки происходит итерация по последовательности, поэтому осуществляется последовательный вход во все точки входа функции gen
3 Second
4 Third
5 QQ QQQQ -1 # И видим вполне ожидаемый результат, как будто gen изначально и был последовательностью уже вычисленных значений
6
Ну а зачем нужны генераторы - поговорим в следующий раз.
4. Операторы ввода
4.1. input
Оператор вывода мы уже знаем - это функция print.
А вот со вводом ситуация чуть-чуть ехидно смеётся более сложная. Функция ввода называется input, и результат её работы - строка:
Это значит, что, если вы хотите, например, ввести число, с этой строчкой нужно что-то делать. Можно преобразовать её к нужному типу, используя соответствующее преобразование:
Но иногда хочется просто ввести числа через запятую, а писать преобразования лень:
4.2. ГРЯЗНЫЙ ХАК ДЛЯ ИСПОЛЬЗОВАНИЯ ИСКЛЮЧИТЕЛЬНО В НАШЕМ КОНТЕСТЕ И БОЛЬШЕ НИГДЕ И БОЖЕ УПАСИ ВАС ИСПОЛЬЗОВАТЬ ЕГО В ПРОДАКШЕН-КОДЕ
Функция eval.
eval позволяет проинтерпретировать как питоновский код ту строку, которую ему передали:
1 >>> a="3+6"
2 >>> a
3 '3+6'
4 >>> type(a) # Нормальная строка
5 <class 'str'>
6 >>> eval(a) # Проинтерпретируем её
7 9
8 >>> eval("a*2") # Поговаривают, что на самом деле ударение нужно ставить на первый слог
9 '3+63+6'
10 >>> dir()
11 ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a']
12 >>> h = 5
13 >>> eval("h*8+1") # Мало того, что он вычисляет то, что есть внутри, он ещё и ХОДИТ К ТЕКУЩЕМУ NAMESPACE
14 41
15
А вот и та таблетка, которой мы будем пользоваться на контесте:
Что с ней не так? А то, что она может делать вообще что угодно:
1 >>> a=eval(input()) # Просмотреть ваш namespace
2 dir()
3 >>> a
4 ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'h']
5 >>> a=eval(input()) # Импортировать модули
6 __import__("random").random()
7 >>> a # И это всё ещё безобидные вещи, а может ведь и в сеть сходить, и вообще выполнить произвольный код
8 0.15582057392974524
9
В общем, использовать можно, но с огромной осторожностью.
История в IDLE работает немного не так: при нажатии Enter появляется та строчка, на которую был установлен курсор. (1)
Не самый удачный термин: здесь имеется в виду строка как тип данных, но строка как тип данных может состоять как из одной, так и из нескольких строк в смысле последовательностей символов, разделённых символом переноса строки (простите за каламбур). Так что проверяйте, точно ли вы понимаете смысл высказываний, содержащих слово "строка". (2)