Файл: Николай Прохоренок Владимир Дронов.pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 05.12.2023

Просмотров: 832

Скачиваний: 3

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

СОДЕРЖАНИЕ

В этом случае мы получим число 17, как и должно быть. Однако если пользователь вместо числа введет строку, то программа завершится с фатальной ошибкой. Как обработать ошиб- ку, мы разберемся по мере изучения языка. 2.6. Удаление переменных Удалить переменную можно с помощью инструкции del: del <Переменная1>[, ..., <ПеременнаяN>] Глава 2. Переменные 51 Пример удаления одной переменной: >>> x = 10; x 10 >>> del x; x Traceback (most recent call last): File "", line 1, in del x; x NameError: name 'x' is not defined Пример удаления нескольких переменных: >>> x, y = 10, 20 >>> del x, y ГЛ А В А 3 Операторы Операторы позволяют произвести с данными определенные действия. Например, операто- ры присваивания служат для сохранения данных в переменной, математические операторы позволяют выполнить арифметические вычисления, а оператор конкатенации строк служит для соединения двух строк в одну. Рассмотрим операторы, доступные в Python 3, подробно. 3.1. Математические операторы Производить операции над числами позволяют математические операторы: + — сложение: >>> 10 + 5 # Целые числа 15 >>> 12.4 + 5.2 # Вещественные числа 17.6 >>> 10 + 12.4 # Целые и вещественные числа 22.4 - — вычитание: >>> 10 - 5 # Целые числа 5 >>> 12.4 - 5.2 # Вещественные числа 7.2 >>> 12 - 5.2 # Целые и вещественные числа 6.8 * — умножение: >>> 10 * 5 # Целые числа 50 >>> 12.4 * 5.2 # Вещественные числа 64.48 >>> 10 * 5.2 # Целые и вещественные числа 52.0 / — деление. Результатом деления всегда является вещественное число, даже если про- изводится деление целых чисел. Обратите внимание на эту особенность, если вы раньше Глава 3. Операторы 53 программировали на Python 2. В Python 2 при делении целых чисел остаток отбрасывал- ся и возвращалось целое число, в Python 3 поведение оператора изменилось: >>> 10 / 5 # Деление целых чисел без остатка 2.0 >>> 10 / 3 # Деление целых чисел с остатком 3.3333333333333335 >>> 10.0 / 5.0 # Деление вещественных чисел 2.0 >>> 10.0 / 3.0 # Деление вещественных чисел 3.3333333333333335 >>> 10 / 5.0 # Деление целого числа на вещественное 2.0 >>> 10.0 / 5 # Деление вещественного числа на целое 2.0 // — деление с округлением вниз. Вне зависимости от типа чисел остаток отбрасывается: >>> 10 // 5 # Деление целых чисел без остатка 2 >>> 10 // 3 # Деление целых чисел с остатком 3 >>> 10.0 // 5.0 # Деление вещественных чисел 2.0 >>> 10.0 // 3.0 # Деление вещественных чисел 3.0 >>> 10 // 5.0 # Деление целого числа на вещественное 2.0 >>> 10 // 3.0 # Деление целого числа на вещественное 3.0 >>> 10.0 // 5 # Деление вещественного числа на целое 2.0 >>> 10.0 // 3 # Деление вещественного числа на целое 3.0 % — остаток от деления: >>> 10 % 5 # Деление целых чисел без остатка 0 >>> 10 % 3 # Деление целых чисел с остатком 1 >>> 10.0 % 5.0 # Операция над вещественными числами 0.0 >>> 10.0 % 3.0 # Операция над вещественными числами 1.0 >>> 10 % 5.0 # Операция над целыми и вещественными числами 0.0 >>> 10 % 3.0 # Операция над целыми и вещественными числами 1.0 >>> 10.0 % 5 # Операция над целыми и вещественными числами 0.0 >>> 10.0 % 3 # Операция над целыми и вещественными числами 1.0 54 Часть I. Основы языка Python ** — возведение в степень: >>> 10 ** 2, 10.0 ** 2 (100, 100.0) унарный минус (–) и унарный плюс (+): >>> +10, +10.0, -10, -10.0, -(-10), -(-10.0) (10, 10.0, -10, -10.0, 10, 10.0) Как видно из примеров, операции над числами разных типов возвращают число, имеющее более сложный тип из типов, участвующих в операции. Целые числа имеют самый простой тип, далее идут вещественные числа и самый сложный тип — комплексные числа. Таким образом, если в операции участвуют целое число и вещественное, то целое число будет автоматически преобразовано в вещественное число, затем будет произведена операция над вещественными числами, а результатом станет вещественное число. При выполнении операций над вещественными числами следует учитывать ограничения точности вычислений. Например, результат следующей операции может показаться стран- ным: >>> 0.3 - 0.1 - 0.1 - 0.1 -2.7755575615628914e-17 Ожидаемым был бы результат 0.0, но, как видно из примера, мы получили совсем другой результат. Если необходимо производить операции с фиксированной точностью, следует использовать модуль decimal: >>> from decimal import Decimal >>> Decimal("0.3") - Decimal("0.1") - Decimal("0.1") - Decimal("0.1") Decimal('0.0') 3.2. Двоичные операторы Двоичные операторы предназначены для манипуляции отдельными битами. Язык Python поддерживает следующие двоичные операторы: — двоичная инверсия. Значение каждого бита заменяется на противоположное: >>> x = 100 # 01100100 >>> x = x # 10011011 & — двоичное И: >>> x = 100 # 01100100 >>> y = 75 # 01001011 >>> z = x & y # 01000000 >>> "{0:b} & {1:b} = {2:b}".format(x, y, z) '1100100 & 1001011 = 1000000' | — двоичное ИЛИ: >>> x = 100 # 01100100 >>> y = 75 # 01001011 >>> z = x | y # 01101111 >>> "{0:b} | {1:b} = {2:b}".format(x, y, z) '1100100 | 1001011 = 1101111' Глава 3. Операторы 55 ^ — двоичное исключающее ИЛИ: >>> x = 100 # 01100100 >>> y = 250 # 11111010 >>> z = x ^ y # 10011110 >>> "{0:b} ^ {1:b} = {2:b}".format(x, y, z) '1100100 ^ 11111010 = 10011110' << — сдвиг влево — сдвигает двоичное представление числа влево на один или более разрядов и заполняет разряды справа нулями: >>> x = 100 # 01100100 >>> y = x << 1 # 11001000 >>> z = y << 1 # 10010000 >>> k = z << 2 # 01000000 >> — сдвиг вправо — сдвигает двоичное представление числа вправо на один или более разрядов и заполняет разряды слева нулями, если число положительное: >>> x = 100 # 01100100 >>> y = x >> 1 # 00110010 >>> z = y >> 1 # 00011001 >>> k = z >> 2 # 00000110 Если число отрицательное, то разряды слева заполняются единицами: >>> x = -127 # 10000001 >>> y = x >> 1 # 11000000 >>> z = y >> 2 # 11110000 >>> k = z << 1 # 11100000 >>> m = k >> 1 # 11110000 3.3. Операторы для работы с последовательностями Для работы с последовательностями предназначены следующие операторы: + — конкатенация: >>> print("Строка1" + "Строка2") # Конкатенация строк Строка1Строка2 >>> [1, 2, 3] + [4, 5, 6] # Списки [1, 2, 3, 4, 5, 6] >>> (1, 2, 3) + (4, 5, 6) # Кортежи (1, 2, 3, 4, 5, 6) * — повторение: >>> "s" * 20 # Строки 'ssssssssssssssssssss' >>> [1, 2] * 3 # Списки [1, 2, 1, 2, 1, 2] >>> (1, 2) * 3 # Кортежи (1, 2, 1, 2, 1, 2) 56 Часть I. Основы языка Python in — проверка на вхождение. Если элемент входит в последовательность, то возвраща- ется логическое значение True: >>> "Строка" in "Строка для поиска" # Строки True >>> "Строка2" in "Строка для поиска" # Строки False >>> 2 in [1, 2, 3], 4 in [1, 2, 3] # Списки (True, False) >>> 2 in (1, 2, 3), 6 in (1, 2, 3) # Кортежи (True, False) not in — проверка на невхождение. Если элемент не входит в последовательность, воз- вращается True: >>> "Строка" not in "Строка для поиска" # Строки False >>> "Строка2" not in "Строка для поиска" # Строки True >>> 2 not in [1, 2, 3], 4 not in [1, 2, 3] # Списки (False, True) >>> 2 not in (1, 2, 3), 6 not in (1, 2, 3) # Кортежи (False, True) 3.4. Операторы присваивания Операторы присваивания предназначены для сохранения значения в переменной. Приведем перечень операторов присваивания, доступных в языке Python: = — присваивает переменной значение: >>> x = 5; x 5 += — увеличивает значение переменной на указанную величину: >>> x = 5; x += 10 # Эквивалентно x = x + 10 >>> x 15 Для последовательностей оператор += производит конкатенацию: >>> s = "Стр"; s += "ока" >>> print(s) Строка -= — уменьшает значение переменной на указанную величину: >>> x = 10; x -= 5 # Эквивалентно x = x — 5 >>> x 5 *= — умножает значение переменной на указанную величину: >>> x = 10; x *= 5 # Эквивалентно x = x * 5 >>> x 50 Глава 3. Операторы 57 Для последовательностей оператор *= производит повторение: >>> s = "*"; s *= 20 >>> s '********************' /= — делит значение переменной на указанную величину: >>> x = 10; x /= 3 # Эквивалентно x = x / 3 >>> x 3.3333333333333335 >>> y = 10.0; y /= 3.0 # Эквивалентно y = y / 3.0 >>> y 3.3333333333333335 //= — деление с округлением вниз и присваиванием: >>> x = 10; x //= 3 # Эквивалентно x = x // 3 >>> x 3 >>> y = 10.0; y //= 3.0 # Эквивалентно y = y // 3.0 >>> y 3.0 %= — деление по модулю и присваивание: >>> x = 10; x %= 2 # Эквивалентно x = x % 2 >>> x 0 >>> y = 10; y %= 3 # Эквивалентно y = y % 3 >>> y 1 **= — возведение в степень и присваивание: >>> x = 10; x **= 2 # Эквивалентно x = x ** 2 >>> x 100 3.5. Приоритет выполнения операторов В какой последовательности будет вычисляться приведенное далее выражение? x = 5 + 10 * 3 / 2 Это зависит от приоритета выполнения операторов. В данном случае последовательность вычисления выражения будет такой: 1. Число 10 будет умножено на 3, т. к. приоритет оператора умножения выше приоритета оператора сложения. 2. Полученное значение будет поделено на 2, т. к. приоритет оператора деления равен при- оритету оператора умножения (а операторы с равными приоритетами выполняются сле- ва направо), но выше, чем у оператора сложения. 58 Часть I. Основы языка Python 3. К полученному значению будет прибавлено число 5, т. к. оператор присваивания = имеет наименьший приоритет. 4. Значение будет присвоено переменной x>>> x = 5 + 10 * 3 / 2 >>> x 20.0 С помощью скобок можно изменить последовательность вычисления выражения: x = (5 + 10) * 3 / 2 Теперь порядок вычислений станет иным: 1. К числу 5 будет прибавлено 10. 2. Полученное значение будет умножено на 3. 3. Полученное значение будет поделено на 2. 4. Значение будет присвоено переменной x>>> x = (5 + 10) * 3 / 2 >>> x 22.5 Перечислим операторы в порядке убывания приоритета: 1. -x, +x, x, ** — унарный минус, унарный плюс, двоичная инверсия, возведение в сте- пень. Если унарные операторы расположены слева от оператора **, то возведение в сте- пень имеет больший приоритет, а если справа — то меньший. Например, выражение: -10 ** -2 эквивалентно следующей расстановке скобок: -(10 ** (-2)) 2. *, %, /, // — умножение (повторение), остаток от деления, деление, деление с округле- нием вниз. 3. +, – — сложение (конкатенация), вычитание. 4. <<, >> — двоичные сдвиги. 5. & — двоичное И. 6. ^ — двоичное исключающее ИЛИ. 7. | — двоичное ИЛИ. 8. =, +=, -=, *=, /=, //=, %=, **= — присваивание. ГЛ А В А 4 Условные операторы и циклы Условные операторы позволяют в зависимости от значения логического выражения выпол- нить отдельный участок программы или, наоборот, не выполнить его. Логические выраже- ния возвращают только два значения: True (истина) или False (ложь), которые ведут себя как целые числа 1 и 0 соответственно: >>> True + 2 # Эквивалентно 1 + 2 3 >>> False + 2 # Эквивалентно 0 + 2 2 Логическое значение можно сохранить в переменной: >>> x = True; y = False >>> x, y (True, False) Любой объект в логическом контексте может интерпретироваться как истина (True) или как ложь (False). Для определения логического значения можно использовать функцию bool()Значение True возвращает следующие объекты: любое число, не равное нулю: >>> bool(1), bool(20), bool(-20) (True, True, True) >>> bool(1.0), bool(0.1), bool(-20.0) (True, True, True) не пустой объект: >>> bool("0"), bool([0, None]), bool((None,)), bool({"x": 5}) (True, True, True, True) Следующие объекты интерпретируются как False: число, равное нулю: >>> bool(0), bool(0.0) (False, False) пустой объект: >>> bool(""), bool([]), bool(()) (False, False, False) 60 Часть I. Основы языка Python значение None: >>> bool(None) False 4.1. Операторы сравнения Операторы сравнения используются в логических выражениях. Приведем их перечень: == — равно: >>> 1 == 1, 1 == 5 (True, False) != — не равно: >>> 1 != 5, 1 != 1 (True, False) < — меньше: >>> 1 < 5, 1 < 0 (True, False) > — больше: >>> 1 > 0, 1 > 5 (True, False) <= — меньше или равно: >>> 1 <= 5, 1 <= 0, 1 <= 1 (True, False, True) >= — больше или равно: >>> 1 >= 0, 1 >= 5, 1 >= 1 (True, False, True) in — проверка на вхождение в последовательность: >>> "Строка" in "Строка для поиска" # Строки True >>> 2 in [1, 2, 3], 4 in [1, 2, 3] # Списки (True, False) >>> 2 in (1, 2, 3), 4 in (1, 2, 3) # Кортежи (True, False) Оператор in можно также использовать для проверки существования ключа словаря: >>> "x" in {"x": 1, "y": 2}, "z" in {"x": 1, "y": 2} (True, False) not in — проверка на невхождение в последовательность: >>> "Строка" not in "Строка для поиска" # Строки False >>> 2 not in [1, 2, 3], 4 not in [1, 2, 3] # Списки (False, True) Глава 4. Условные операторы и циклы 61 >>> 2 not in (1, 2, 3), 4 not in (1, 2, 3) # Кортежи (False, True) is — проверяет, ссылаются ли две переменные на один и тот же объект. Если перемен- ные ссылаются на один и тот же объект, оператор is возвращает значение True: >>> x = y = [1, 2] >>> x is y True >>> x = [1, 2]; y = [1, 2] >>> x is y False Следует заметить, что в целях повышения эффективности интерпретатор производит кэширование малых целых чисел и небольших строк. Это означает, что если ста пере- менным присвоено число 2, то в этих переменных, скорее всего, будет сохранена ссылка на один и тот же объект: >>> x = 2; y = 2; z = 2 >>> x is y, y is z (True, True) is not — проверяет, ссылаются ли две переменные на разные объекты. Если это так, возвращается значение True: >>> x = y = [1, 2] >>> x is not y False >>> x = [1, 2]; y = [1, 2] >>> x is not y True Значение логического выражения можно инвертировать с помощью оператора not: >>> x = 1; y = 1 >>> x == y True >>> not (x == y), not x == y (False, False) Если переменные x и y равны, возвращается значение True, но так как перед выражением стоит оператор not, выражение вернет False. Круглые скобки можно не указывать, по- скольку оператор not имеет более низкий приоритет выполнения, чем операторы сравнения. В логическом выражении можно указывать сразу несколько условий: >>> x = 10 >>> 1 < x < 20, 11 < x < 20 (True, False) Несколько логических выражений можно объединить в одно большое с помощью следую- щих операторов: and — логическое И. Если x в выражении x and y интерпретируется как False, то воз- вращается x, в противном случае — y. Примеры: >>> 1 < 5 and 2 < 5 # True and True == True True 62 Часть I. Основы языка Python >>> 1 < 5 and 2 > 5 # True and False == False False >>> 1 > 5 and 2 < 5 # False and True == False False >>> 10 and 20, 0 and 20, 10 and 0 (20, 0, 0) or — логическое ИЛИ. Если x в выражении x or y интерпретируется как False, то воз- вращается y, в противном случае — x. Примеры: >>> 1 < 5 or 2 < 5 # True or True == True True >>> 1 < 5 or 2 > 5 # True or False == True True >>> 1 > 5 or 2 < 5 # False or True == True True >>> 1 > 5 or 2 > 5 # False or False == False False >>> 10 or 20, 0 or 20, 10 or 0 (10, 20, 10) >>> 0 or "" or None or [] or "s" 's' Следующее выражение вернет True только в том случае, если оба выражения вернут True: x1 == x2 and x2 != x3 А это выражение вернет True, если хотя бы одно из выражений вернет True: x1 == x2 or x3 == x4 Перечислим операторы сравнения в порядке убывания приоритета: 1. <, >, <=, >=, ==, !=, <>, is, is not, in, not in2. not — логическое отрицание. 3. and — логическое И. 4. or — логическое ИЛИ. 4.2. Оператор ветвления if...else Оператор ветвления if...else позволяет в зависимости от значения логического выраже- ния выполнить отдельный участок программы или, наоборот, не выполнить его. Оператор имеет следующий формат: if <Логическое выражение>: <Блок, выполняемый, если условие истинно> [elif <Логическое выражение>: <Блок, выполняемый, если условие истинно> ] [else: <Блок, выполняемый, если все условия ложны> ] Глава 4. Условные операторы и циклы 63 Как вы уже знаете, блоки внутри составной инструкции выделяются одинаковым количест- вом пробелов (обычно четырьмя). Концом блока является инструкция, перед которой рас- положено меньшее количество пробелов. В некоторых языках программирования логиче- ское выражение заключается в круглые скобки. В языке Python это делать необязательно, но можно, т. к. любое выражение может быть расположено внутри круглых скобок. Тем не менее, круглые скобки следует использовать только при необходимости разместить условие на нескольких строках. 1   ...   4   5   6   7   8   9   10   11   ...   83

%(h1)s

>>> bytes("string\uFFFD", "cp1251", "replace") b'string?' >>> bytes("string\uFFFD", "cp1251", "ignore") b'string' с помощью строкового метода encode([encoding="utf-8"][, errors="strict"]). Если кодировка не указана, строка преобразуется в последовательность байтов в кодировке UTF-8. В параметре errors могут быть указаны значения "strict" (значение по умолча- нию), "replace", "ignore", "xmlcharrefreplace" или "backslashreplace": >>> "строка".encode() b'\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xba\xd0\xb0' >>> "строка".encode(encoding="cp1251") b'\xf1\xf2\xf0\xee\xea\xe0' >>> "строка\uFFFD".encode(encoding="cp1251", errors="xmlcharrefreplace") b'\xf1\xf2\xf0\xee\xea\xe0�' >>> "строка\uFFFD".encode(encoding="cp1251", errors="backslashreplace") b'\xf1\xf2\xf0\xee\xea\xe0\\ufffd' Глава 6. Строки и двоичные данные 113 указав букву b (регистр не имеет значения) перед строкой в апострофах, кавычках, трой- ных апострофах или тройных кавычках. Обратите внимание на то, что в строке могут быть только символы с кодами, входящими в кодировку ASCII. Все остальные символы должны быть представлены специальными последовательностями: >>> b"string", b'string', b"""string""", b'''string''' (b'string', b'string', b'string', b'string') >>> b"строка" SyntaxError: bytes can only contain ASCII literal characters. >>> b"\xf1\xf2\xf0\xee\xea\xe0" b'\xf1\xf2\xf0\xee\xea\xe0' с помощью функции bytes(<Последовательность>), которая преобразует последователь- ность целых чисел от 0 до 255 в объект типа bytes. Если число не попадает в диапазон, возбуждается исключение ValueError: >>> b = bytes([225, 226, 224, 174, 170, 160]) >>> b b'\xe1\xe2\xe0\xae\xaa\xa0' >>> str(b, "cp866") 'строка' с помощью функции bytes(<Число>), которая задает количество элементов в последова- тельности. Каждый элемент будет содержать нулевой символ: >>> bytes(10) b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' с помощью метода bytes.fromhex(<Строка>). Строка в этом случае должна содержать шестнадцатеричные значения символов: >>> b = bytes.fromhex(" e1 e2e0ae aaa0 ") >>> b b'\xe1\xe2\xe0\xae\xaa\xa0' >>> str(b, "cp866") 'строка' Объекты типа bytes относятся к последовательностям. Каждый элемент такой последова- тельности может хранить целое число от 0 до 255, которое обозначает код символа. Как и все последовательности, объекты поддерживают обращение к элементу по индексу, полу- чение среза, конкатенацию, повторение и проверку на вхождение: >>> b = bytes("string", "cp1251") >>> b b'string' >>> b[0] # Обращение по индексу 115 >>> b[1:3] # Получение среза b'tr' >>> b + b"123" # Конкатенация b'string123' >>> b * 3 # Повторение b'stringstringstring' >>> 115 in b, b"tr" in b, b"as" in b (True, True, False) 114 Часть I. Основы языка Python Как видно из примера, при выводе объекта целиком, а также при извлечении среза, произ- водится попытка отображения символов. Однако доступ по индексу возвращает целое чис- ло, а не символ. Если преобразовать объект в список, то мы получим последовательность целых чисел: >>> list(bytes("string", "cp1251")) [115, 116, 114, 105, 110, 103] Тип bytes относится к неизменяемым типам. Это означает, что можно получить значение по индексу, но изменить его нельзя: >>> b = bytes("string", "cp1251") >>> b[0] = 168 Traceback (most recent call last): File "", line 1, in b[0] = 168 TypeError: 'bytes' object does not support item assignment Объекты типа bytes поддерживают большинство строковых методов, которые мы рассмат- ривали в предыдущих разделах. Однако некоторые из этих методов могут некорректно ра- ботать с русскими буквами — в этих случаях следует использовать тип str, а не тип bytesНе поддерживаются объектами типа bytes строковые методы encode(), isidentifier(), isprintable(), isnumeric(), isdecimal(), format_map() и format(), а также операция фор- матирования. При использовании методов следует учитывать, что в параметрах нужно указывать объекты типа bytes, а не строки: >>> b = bytes("string", "cp1251") >>> b.replace(b"s", b"S") b'String' Необходимо также помнить, что смешивать строки и объекты типа bytes в выражениях нельзя. Предварительно необходимо явно преобразовать объекты к одному типу, а лишь затем производить операцию: >>> b"string" + "string" Traceback (most recent call last): File "", line 1, in b"string" + "string" TypeError: can't concat bytes to str >>> b"string" + "string".encode("ascii") b'stringstring' Объект типа bytes может содержать как однобайтовые, так и многобайтовые символы. При использовании многобайтовых символов некоторые функции могут работать не так, как предполагалось, — например, функция len() вернет количество байтов, а не символов: >>> len("строка") 6 >>> len(bytes("строка", "cp1251")) 6 >>> len(bytes("строка", "utf-8")) 12 Глава 6. Строки и двоичные данные 115 Преобразовать объект типа bytes в строку позволяет метод decode(). Метод имеет следую- щий формат: decode([encoding="utf-8"][, errors="strict"]) Параметр encoding задает кодировку символов (по умолчанию UTF-8) в объекте bytes, а параметр errors — способ обработки ошибок при преобразовании. В параметре errors можно указать значения "strict" (значение по умолчанию), "replace" или "ignore". При- мер преобразования: >>> b = bytes("строка", "cp1251") >>> b.decode(encoding="cp1251"), b.decode("cp1251") ('строка', 'строка') Для преобразования можно также воспользоваться функцией str(): >>> b = bytes("строка", "cp1251") >>> str(b, "cp1251") 'строка' Чтобы изменить кодировку данных, следует сначала преобразовать тип bytes в строку, а затем произвести обратное преобразование, указав нужную кодировку. Преобразуем дан- ные из кодировки Windows-1251 в кодировку KOI8-R, а затем обратно: >>> w = bytes("Строка", "cp1251") # Данные в кодировке windows-1251 >>> k = w.decode("cp1251").encode("koi8-r") >>> k, str(k, "koi8-r") # Данные в кодировке KOI8-R (b'\xf3\xd4\xd2\xcf\xcb\xc1', 'Строка') >>> w = k.decode("koi8-r").encode("cp1251") >>> w, str(w, "cp1251") # Данные в кодировке windows-1251 (b'\xd1\xf2\xf0\xee\xea\xe0', 'Строка') В Python 3.5 появились два полезных инструмента для работы с типом данных bytes. Во- первых, теперь можно форматировать такие данные с применением описанного в разд. 6.4 оператора %: >>> b"%i - %i - %f" % (10, 20, 30) b'10 - 20 - 30.000000' Однако здесь нужно помнить, что тип преобразования s (т. е. вывод в виде Unicode-строки) в этом случае не поддерживается, и его использование приведет к возбуждению исключе- ния TypeError: >>> b"%s - %s - %s" % (10, 20, 30) Traceback (most recent call last): File "", line 1, in b"%s - %s - %s" % (10, 20, 30) TypeError: %b requires a bytes-like object, or an object that implements __bytes__, not 'int' Во-вторых, тип bytes получил поддержку метода hex(), который возвращает строку с шест- надцатеричным представлением значения: >>> b"string".hex() '737472696e67' 116 Часть I. Основы языка Python 6.13. Тип данных bytearray Тип данных bytearray является разновидностью типа bytes и поддерживает те же самые методы и операции (включая оператор форматирования % и метод hex(), описанные ранее). В отличие от типа bytes, тип bytearray допускает возможность непосредственного измене- ния объекта и содержит дополнительные методы, позволяющие выполнять эти изменения. Создать объект типа bytearray можно следующими способами: с помощью функции bytearray([<Строка>, <Кодировка>[, <Обработка ошибок>]]). Если параметры не указаны, то возвращается пустой объект. Чтобы преобразовать строку в объект типа bytearray, необходимо передать минимум два первых параметра. Если строка указана только в первом параметре, то возбуждается исключение TypeError: >>> bytearray() bytearray(b'') >>> bytearray("строка", "cp1251") bytearray(b'\xf1\xf2\xf0\xee\xea\xe0') >>> bytearray("строка") Traceback (most recent call last): File "", line 1, in bytearray("строка") TypeError: string argument without an encoding В третьем параметре могут быть указаны значения "strict" (при ошибке возбуждается исключение UnicodeEncodeError — значение по умолчанию), "replace" (неизвестный символ заменяется символом вопроса) или "ignore" (неизвестные символы игнорируются): >>> bytearray("string\uFFFD", "cp1251", "strict") Traceback (most recent call last): File "", line 1, in bytearray("string\uFFFD", "cp1251", "strict") File "C:\Python36\lib\encodings\cp1251.py", line 12, in encode return codecs.charmap_encode(input,errors,encoding_table) UnicodeEncodeError: 'charmap' codec can't encode character '\ufffd' in position 6: character maps to >>> bytearray("string\uFFFD", "cp1251", "replace") bytearray(b'string?') >>> bytearray("string\uFFFD", "cp1251", "ignore") bytearray(b'string') с помощью функции bytearray(<Последовательность>), которая преобразует последова- тельность целых чисел от 0 до 255 в объект типа bytearray. Если число не попадает в диапазон, возбуждается исключение ValueError: >>> b = bytearray([225, 226, 224, 174, 170, 160]) >>> b bytearray(b'\xe1\xe2\xe0\xae\xaa\xa0') >>> bytearray(b'\xe1\xe2\xe0\xae\xaa\xa0') bytearray(b'\xe1\xe2\xe0\xae\xaa\xa0') >>> str(b, "cp866") 'строка' с помощью функции bytearray(<Число>), которая задает количество элементов в после- довательности. Каждый элемент будет содержать нулевой символ: Глава 6. Строки и двоичные данные 117 >>> bytearray(5) bytearray(b'\x00\x00\x00\x00\x00') с помощью метода bytearray.fromhex(<Строка>). Строка в этом случае должна содер- жать шестнадцатеричные значения символов: >>> b = bytearray.fromhex(" e1 e2e0ae aaa0 ") >>> b bytearray(b'\xe1\xe2\xe0\xae\xaa\xa0') >>> str(b, "cp866") 'строка' Тип bytearray относится к изменяемым типам. Поэтому можно не только получить значе- ние по индексу, но и изменить его (что не свойственно строкам): >>> b = bytearray("Python", "ascii") >>> b[0] # Можем получить значение 80 >>> b[0] = b"J"[0] # Можем изменить значение >>> b bytearray(b'Jython') При изменении значения важно помнить, что присваиваемое значение должно быть целым числом в диапазоне от 0 до 255. Чтобы получить число в предыдущем примере, мы создали объект типа bytes, а затем присвоили значение, расположенное по индексу 0 (b[0] = b"J"[0]). Можно, конечно, сразу указать код символа, но ведь держать все коды символов в памяти свойственно компьютеру, а не человеку. Для изменения объекта можно также использовать следующие методы: append(<Число>) — добавляет один элемент в конец объекта. Метод изменяет текущий объект и ничего не возвращает: >>> b = bytearray("string", "ascii") >>> b.append(b"1"[0]); b bytearray(b'string1') extend(<Последовательность>) — добавляет элементы последовательности в конец объекта. Метод изменяет текущий объект и ничего не возвращает: >>> b = bytearray("string", "ascii") >>> b.extend(b"123"); b bytearray(b'string123') Добавить несколько элементов можно с помощью операторов + и +=: >>> b = bytearray("string", "ascii") >>> b + b"123" # Возвращает новый объект bytearray(b'string123') >>> b += b"456"; b # Изменяет текущий объект bytearray(b'string456') Кроме того, можно воспользоваться операцией присваивания значения срезу: >>> b = bytearray("string", "ascii") >>> b[len(b):] = b"123" # Добавляем элементы в последовательность >>> b bytearray(b'string123') 118 Часть I. Основы языка Python insert(<Индекс>, <Число>) — добавляет один элемент в указанную позицию. Осталь- ные элементы смещаются. Метод изменяет текущий объект и ничего не возвращает. До- бавим элемент в начало объекта: >>> b = bytearray("string", "ascii") >>> b.insert(0, b"1"[0]); b bytearray(b'1string') Метод insert() позволяет добавить только один элемент. Чтобы добавить несколько элементов, можно воспользоваться операцией присваивания значения срезу. Добавим несколько элементов в начало объекта: >>> b = bytearray("string", "ascii") >>> b[:0] = b"123"; b bytearray(b'123string') pop([<Индекс>]) — удаляет элемент, расположенный по указанному индексу, и воз- вращает его. Если индекс не указан, удаляет и возвращает последний элемент: >>> b = bytearray("string", "ascii") >>> b.pop() # Удаляем последний элемент 103 >>> b bytearray(b'strin') >>> b.pop(0) # Удаляем первый элемент 115 >>> b bytearray(b'trin') Удалить элемент списка позволяет также оператор del: >>> b = bytearray("string", "ascii") >>> del b[5] # Удаляем последний элемент >>> b bytearray(b'strin') >>> del b[:2] # Удаляем первый и второй элементы >>> b bytearray(b'rin') remove(<Число>) — удаляет первый элемент, содержащий указанное значение. Если элемент не найден, возбуждается исключение ValueError. Метод изменяет текущий объ- ект и ничего не возвращает: >>> b = bytearray("strstr", "ascii") >>> b.remove(b"s"[0]) # Удаляет только первый элемент >>> b bytearray(b'trstr') reverse() — изменяет порядок следования элементов на противоположный. Метод изменяет текущий объект и ничего не возвращает: >>> b = bytearray("string", "ascii") >>> b.reverse(); b bytearray(b'gnirts') Преобразовать объект типа bytearray в строку позволяет метод decode(). Метод имеет сле- дующий формат: decode([encoding="utf-8"][, errors="strict"]) Глава 6. Строки и двоичные данные 119 Параметр encoding задает кодировку символов (по умолчанию UTF-8) в объекте bytearray, а параметр errors — способ обработки ошибок при преобразовании. В параметре errors можно указать значения "strict" (значение по умолчанию), "replace" или "ignore". При- мер преобразования: >>> b = bytearray("строка", "cp1251") >>> b.decode(encoding="cp1251"), b.decode("cp1251") ('строка', 'строка') Для преобразования можно также воспользоваться функцией str(): >>> b = bytearray("строка", "cp1251") >>> str(b, "cp1251") 'строка' 6.14. Преобразование объекта в последовательность байтов Преобразовать объект в последовательность байтов (выполнить его сериализацию), а затем восстановить (десериализовать) объект позволяет модуль pickle. Прежде чем использовать функции из этого модуля, необходимо подключить модуль с помощью инструкции: import pickle Для преобразования предназначены две функции: dumps(<Объект>[, protocol=None][, fix_imports=True]) — производит сериализацию объекта и возвращает последовательность байтов специального формата. Формат зави- сит от указанного во втором параметре протокола, который задается в виде числа от 0 до значения константы pickle.HIGHEST_PROTOCOL. Если второй параметр не указан, будет использован протокол 3 (константа pickle.DEFAULT_PROTOCOL). Пример преобразования списка и кортежа: >>> import pickle >>> obj1 = [1, 2, 3, 4, 5] # Список >>> obj2 = (6, 7, 8, 9, 10) # Кортеж >>> pickle.dumps(obj1) b'\x80\x03]q\x00(K\x01K\x02K\x03K\x04K\x05e.' >>> pickle.dumps(obj2) b'\x80\x03(K\x06K\x07K\x08K\tK\ntq\x00.' loads(<Последовательность байтов>[, fix_imports=True][, encoding="ASCII"][, errors="strict"]) — преобразует последовательность байтов специального формата обратно в объект, выполняя его десериализацию. Пример восстановления списка и кор- тежа: >>> pickle.loads(b'\x80\x03]q\x00(K\x01K\x02K\x03K\x04K\x05e.') [1, 2, 3, 4, 5] >>> pickle.loads(b'\x80\x03(K\x06K\x07K\x08K\tK\ntq\x00.') (6, 7, 8, 9, 10) 120 Часть I. Основы языка Python 6.15. Шифрование строк Для шифрования строк предназначен модуль hashlib. Прежде чем использовать функции из этого модуля, необходимо подключить модуль с помощью инструкции: import hashlib Модуль предоставляет следующие функции: md5(), sha1(), sha224(), sha256(), sha384(), sha512(), в Python 3.6 появилась поддержка функций sha3_224(), sha3_256(), sha3_384(), sha3_512(), shake_128() и shake_256(). В качестве необязательного параметра функциям можно передать шифруемую последовательность байтов: >>> import hashlib >>> h = hashlib.sha1(b"password") Передать последовательность байтов можно также с помощью метода update(). В этом случае объект присоединяется к предыдущему значению: >>> h = hashlib.sha1() >>> h.update(b"password") Получить зашифрованную последовательность байтов и строку позволяют два метода: digest() и hexdigest(). Первый метод возвращает значение, относящееся к типу bytes, а второй — строку, содержащую шестнадцатеричные цифры: >>> h = hashlib.sha1(b"password") >>> h.digest() b'[\xaaa\xe4\xc9\xb9??\x06\x82%\x0bl\xf83\x1b\xe6\x8f\xd8' >>> h.hexdigest() '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' Наиболее часто применяемой является функция md5(), которая шифрует строку с помощью алгоритма MD5. Эта функция используется для шифрования паролей, т. к. не существует алгоритма для дешифровки зашифрованных ей значений. Для сравнения введенного поль- зователем пароля с сохраненным в базе необходимо зашифровать введенный пароль, а за- тем произвести сравнение: >>> import hashlib >>> h = hashlib.md5(b"password") >>> p = h.hexdigest() >>> p # Пароль, сохраненный в базе '5f4dcc3b5aa765d61d8327deb882cf99' >>> h2 = hashlib.md5(b"password") # Пароль, введенный пользователем >>> if p == h2.hexdigest(): print("Пароль правильный") Пароль правильный Свойство digest_size хранит размер значения, генерируемого описанными ранее функция- ми шифрования, в байтах: >>> h = hashlib.sha512(b"password") >>> h.digest_size 64 Поддерживается еще несколько функций, выполняющих устойчивое к взлому шифрование паролей: Глава 6. Строки и двоичные данные 121 pbkdf2_hmac(<Основной алгоритм шифрования>, <Шифруемый пароль>, <"Соль">, <Коли- чество проходов шифрования>, dklen=None). В качестве основного алгоритма шифрова- ния следует указать строку с наименованием этого алгоритма: "md5", "sha1", "sha224", "sha256", "sha384" и "sha512". Шифруемый пароль указывается в виде значения типа bytes"Соль" — это особая величина типа bytes, выступающая в качестве ключа шиф- рования, — ее длина не должна быть менее 16 символов. Количество проходов шифро- вания следует указать достаточно большим (так, при использовании алгоритма SHA512 оно должно составлять 100 000). Последним параметром функции pbkdf2_hmac() можно указать длину результирующего закодированного значения в байтах — если она не за- дана или равна None, будет создано значение стандартной для выбранного алгоритма длины (64 байта для алгоритма SHA512). Закодированный пароль возвращается в виде величины типа bytes: >>> import hashlib >>> dk = hashlib.pbkdf2_hmac('sha512', b'1234567', b'saltsaltsaltsalt', 100000) >>> dk b"Sb\x85tc-\xcb@\xc5\x97\x19\x90\x94@\x9f\xde\x07\xa4p-\x83\x94\xf4\x94\x99\x07\ xec\xfa\xf3\xcd\xc3\x88jv\xd1\xe5\x9a\x119\x15/\xa4\xc2\xd3N\xaba\x02\xc0s\xc1\ xd1\x0b\x86xj(\x8c>Mr'@\xbb" ПРИМЕЧАНИЕКодирование данных с применением функции pbkdf2_hmac() отнимает очень много сис- темных ресурсов и может занять значительное время, особенно на маломощных компью- терах. Поддержка двух следующих функций появилась в Python 3.6: blake2s([<Шифруемый пароль>][, digest_size=32], [salt=b””]) — шифрует пароль по алгоритму BLAKE2s, оптимизированному для 32-разрядных систем. Второй параметр задает размер значения в виде числа от 1 до 32, третий — «соль» в виде величины типа bytes, которая может иметь в длину не более 8 байтов. Возвращает объект, хранящий закодированный пароль: >>> h = hashlib.blake2s(b"string", digest_size=16, salt=b"saltsalt") >>> h.digest() b'\x96l\xe0\xfa\xb4\xe7Bw\x11\xf7D\xc2\xa4\xcf\x06\xf7' blake2b([<Шифруемый пароль>][, digest_size=64], [salt=b””]) — шифрует пароль по алгоритму BLAKE2b, оптимизированному для 64-разрядных систем. Второй параметр задает размер значения в виде числа от 1 до 64, третий — «соль» в виде величины типа bytes, которая может иметь в длину не более 16 байтов. Возвращает объект, хранящий закодированный пароль: >>> h = hashlib.blake2b(b"string", digest_size=48, salt=b"saltsaltsalt") >>> h.digest() b'\x0e\xcf\xb9\xc8G;q\xbaV\xbdV\x16\xd4@/J\x97W\x0c\xc4\xc5{\xd4\xb6\x12\x01z\ x9f\xdd\xf6\xf1\x03o\x97&v\xfd\xa6\x90\x81\xc4T\xb8z\xaf\xc3\x9a\xd9' ПРИМЕЧАНИЕФункции blake2b() и blake2s() поддерживают большое количество параметров, которые применяются только в специфических случаях. Полное описание этих функций можно най- ти в документации по Python. ГЛ А В А 7 Регулярные выражения Регулярные выражения предназначены для выполнения в строке сложного поиска или за- мены. В языке Python использовать регулярные выражения позволяет модуль re. Прежде чем задействовать функции из этого модуля, необходимо подключить модуль с помощью инструкции: import re 7.1. Синтаксис регулярных выражений Создать откомпилированный шаблон регулярного выражения позволяет функция compile(). Функция имеет следующий формат: <Шаблон> = re.compile(<Регулярное выражение>[, <Модификатор>]) В параметре <Модификатор> могут быть указаны следующие флаги (или их комбинация через оператор |): I или IGNORECASE — поиск без учета регистра: >>> import re >>> p = re.compile(r"^[а-яе]+$", re.I | re.U) >>> print("Найдено" if p.search("АБВГДЕЕ") else "Нет") Найдено >>> p = re.compile(r"^[а-яе]+$", re.U) >>> print("Найдено" if p.search("АБВГДЕЕ") else "Нет") Нет M или MULTILINE — поиск в строке, состоящей из нескольких подстрок, разделенных символом новой строки ("\n"). Символ ^ соответствует привязке к началу каждой под- строки, а символ $ — позиции перед символом перевода строки; S или DOTALL — метасимвол «точка» по умолчанию соответствует любому символу, кро- ме символа перевода строки (\n). Символу перевода строки метасимвол «точка» будет соответствовать в присутствии дополнительного модификатора. Символ ^ соответствует привязке к началу всей строки, а символ $ — привязке к концу всей строки: >>> p = re.compile(r"^.$") >>> print("Найдено" if p.search("\n") else "Нет") Нет >>> p = re.compile(r"^.$", re.M) Глава 7. Регулярные выражения 123 >>> print("Найдено" if p.search("\n") else "Нет") Нет >>> p = re.compile(r"^.$", re.S) >>> print("Найдено" if p.search("\n") else "Нет") Найдено X или VERBOSE — если флаг указан, то пробелы и символы перевода строки будут проиг- норированы. Внутри регулярного выражения можно использовать и комментарии: >>> p = re.compile(r"""^ # Привязка к началу строки [0-9]+ # Строка должна содержать одну цифру (или более) $ # Привязка к концу строки """, re.X | re.S) >>> print("Найдено" if p.search("1234567890") else "Нет") Найдено >>> print("Найдено" if p.search("abcd123") else "Нет") Нет A или ASCII — классы \w, \W, \b, \B, \d, \D, \s и \S будут соответствовать символам в ко- дировке ASCII (по умолчанию указанные классы соответствуют Unicode-символам); ПРИМЕЧАНИЕФлаги U и UNICODE, включающие режим соответствия Unicode-символам классов \w, \W, \b, \B, \d, \D, \s и \S, сохранены в Python 3 лишь для совместимости с ранними версиями это- го языка и никакого влияния на обработку регулярных выражений не оказывают. L или LOCALE — учитываются настройки текущей локали. Начиная с Python 3.6, могут быть использованы только в том случае, когда регулярное выражение задается в виде значения типов bytes или bytearrayКак видно из примеров, перед всеми строками, содержащими регулярные выражения, указан модификатор r. Иными словами, мы используем неформатированные строки. Если модификатор не указать, то все слэши необходимо экранировать. Например, строку: p = re.compile(r"^\w+$") нужно было бы записать так: p = re.compile("^\\w+$") Внутри регулярного выражения символы , ^, $, *, +, ?, {, [, ], \, |, ( и ) имеют специальное значение. Если эти символы должны трактоваться как есть, их следует экранировать с по- мощью слэша. Некоторые специальные символы теряют свое особое значение, если их раз- местить внутри квадратных скобок, — в этом случае экранировать их не нужно. Например, как уже было отмечено ранее, метасимвол «точка» по умолчанию соответствует любому символу, кроме символа перевода строки. Если необходимо найти именно точку, то перед точкой нужно указать символ \ или разместить точку внутри квадратных скобок: [.]. Про- демонстрируем это на примере проверки правильности введенной даты (листинг 7.1). Листинг 7.1. Проверка правильности ввода даты # -*- coding: utf-8 -*- import re # Подключаем модуль d = "29,12.2009" # Вместо точки указана запятая p = re.compile(r"^[0-3][0-9].[01][0-9].[12][09][0-9][0-9]$") 124 Часть I. Основы языка Python # Символ "\" не указан перед точкой if p.search(d): print("Дата введена правильно") else: print("Дата введена неправильно") # Так как точка означает любой символ, # выведет: Дата введена правильно p = re.compile(r"^[0-3][0-9]\.[01][0-9]\.[12][09][0-9][0-9]$") # Символ "\" указан перед точкой if p.search(d): print("Дата введена правильно") else: print("Дата введена неправильно") # Так как перед точкой указан символ "\", # выведет: Дата введена неправильно p = re.compile(r"^[0-3][0-9][.][01][0-9][.][12][09][0-9][0-9]$") # Точка внутри квадратных скобок if p.search(d): print("Дата введена правильно") else: print("Дата введена неправильно") # Выведет: Дата введена неправильно input() В этом примере мы осуществляли привязку к началу и концу строки с помощью следующих метасимволов: ^ — привязка к началу строки или подстроки. Она зависит от флагов M (или MULTILINE) и S (или DOTALL); $ — привязка к концу строки или подстроки. Она зависит от флагов M (или MULTILINE) и S (или DOTALL); \A — привязка к началу строки (не зависит от модификатора); \Z — привязка к концу строки (не зависит от модификатора). Если в параметре <Модификатор> указан флаг M (или MULTILINE), то поиск производится в строке, состоящей из нескольких подстрок, разделенных символом новой строки (\n). В этом случае символ ^ соответствует привязке к началу каждой подстроки, а символ $ — позиции перед символом перевода строки: >>> p = re.compile(r"^.+$") # Точка не соответствует \n >>> p.findall("str1\nstr2\nstr3") # Ничего не найдено [] >>> p = re.compile(r"^.+$", re.S) # Теперь точка соответствует \n >>> p.findall("str1\nstr2\nstr3") # Строка полностью соответствует ['str1\nstr2\nstr3'] >>> p = re.compile(r"^.+$", re.M) # Многострочный режим >>> p.findall("str1\nstr2\nstr3") # Получили каждую подстроку ['str1', 'str2', 'str3'] Глава 7. Регулярные выражения 125 Привязку к началу и концу строки следует использовать, если строка должна полностью соответствовать регулярному выражению. Например, для проверки, содержит ли строка число (листинг 7.2). Листинг 7.2. Проверка наличия целого числа в строке # -*- coding: utf-8 -*- import re # Подключаем модуль p = re.compile(r"^[0-9]+$", re.S) if p.search("245"): print("Число") # Выведет: Число else: print("Не число") if p.search("Строка245"): print("Число") else: print("Не число") # Выведет: Не число input() Если убрать привязку к началу и концу строки, то любая строка, содержащая хотя бы одну цифру, будет распознана как Число (листинг 7.3). Листинг 7.3. Отсутствие привязки к началу или концу строки # -*- coding: utf-8 -*- import re # Подключаем модуль p = re.compile(r"[0-9]+", re.S) if p.search("Строка245"): print("Число") # Выведет: Число else: print("Не число") input() Кроме того, можно указать привязку только к началу или только к концу строки (листинг 7.4). Листинг 7.4. Привязка к началу и концу строки # -*- coding: utf-8 -*- import re # Подключаем модуль p = re.compile(r"[0-9]+$", re.S) if p.search("Строка245"): print("Есть число в конце строки") else: print("Нет числа в конце строки") # Выведет: Есть число в конце строки p = re.compile(r"^[0-9]+", re.S) if p.search("Строка245"): print("Есть число в начале строки") else: print("Нет числа в начале строки") # Выведет: Нет числа в начале строки input() 126 Часть I. Основы языка Python Также поддерживаются два метасимвола, позволяющие указать привязку к началу или кон- цу слова: \b — привязка к началу слова (началом слова считается пробел или любой символ, не являющийся буквой, цифрой или знаком подчеркивания); \B — привязка к позиции, не являющейся началом слова. Рассмотрим несколько примеров: >>> p = re.compile(r"\bpython\b") >>> print("Найдено" if p.search("python") else "Нет") Найдено >>> print("Найдено" if p.search("pythonware") else "Нет") Нет >>> p = re.compile(r"\Bth\B") >>> print("Найдено" if p.search("python") else "Нет") Найдено >>> print("Найдено" if p.search("this") else "Нет") Нет В квадратных скобках [] можно указать символы, которые могут встречаться на этом месте в строке. Разрешается записать символы подряд или указать диапазон через дефис: [09] — соответствует числу 0 или 9; [0-9] — соответствует любому числу от 0 до 9; [абв] — соответствует буквам «а», «б» и «в»; [а-г] — соответствует буквам «а», «б», «в» и «г»; [а-яё] — соответствует любой букве от «а» до «я»; [АБВ] — соответствует буквам «А», «Б» и «В»; [А-ЯЁ] — соответствует любой букве от «А» до «Я»; [а-яА-ЯёЁ] — соответствует любой русской букве в любом регистре; [0-9а-яА-ЯёЁa-zA-Z] — любая цифра и любая буква независимо от регистра и языка. ВНИМАНИЕ! Буква «ё» не входит в диапазон [а-я], а буква «Ё» — в диапазон [А-Я]. Значение в скобках инвертируется, если после первой скобки вставить символ ^. Таким образом можно указать символы, которых не должно быть на этом месте в строке: [^09] — не цифра 0 или 9; [^0-9] — не цифра от 0 до 9; [^а-яА-ЯёЁa-zA-Z] — не буква. Как вы уже знаете, точка теряет свое специальное значение, если ее заключить в квадрат- ные скобки. Кроме того, внутри квадратных скобок могут встретиться символы, которые имеют специальное значение (например, ^ и -). Символ ^ теряет свое специальное значение, если он не расположен сразу после открывающей квадратной скобки. Чтобы отменить спе- циальное значение символа -, его необходимо указать после всех символов, перед закры- вающей квадратной скобкой или сразу после открывающей квадратной скобки. Все специ- альные символы можно сделать обычными, если перед ними указать символ \ Глава 7. Регулярные выражения 127 Метасимвол | позволяет сделать выбор между альтернативными значениями. Выражение n|m соответствует одному из символов: n или m: >>> p = re.compile(r"красн((ая)|(ое))") >>> print("Найдено" if p.search("красная") else "Нет") Найдено >>> print("Найдено" if p.search("красное") else "Нет") Найдено >>> print("Найдено" if p.search("красный") else "Нет") Нет Вместо указания символов можно использовать стандартные классы: \d — соответствует любой цифре. При указании флага A (ASCII) эквивалентно [0-9]; \w — соответствует любой букве, цифре или символу подчеркивания. При указании фла- га A (ASCII) эквивалентно [a-zA-Z0-9_]; \s — любой пробельный символ. При указании флага A (ASCII) эквивалентно [\t\n\r\f\v]; \D — не цифра. При указании флага A (ASCII) эквивалентно [^0-9]; \W — не буква, не цифра и не символ подчеркивания. При указании флага A (ASCII) экви- валентно [^a-zA-Z0-9_]; \S — не пробельный символ. При указании флага A (ASCII) эквивалентно [^\t\n\r\f\v]ПРИМЕЧАНИЕВ Python 3 поддержка Unicode в регулярных выражениях установлена по умолчанию. При этом все классы трактуются гораздо шире. Так, класс \d соответствует не только десятич- ным цифрам, но и другим цифрам из кодировки Unicode, — например, дробям, класс \w включает не только латинские буквы, но и любые другие, а класс \s охватывает также не- разрывные пробелы. Поэтому на практике лучше явно указывать символы внутри квадрат- ных скобок, а не использовать классы. Количество вхождений символа в строку задается с помощью квантификаторов: {n} — n вхождений символа в строку. Например, шаблон r"^[0-9]{2}$" соответствует двум вхождениям любой цифры; {n,} — n или более вхождений символа в строку. Например, шаблон r"^[0-9]{2,}$"соответствует двум и более вхождениям любой цифры; {n,m} — не менее n и не более m вхождений символа в строку. Числа указываются через запятую без пробела. Например, шаблон r"^[0-9]{2,4}$" соответствует от двух до четырех вхождений любой цифры; * — ноль или большее число вхождений символа в строку. Эквивалентно комбинации {0,}; + — одно или большее число вхождений символа в строку. Эквивалентно комбинации {1,}; ? — ни одного или одно вхождение символа в строку. Эквивалентно комбинации {0,1}Все квантификаторы являются «жадными». При поиске соответствия ищется самая длинная подстрока, соответствующая шаблону, и не учитываются более короткие соответствия. Рас- смотрим это на примере и получим содержимое всех тегов вместе с тегами: >>> s = "Text1Text2Text3" >>> p = re.compile(r".*", re.S) 128 Часть I. Основы языка Python >>> p.findall(s) ['Text1Text2Text3'] Вместо желаемого результата мы получили полностью строку. Чтобы ограничить «жад- ность», необходимо после квантификатора указать символ ?: >>> s = "Text1Text2Text3" >>> p = re.compile(r".*?", re.S) >>> p.findall(s) ['Text1', 'Text3'] Этот код вывел то, что мы искали. Если необходимо получить содержимое без тегов, то нужный фрагмент внутри шаблона следует разместить внутри круглых скобок: >>> s = "Text1Text2Text3" >>> p = re.compile(r"(.*?)", re.S) >>> p.findall(s) ['Text1', 'Text3'] Круглые скобки часто используются для группировки фрагментов внутри шаблона. В этом случае не требуется, чтобы фрагмент запоминался и был доступен в результатах поиска. Чтобы избежать захвата фрагмента, следует после открывающей круглой скобки разместить символы ?: (вопросительный знак и двоеточие): >>> s = "test text" >>> p = re.compile(r"([a-z]+((st)|(xt)))", re.S) >>> p.findall(s) [('test', 'st', 'st', ''), ('text', 'xt', '', 'xt')] >>> p = re.compile(r"([a-z]+(?:(?:st)|(?:xt)))", re.S) >>> p.findall(s) ['test', 'text'] В первом примере мы получили список с двумя элементами. Каждый элемент списка явля- ется кортежем, содержащим четыре элемента. Все эти элементы соответствуют фрагмен- там, заключенным в шаблоне в круглые скобки. Первый элемент кортежа содержит фраг- мент, расположенный в первых круглых скобках, второй — во вторых круглых скобках и т. д. Три последних элемента кортежа являются лишними. Чтобы они не выводились в результатах, мы добавили символы ?: после каждой открывающей круглой скобки. В ре- зультате список состоит только из фрагментов, полностью соответствующих регулярному выражению. К найденному фрагменту в круглых скобках внутри шаблона можно обратиться с помощью механизма обратных ссылок. Для этого порядковый номер круглых скобок в шаблоне ука- зывается после слэша, например, так: \1. Нумерация скобок внутри шаблона начинается с 1Для примера получим текст между одинаковыми парными тегами: >>> s = "Text1Text2Text3Text4" >>> p = re.compile(r"<([a-z]+)>(.*?)\1>", re.S | re.I) >>> p.findall(s) [('b', 'Text1'), ('I', 'Text3'), ('b', 'Text4')] Фрагментам внутри круглых скобок можно дать имена, создав тем самым именованные фрагменты. Для этого после открывающей круглой скобки следует указать комбинацию символов ?P. В качестве примера разберем e-mail на составные части: Глава 7. Регулярные выражения 129 >>> email = "test@mail.ru" >>> p = re.compile(r"""(?P[a-z0-9_.-]+) # Название ящика @ # Символ "@" (?P1   ...   8   9   10   11   12   13   14   15   ...   83

254 Часть I. Основы языка Python __getattribute__(self, <Атрибут>) — вызывается при обращении к любому атрибуту класса. Необходимо учитывать, что использование точечной нотации (для обращения к атрибуту класса) внутри этого метода приведет к зацикливанию. Чтобы избежать за- цикливания, следует вызвать метод __getattribute__() объекта object и внутри этого метода вернуть значение атрибута или возбудить исключение AttributeError: class MyClass: def __init__(self): self.i = 20 def __getattribute__(self, attr): print("Вызван метод __getattribute__()") return object.__getattribute__(self, attr) # Только так!!! c = MyClass() print(c.i) # Выведет: Вызван метод __getattribute__() 20 __setattr__(self, <Атрибут>, <Значение>) — вызывается при попытке присваивания значения атрибуту экземпляра класса. Если внутри метода необходимо присвоить зна- чение атрибуту, следует использовать словарь __dict__, поскольку при применении то- чечной нотации метод __setattr__() будет вызван повторно, что приведет к зациклива- нию: class MyClass: def __setattr__(self, attr, value): print("Вызван метод __setattr__()") self.__dict__[attr] = value # Только так!!! c = MyClass() c.i = 10 # Выведет: Вызван метод __setattr__() print(c.i) # Выведет: 10 __delattr__(self, <Атрибут>) — вызывается при удалении атрибута с помощью инст- рукции del <Экземпляр класса>.<Атрибут>; __len__(self) — вызывается при использовании функции len(), а также для проверки объекта на логическое значение при отсутствии метода __bool__(). Метод должен воз- вращать положительное целое число: class MyClass: def __len__(self): return 50 c = MyClass() print(len(c)) # Выведет: 50 __bool__(self) — вызывается при использовании функции bool(); __int__(self) — вызывается при преобразовании объекта в целое число с помощью функции int(); __float__(self) — вызывается при преобразовании объекта в вещественное число с по- мощью функции float(); __complex__(self) — вызывается при преобразовании объекта в комплексное число с помощью функции complex(); __round__(self, n) — вызывается при использовании функции round(); __index__(self) — вызывается при использовании функций bin(), hex() и oct(); Глава 13. Объектно-ориентированное программирование 255 __repr__(self) и __str__(self) — служат для преобразования объекта в строку. Метод __repr__() вызывается при выводе в интерактивной оболочке, а также при использова- нии функции repr(). Метод __str__() вызывается при выводе с помощью функции print(), а также при использовании функции str(). Если метод __str__() отсутствует, будет вызван метод __repr__(). В качестве значения методы __repr__() и __str__()должны возвращать строку: class MyClass: def __init__(self, m): self.msg = m def __repr__(self): return "Вызван метод __repr__() {0}".format(self.msg) def __str__(self): return "Вызван метод __str__() {0}".format(self.msg) c = MyClass("Значение") print(repr(c)) # Выведет: Вызван метод __repr__() Значение print(str(c)) # Выведет: Вызван метод __str__() Значение print(c) # Выведет: Вызван метод __str__() Значение __hash__(self) — этот метод следует переопределить, если экземпляр класса планиру- ется использовать в качестве ключа словаря или внутри множества: class MyClass: def __init__(self, y): self.x = y def __hash__(self): return hash(self.x) c = MyClass(10) d = {} d[c] = "Значение" print(d[c]) # Выведет: Значение Классы поддерживают еще несколько специальных методов, которые применяются лишь в особых случаях и будут рассмотрены в главе 15. 13.6. Перегрузка операторов Перегрузка операторов позволяет экземплярам классов участвовать в обычных операциях. Чтобы перегрузить оператор, необходимо в классе определить метод со специальным на- званием. Для перегрузки математических операторов используются следующие методы: x + y — сложение: x.__add__(y); y + x — сложение (экземпляр класса справа): x.__radd__(y); x += y — сложение и присваивание: x.__iadd__(y); x — y — вычитание: x.__sub__(y); y — x — вычитание (экземпляр класса справа): x.__rsub__(y); x -= y — вычитание и присваивание: x.__isub__(y); x * y — умножение: x.__mul__(y); y * x — умножение (экземпляр класса справа): x.__rmul__(y); 256 Часть I. Основы языка Python x *= y — умножение и присваивание: x.__imul__(y); x / y — деление: x.__truediv__(y); y / x — деление (экземпляр класса справа): x.__rtruediv__(y); x /= y — деление и присваивание: x.__itruediv__(y); x // y — деление с округлением вниз: x.__floordiv__(y); y // x — деление с округлением вниз (экземпляр класса справа): x.__rfloordiv__(y); x //= y — деление с округлением вниз и присваивание: x.__ifloordiv__(y); x % y — остаток от деления: x.__mod__(y); y % x — остаток от деления (экземпляр класса справа): x.__rmod__(y); x %= y — остаток от деления и присваивание: x.__imod__(y); x ** y — возведение в степень: x.__pow__(y); y ** x — возведение в степень (экземпляр класса справа): x.__rpow__(y); x **= y — возведение в степень и присваивание: x.__ipow__(y); -x — унарный минус: x.__neg__(); +x — унарный плюс: x.__pos__(); abs(x) — абсолютное значение: x.__abs__()Пример перегрузки математических операторов приведен в листинге 13.13. Листинг 13.13. Пример перегрузки математических операторов class MyClass: def __init__(self, y): self.x = y def __add__(self, y): # Перегрузка оператора + print("Экземпляр слева") return self.x + y def __radd__(self, y): # Перегрузка оператора + print("Экземпляр справа") return self.x + y def __iadd__(self, y): # Перегрузка оператора += print("Сложение с присваиванием") self.x += y return self c = MyClass(50) print(c + 10) # Выведет: Экземпляр слева 60 print(20 + c) # Выведет: Экземпляр справа 70 c += 30 # Выведет: Сложение с присваиванием print(c.x) # Выведет: 80 Методы перегрузки двоичных операторов: x — двоичная инверсия: x.__invert__(); x & y — двоичное И: x.__and__(y); Глава 13. Объектно-ориентированное программирование 257 y & x — двоичное И (экземпляр класса справа): x.__rand__(y); x &= y — двоичное И и присваивание: x.__iand__(y); x | y — двоичное ИЛИ: x.__or__(y); y | x — двоичное ИЛИ (экземпляр класса справа): x.__ror__(y); x |= y — двоичное ИЛИ и присваивание: x.__ior__(y); x ^ y — двоичное исключающее ИЛИ: x.__xor__(y); y ^ x — двоичное исключающее ИЛИ (экземпляр класса справа): x.__rxor__(y); x ^= y — двоичное исключающее ИЛИ и присваивание: x.__ixor__(y); x << y — сдвиг влево: x.__lshift__(y); y << x — сдвиг влево (экземпляр класса справа): x.__rlshift__(y); x <<= y — сдвиг влево и присваивание: x.__ilshift__(y); x >> y — сдвиг вправо: x.__rshift__(y); y >> x — сдвиг вправо (экземпляр класса справа): x.__rrshift__(y); x >>= y — сдвиг вправо и присваивание: x.__irshift__(y)Перегрузка операторов сравнения производится с помощью следующих методов: x == y — равно: x.__eq__(y); x != y — не равно: x.__ne__(y); x < y — меньше: x.__lt__(y); x > y — больше: x.__gt__(y); x <= y — меньше или равно: x.__le__(y); x >= y — больше или равно: x.__ge__(y); y in x — проверка на вхождение: x.__contains__(y)Пример перегрузки операторов сравнения приведен в листинге 13.14. Листинг 13.14. Пример перегрузки операторов сравнения class MyClass: def __init__(self): self.x = 50 self.arr = [1, 2, 3, 4, 5] def __eq__(self, y): # Перегрузка оператора == return self.x == y def __contains__(self, y): # Перегрузка оператора in return y in self.arr c = MyClass() print("Равно" if c == 50 else "Не равно") # Выведет: Равно print("Равно" if c == 51 else "Не равно") # Выведет: Не равно print("Есть" if 5 in c else "Нет") # Выведет: Есть 258 Часть I. Основы языка Python 13.7. Статические методы и методы класса Внутри класса можно создать метод, который будет доступен без создания экземпляра класса (статический метод). Для этого перед определением метода внутри класса следует указать декоратор @staticmethod. Вызов статического метода без создания экземпляра клас- са осуществляется следующим образом: <Название класса>.<Название метода>(<Параметры>) Кроме того, можно вызвать статический метод через экземпляр класса: <Экземпляр класса>.<Название метода>(<Параметры>) Пример использования статических методов приведен в листинге 13.15. Листинг 13.15. Статические методы class MyClass: @staticmethod def func1(x, y): # Статический метод return x + y def func2(self, x, y): # Обычный метод в классе return x + y def func3(self, x, y): return MyClass.func1(x, y) # Вызов из метода класса print(MyClass.func1(10, 20)) # Вызываем статический метод c = MyClass() print(c.func2(15, 6)) # Вызываем метод класса print(c.func1(50, 12)) # Вызываем статический метод # через экземпляр класса print(c.func3(23, 5)) # Вызываем статический метод # внутри класса Обратите внимание на то, что в определении статического метода нет параметра self. Это означает, что внутри статического метода нет доступа к атрибутам и методам экземпляра класса. Методы класса создаются с помощью декоратора @classmethod. В качестве первого пара- метра в метод класса передается ссылка на класс. Вызов метода класса осуществляется сле- дующим образом: <Название класса>.<Название метода>(<Параметры>) Кроме того, можно вызвать метод класса через экземпляр класса: <Экземпляр класса>.<Название метода>(<Параметры>) Пример использования методов класса приведен в листинге 13.16. Листинг 13.16. Методы класса class MyClass: @classmethod def func(cls, x): # Метод класса print(cls, x) Глава 13. Объектно-ориентированное программирование 259 MyClass.func(10) # Вызываем метод через название класса c = MyClass() c.func(50) # Вызываем метод класса через экземпляр 13.8. Абстрактные методы Абстрактные методы содержат только определение метода без реализации. Предполагает- ся, что производный класс должен переопределить метод и реализовать его функциональ- ность. Чтобы такое предположение сделать более очевидным, часто внутри абстрактного метода возбуждают исключение (листинг 13.17). Листинг 13.17. Абстрактные методы class Class1: def func(self, x): # Абстрактный метод # Возбуждаем исключение с помощью raise raise NotImplementedError("Необходимо переопределить метод") class Class2(Class1): # Наследуем абстрактный метод def func(self, x): # Переопределяем метод print(x) class Class3(Class1): # Класс не переопределяет метод pass c2 = Class2() c2.func(50) # Выведет: 50 c3 = Class3() try: # Перехватываем исключения c3.func(50) # Ошибка. Метод func() не переопределен except NotImplementedError as msg: print(msg) # Выведет: Необходимо переопределить метод В состав стандартной библиотеки входит модуль abc. В этом модуле определен декоратор @abstractmethod, который позволяет указать, что метод, перед которым расположен декора- тор, является абстрактным. При попытке создать экземпляр производного класса, в котором не переопределен абстрактный метод, возбуждается исключение TypeError. Рассмотрим использование декоратора @abstractmethod на примере (листинг 13.18). Листинг 13.18. Использование декоратора @abstractmethod from abc import ABCMeta, abstractmethod class Class1(metaclass=ABCMeta): @abstractmethod def func(self, x): # Абстрактный метод pass class Class2(Class1): # Наследуем абстрактный метод def func(self, x): # Переопределяем метод print(x) 260 Часть I. Основы языка Python class Class3(Class1): # Класс не переопределяет метод pass c2 = Class2() c2.func(50) # Выведет: 50 try: c3 = Class3() # Ошибка. Метод func() не переопределен c3.func(50) except TypeError as msg: print(msg) # Can't instantiate abstract class Class3 # with abstract methods func Имеется возможность создания абстрактных статических методов и абстрактных методов класса, для чего необходимые декораторы указываются одновременно, друг за другом. Для примера объявим класс с абстрактными статическим методом и методом класса (лис- тинг 13.19). Листинг 13.19. Абстрактный статический метод и абстрактный метод класса from abc import ABCMeta, abstractmethod class MyClass(metaclass=ABCMeta): @staticmethod @abstractmethod def static_func(self, x): # Абстрактный статический метод pass @classmethod @abstractmethod def class_func(self, x): # Абстрактный метод класса pass 13.9. Ограничение доступа к идентификаторам внутри класса Все идентификаторы внутри класса в языке Python являются открытыми, т. е. доступны для непосредственного изменения. Для имитации частных идентификаторов можно воспользо- ваться методами __getattr__(), __getattribute__() и __setattr__(), которые перехваты- вают обращения к атрибутам класса. Кроме того, можно воспользоваться идентификатора- ми, названия которых начинаются с двух символов подчеркивания. Такие идентификаторы называются псевдочастными. Псевдочастные идентификаторы доступны лишь внутри класса, но никак не вне его. Тем не менее изменить идентификатор через экземпляр класса все равно можно, зная, каким образом искажается название идентификатора. Напри- мер, идентификатор __privateVar внутри класса Class1 будет доступен по имени _Class1__ privateVar. Как можно видеть, здесь перед идентификатором добавляется название класса с предваряющим символом подчеркивания. Приведем пример использования псевдочаст- ных идентификаторов (листинг 13.20). Глава 13. Объектно-ориентированное программирование 261 Листинг 13.20. Псевдочастные идентификаторы class MyClass: def __init__(self, x): self.__privateVar = x def set_var(self, x): # Изменение значения self.__privateVar = x def get_var(self): # Получение значения return self.__privateVar c = MyClass(10) # Создаем экземпляр класса print(c.get_var()) # Выведет: 10 c.set_var(20) # Изменяем значение print(c.get_var()) # Выведет: 20 try: # Перехватываем ошибки print(c.__privateVar) # Ошибка!!! except AttributeError as msg: print(msg) # Выведет: 'MyClass' object has # no attribute '__privateVar' c._MyClass__privateVar = 50 # Значение псевдочастных атрибутов # все равно можно изменить print(c.get_var()) # Выведет: 50 Можно также ограничить перечень атрибутов, разрешенных для экземпляров класса. Для этого разрешенные атрибуты указываются внутри класса в атрибуте __slots__. В качестве значения атрибуту можно присвоить строку или список строк с названиями идентификато- ров. Если производится попытка обращения к атрибуту, не указанному в __slots__, возбу- ждается исключение AttributeError (листинг 13.21). Листинг 13.21. Использование атрибута __slots__ class MyClass: __slots__ = ["x", "y"] def __init__(self, a, b): self.x, self.y = a, b c = MyClass(1, 2) print(c.x, c.y) # Выведет: 1 2 c.x, c.y = 10, 20 # Изменяем значения атрибутов print(c.x, c.y) # Выведет: 10 20 try: # Перехватываем исключения c.z = 50 # Атрибут z не указан в __slots__, # поэтому возбуждается исключение except AttributeError as msg: print(msg) # 'MyClass' object has no attribute 'z' 13.10. Свойства класса Внутри класса можно создать идентификатор, через который в дальнейшем будут произво- диться операции получения и изменения значения какого-либо атрибута, а также его удале- ния. Создается такой идентификатор с помощью функции property(). Формат функции: 262 Часть I. Основы языка Python <Свойство> = property(<Чтение>[, <Запись>[, <Удаление> [, <Строка документирования>]]]) В первых трех параметрах указывается ссылка на соответствующий метод класса. При по- пытке получить значение будет вызван метод, указанный в первом параметре. При опера- ции присваивания значения будет вызван метод, указанный во втором параметре, — этот метод должен принимать один параметр. В случае удаления атрибута вызывается метод, указанный в третьем параметре. Если в качестве какого-либо параметра задано значение None, то это означает, что соответствующий метод не поддерживается. Рассмотрим свойства класса на примере (листинг 13.22). Листинг 13.22. Свойства класса class MyClass: def __init__(self, value): self.__var = value def get_var(self): # Чтение return self.__var def set_var(self, value): # Запись self.__var = value def del_var(self): # Удаление del self.__var v = property(get_var, set_var, del_var, "Строка документирования") c = MyClass(5) c.v = 35 # Вызывается метод set_var() print(c.v) # Вызывается метод get_var() del c.v # Вызывается метод del_var() Python поддерживает альтернативный метод определения свойств — с помощью методов getter(), setter() и deleter(), которые используются в декораторах. Соответствующий пример приведен в листинге 13.23. Листинг 13.23. Методы getter(), setter() и deleter() class MyClass: def __init__(self, value): self.__var = value @property def v(self): # Чтение return self.__var @v.setter def v(self, value): # Запись self.__var = value @v.deleter def v(self): # Удаление del self.__var c = MyClass(5) c.v = 35 # Запись print(c.v) # Чтение del c.v # Удаление Глава 13. Объектно-ориентированное программирование 263 Имеется возможность определить абстрактное свойство — в этом случае все реализующие его методы должны быть переопределены в подклассе. Выполняется это с помощью знако- мого нам декоратора 1   ...   21   22   23   24   25   26   27   28   ...   83

QMargins из модуля QtCore, конструктор которого имеет следующий формат: QMargins(<Граница слева>, <Граница сверху>, <Граница справа>, <Граница снизу>) Текущая область при этом не изменяется: >>> r1 = QtCore.QRect(10, 15, 400, 300) >>> m = QtCore.QMargins(5, 2, 5, 2) 376 Часть II. Библиотека PyQt 5 >>> r2 = r1.marginsAdded(m) >>> r2 PyQt5.QtCore.QRect(5, 13, 410, 304) >>> r1 PyQt5.QtCore.QRect(10, 15, 400, 300) marginsRemoved() — то же самое, что marginsAdded(), но уменьшает новую область на заданные величины границ: >>> r1 = QtCore.QRect(10, 15, 400, 300) >>> m = QtCore.QMargins(2, 10, 2, 10) >>> r2 = r1.marginsRemoved(m) >>> r2 PyQt5.QtCore.QRect(12, 25, 396, 280) >>> r1 PyQt5.QtCore.QRect(10, 15, 400, 300) Переместить область при изменении координат позволяют следующие методы: moveTo(, ), moveTo(), moveLeft() и moveTop() — задают новые координаты левого верхнего угла: >>> r = QtCore.QRect(10, 15, 400, 300) >>> r.moveTo(0, 0); r PyQt5.QtCore.QRect(0, 0, 400, 300) >>> r.moveTo(QtCore.QPoint(10, 10)); r PyQt5.QtCore.QRect(10, 10, 400, 300) >>> r.moveLeft(5); r.moveTop(0); r PyQt5.QtCore.QRect(5, 0, 400, 300) moveRight() и moveBottom() — задают новые координаты правого нижнего угла; moveTopLeft() — задает новые координаты левого верхнего угла; moveTopRight() — задает новые координаты правого верхнего угла; moveBottomLeft() — задает новые координаты левого нижнего угла; moveBottomRight() — задает новые координаты правого нижнего угла. Примеры: >>> r = QtCore.QRect(10, 15, 400, 300) >>> r.moveTopLeft(QtCore.QPoint(0, 0)); r PyQt5.QtCore.QRect(0, 0, 400, 300) >>> r.moveBottomRight(QtCore.QPoint(599, 499)); r PyQt5.QtCore.QRect(200, 200, 400, 300) moveCenter() — задает новые координаты центра; translate(<Сдвиг по оси X>, <Сдвиг по оси Y>) и translate() — задают но- вые координаты левого верхнего угла относительно текущего значения координат: >>> r = QtCore.QRect(0, 0, 400, 300) >>> r.translate(20, 15); r PyQt5.QtCore.QRect(20, 15, 400, 300) >>> r.translate(QtCore.QPoint(10, 5)); r PyQt5.QtCore.QRect(30, 20, 400, 300) Глава 18. Управление окном приложения 377 translated(<Сдвиг по оси X>, <Сдвиг по оси Y>) и translated() — анало- гичен методу translate(), но возвращает новый экземпляр класса QRect, а не изменяет текущий; adjust(, , , ) — задает новые координаты левого верхнего и правого нижнего углов относительно текущих значений координат: >>> r = QtCore.QRect(0, 0, 400, 300) >>> r.adjust(10, 5, 10, 5); r PyQt5.QtCore.QRect(10, 5, 400, 300) adjusted(, , , ) — аналогичен методу adjust(), но возвращает но- вый экземпляр класса QRect, а не изменяет текущий. Для получения параметров области предназначены следующие методы: left() и x() — возвращают координату левого верхнего угла по оси X; top() и y() — возвращают координату левого верхнего угла по оси Y; right() и bottom() — возвращают координаты правого нижнего угла по осям X и Y соот- ветственно; width() и height() — возвращают ширину и высоту соответственно; size() — возвращает размеры в виде экземпляра класса QSizeПримеры: >>> r = QtCore.QRect(10, 15, 400, 300) >>> r.left(), r.top(), r.x(), r.y(), r.right(), r.bottom() (10, 15, 10, 15, 409, 314) >>> r.width(), r.height(), r.size() (400, 300, PyQt5.QtCore.QSize(400, 300)) topLeft() — возвращает координаты левого верхнего угла; topRight() — возвращает координаты правого верхнего угла; bottomLeft() — возвращает координаты левого нижнего угла; bottomRight() — возвращает координаты правого нижнего угла. Примеры: >>> r = QtCore.QRect(10, 15, 400, 300) >>> r.topLeft(), r.topRight() (PyQt5.QtCore.QPoint(10, 15), PyQt5.QtCore.QPoint(409, 15)) >>> r.bottomLeft(), r.bottomRight() (PyQt5.QtCore.QPoint(10, 314), PyQt5.QtCore.QPoint(409, 314)) center() — возвращает координаты центра области. Например, вывести окно по центру доступной области экрана можно так: desktop = QtWidgets.QApplication.desktop() window.move(desktop.availableGeometry().center() - window.rect().center()) getRect() — возвращает кортеж с координатами левого верхнего угла и размерами об- ласти; getCoords() — возвращает кортеж с координатами левого верхнего и правого нижнего углов: 378 Часть II. Библиотека PyQt 5 >>> r = QtCore.QRect(10, 15, 400, 300) >>> r.getRect(), r.getCoords() ((10, 15, 400, 300), (10, 15, 409, 314)) Прочие методы: isNull() — возвращает True, если ширина и высота равны нулю, и False — в противном случае; isValid() — возвращает True, если left() < right() и top() < bottom(), и False — в противном случае; isEmpty() — возвращает True, если left() > right() или top() > bottom(), и False — в противном случае; normalized() — исправляет ситуацию, при которой left() > right() или top() > bottom(), и возвращает новый экземпляр класса QRect: >>> r = QtCore.QRect(QtCore.QPoint(409, 314), QtCore.QPoint(10, 15)) >>> r PyQt5.QtCore.QRect(409, 314, -398, -298) >>> r.normalized() PyQt5.QtCore.QRect(10, 15, 400, 300) contains([, <Флаг>]) и contains(, [, <Флаг>]) — возвращает True, если точка с указанными координатами расположена внутри области или на ее границе, и False — в противном случае. Если во втором параметре указано значение True, то точка должна быть расположена только внутри области, а не на ее границе. Значение парамет- ра по умолчанию — False: >>> r = QtCore.QRect(0, 0, 400, 300) >>> r.contains(0, 10), r.contains(0, 10, True) (True, False) contains([, <Флаг>]) — возвращает True, если указанная область расположена внутри текущей области или на ее краю, и False — в противном случае. Если во втором параметре указано значение True, то указанная область должна быть расположена толь- ко внутри текущей области, а не на ее краю. Значение параметра по умолчанию — False: >>> r = QtCore.QRect(0, 0, 400, 300) >>> r.contains(QtCore.QRect(0, 0, 20, 5)) True >>> r.contains(QtCore.QRect(0, 0, 20, 5), True) False intersects() — возвращает True, если указанная область пересекается с текущей областью, и False — в противном случае; intersected() — возвращает область, которая расположена на пересечении те- кущей и указанной областей: >>> r = QtCore.QRect(0, 0, 20, 20) >>> r.intersects(QtCore.QRect(10, 10, 20, 20)) True >>> r.intersected(QtCore.QRect(10, 10, 20, 20)) PyQt5.QtCore.QRect(10, 10, 10, 10) Глава 18. Управление окном приложения 379 united() — возвращает область, которая охватывает текущую и указанную об- ласти: >>> r = QtCore.QRect(0, 0, 20, 20) >>> r.united(QtCore.QRect(30, 30, 20, 20)) PyQt5.QtCore.QRect(0, 0, 50, 50) Над двумя экземплярами класса QRect можно выполнять операции & и &= (пересечение), | и |= (объединение), in (проверка на вхождение), == и !=Пример: >>> r1, r2 = QtCore.QRect(0, 0, 20, 20), QtCore.QRect(10, 10, 20, 20) >>> r1 & r2, r1 | r2 (PyQt5.QtCore.QRect(10, 10, 10, 10), PyQt5.QtCore.QRect(0, 0, 30, 30)) >>> r1 in r2, r1 in QtCore.QRect(0, 0, 30, 30) (False, True) >>> r1 == r2, r1 != r2 (False, True) Помимо этого, поддерживаются операторы + и -, выполняющие увеличение и уменьшение области на заданные величины границ, которые должны быть заданы в виде объекта класса QMargins: >>> r = QtCore.QRect(10, 15, 400, 300) >>> m = QtCore.QMargins(5, 15, 5, 15) >>> r + m PyQt5.QtCore.QRect(5, 0, 410, 330) >>> r - m PyQt5.QtCore.QRect(15, 30, 390, 270) 18.6. Разворачивание и сворачивание окна В заголовке окна расположены кнопки Свернуть и Развернуть, с помощью которых мож- но свернуть окно в значок на панели задач или развернуть его на весь экран. Выполнить подобные действия из программы позволяют следующие методы класса QWidget: showMinimized() — сворачивает окно на панель задач. Эквивалентно нажатию кнопки Свернуть в заголовке окна; showMaximized() — разворачивает окно до максимального размера. Эквивалентно нажа- тию кнопки Развернуть в заголовке окна; showFullScreen() — включает полноэкранный режим отображения окна. Окно отобра- жается без заголовка и границ; showNormal() — отменяет сворачивание, максимальный размер и полноэкранный режим, возвращая окно к изначальным размерам; activateWindow() — делает окно активным (т. е. имеющим фокус ввода). В Windows, если окно было ранее свернуто в значок на панель задач, оно не будет развернуто в из- начальный вид; setWindowState(<Флаги>) — изменяет состояние окна в зависимости от переданных фла- гов. В качестве параметра указывается комбинация следующих атрибутов из класса QtCore.Qt через побитовые операторы: 380 Часть II. Библиотека PyQt 5 • WindowNoState — нормальное состояние окна; • WindowMinimized — окно свернуто; • WindowMaximized — окно максимально развернуто; • WindowFullScreen — полноэкранный режим; • WindowActive — окно имеет фокус ввода, т. е. является активным. Например, включить полноэкранный режим можно так: window.setWindowState((window.windowState() & (QtCore.Qt.WindowMinimized | QtCore.Qt.WindowMaximized)) | QtCore.Qt.WindowFullScreen) Проверить текущий статус окна позволяют следующие методы: isMinimized() — возвращает True, если окно свернуто, и False — в противном случае; isMaximized() — возвращает True, если окно раскрыто до максимальных размеров, и False — в противном случае; isFullScreen() — возвращает True, если включен полноэкранный режим, и False — в противном случае; isActiveWindow() — возвращает True, если окно имеет фокус ввода, и False — в про- тивном случае; windowState() — возвращает комбинацию флагов, обозначающих текущий статус окна. Пример проверки использования полноэкранного режима: if window.windowState() & QtCore.Qt.WindowFullScreen: print("Полноэкранный режим") Пример разворачивания и сворачивания окна приведен в листинге 18.4. Листинг 18.4. Разворачивание и сворачивание окна # -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.btnMin = QtWidgets.QPushButton("Свернуть") self.btnMax = QtWidgets.QPushButton("Развернуть") self.btnFull = QtWidgets.QPushButton("Полный экран") self.btnNormal = QtWidgets.QPushButton("Нормальный размер") vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.btnMin) vbox.addWidget(self.btnMax) vbox.addWidget(self.btnFull) vbox.addWidget(self.btnNormal) self.setLayout(vbox) self.btnMin.clicked.connect(self.on_min) self.btnMax.clicked.connect(self.on_max) self.btnFull.clicked.connect(self.on_full) self.btnNormal.clicked.connect(self.on_normal) Глава 18. Управление окном приложения 381 def on_min(self): self.showMinimized() def on_max(self): self.showMaximized() def on_full(self): self.showFullScreen() def on_normal(self): self.showNormal() if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.setWindowTitle("Разворачивание и сворачивание окна") window.resize(300, 100) window.show() sys.exit(app.exec_()) 18.7. Управление прозрачностью окна Сделать окно полупрозрачным позволяет метод setWindowOpacity() класса QWidget. Формат метода: setWindowOpacity(<Вещественное число от 0.0 до 1.0>) Число 0.0 соответствует полностью прозрачному окну, а число 1.0 — отсутствию прозрач- ности. Для получения степени прозрачности окна из программы предназначен метод windowOpacity(). Выведем окно со степенью прозрачности 0.5 (листинг 18.5). Листинг 18.5. Полупрозрачное окно # -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Полупрозрачное окно") window.resize(300, 100) window.setWindowOpacity(0.5) window.show() print(window.windowOpacity()) # Выведет: 0.4980392156862745 sys.exit(app.exec_()) 18.8. Модальные окна Модальным называется окно, которое не позволяет взаимодействовать с другими окнами в том же приложении, — пока модальное окно не будет закрыто, сделать активным другое окно нельзя. Например, если в программе Microsoft Word выбрать пункт меню Файл | Со- хранить как, откроется модальное диалоговое окно, позволяющее выбрать путь и название 382 Часть II. Библиотека PyQt 5 файла, и, пока это окно не будет закрыто, вы не сможете взаимодействовать с главным ок- ном приложения. Указать, что окно является модальным, позволяет метод setWindowModality(<Флаг>) класса QWidget. В качестве параметра могут быть указаны следующие атрибуты из класса QtCore.Qt: NonModal — 0 — окно не является модальным (поведение по умолчанию); WindowModal — 1 — окно блокирует только родительские окна в пределах иерархии; ApplicationModal — 2 — окно блокирует все окна в приложении. Окна, открытые из модального окна, не блокируются. Следует также учитывать, что метод setWindowModality() должен быть вызван до отображения окна. Получить текущее значение модальности позволяет метод windowModality(). Проверить, является ли окно модальным, можно с помощью метода isModal() — он возвращает True, если окно является модальным, и False — в противном случае. Создадим два независимых окна. В первом окне разместим кнопку, по нажатию которой откроется модальное окно, — оно будет блокировать только первое окно, но не второе. При открытии модального окна отобразим его примерно по центру родительского окна (лис- тинг 18.6). Листинг 18.6. Модальные окна # -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import sys def show_modal_window(): global modalWindow modalWindow = QtWidgets.QWidget(window1, QtCore.Qt.Window) modalWindow.setWindowTitle("Модальное окно") modalWindow.resize(200, 50) modalWindow.setWindowModality(QtCore.Qt.WindowModal) modalWindow.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) modalWindow.move(window1.geometry().center() - modalWindow.rect().center() – QtCore.QPoint(4, 30)) modalWindow.show() app = QtWidgets.QApplication(sys.argv) window1 = QtWidgets.QWidget() window1.setWindowTitle("Обычное окно") window1.resize(300, 100) button = QtWidgets.QPushButton("Открыть модальное окно") button.clicked.connect(show_modal_window) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(button) window1.setLayout(vbox) window1.show() window2 = QtWidgets.QWidget() window2.setWindowTitle("Это окно не будет блокировано при WindowModal") Глава 18. Управление окном приложения 383 window2.resize(500, 100) window2.show() sys.exit(app.exec_()) Если запустить приложение и нажать кнопку Открыть модальное окно, откроется окно, выровненное примерно по центру родительского окна (произвести точное выравнивание вы сможете самостоятельно). При этом получить доступ к родительскому окну можно только после закрытия модального окна, второе же окно блокировано не будет. Если заменить атрибут WindowModal атрибутом ApplicationModal, оба окна будут блокированы. Обратите внимание, что в конструктор модального окна мы передали ссылку на первое ок- но и атрибут Window. Если не указать ссылку, то окно блокировано не будет, а если не ука- зать атрибут, окно вообще не откроется. Кроме того, мы объявили переменную modalWindow глобальной, иначе при достижении конца функции переменная выйдет из области видимо- сти, и окно будет автоматически удалено. Чтобы объект окна автоматически удалялся при закрытии окна, атрибуту WA_DeleteOnClose в методе setAttribute() было присвоено значе- ние TrueМодальные окна в большинстве случаев являются диалоговыми. Для работы с такими окнами в PyQt предназначен класс QDialog, который автоматически выравнивает окно по центру экрана или родительского окна. Кроме того, этот класс предоставляет множество специальных методов, позволяющих дождаться закрытия окна, определить статус заверше- ния и выполнить другие действия. Подробно класс QDialog мы рассмотрим в главе 26. 18.9. Смена значка в заголовке окна По умолчанию в левом верхнем углу окна отображается стандартный значок. Отобразить другой значок позволяет метод setWindowIcon() класса QWidget. В качестве параметра метод принимает экземпляр класса QIcon из модуля QtGui (см. разд. 24.3.4). Чтобы загрузить значок из файла, следует передать путь к файлу конструктору класса QIcon. Если указан относительный путь, поиск файла будет производиться относительно текущего рабочего каталога. Получить список поддерживаемых форматов файлов можно с помощью статического метода supportedImageFormats() класса QImageReader, объявлен- ного в модуле QtGui. Метод возвращает список с экземплярами класса QByteArray. Получим список поддерживаемых форматов: >>> from PyQt5 import QtGui >>> for i in QtGui.QImageReader.supportedImageFormats(): print(str(i, "ascii").upper(), end=" ") Вот результат выполнения этого примера на компьютере одного из авторов: BMP CUR GIF ICNS ICO JPEG JPG PBM PGM PNG PPM SVG SVGZ TGA TIF TIFF WBMP WEBP XBM XPM Если для окна не указать значок, будет использоваться значок приложения, установленный с помощью метода setWindowIcon() класса QApplication. В качестве параметра метод также принимает экземпляр класса QIconВместо загрузки значка из файла можно воспользоваться одним из встроенных значков. Загрузить стандартный значок позволяет следующий код: ico = window.style().standardIcon(QtWidgets.QStyle.SP_MessageBoxCritical) window.setWindowIcon(ico) 384 Часть II. Библиотека PyQt 5 Посмотреть список всех встроенных значков можно в документации к классу QStyle(см. https://doc.qt.io/qt-5/qstyle.html#StandardPixmap-enum). В качестве примера создадим значок размером 16 на 16 пикселов в формате PNG и сохра- ним его в одном каталоге с программой, после чего установим этот значок для окна и всего приложения (листинг 18.7). Листинг 18.7. Смена значка в заголовке окна # -*- coding: utf-8 -*- from PyQt5 import QtGui, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Смена значка в заголовке окна") window.resize(300, 100) ico = QtGui.QIcon("icon.png") window.setWindowIcon(ico) # Значок для окна app.setWindowIcon(ico) # Значок приложения window.show() sys.exit(app.exec_()) 18.10. Изменение цвета фона окна Чтобы изменить цвет фона окна (или компонента), следует установить палитру с настроен- ной ролью Window (или Background). Цветовая палитра содержит цвета для каждой роли и состояния компонента. Указать состояние компонента позволяют следующие атрибуты из класса QPalette (модуль QtGui): Active и Normal — 0 — компонент активен (окно находится в фокусе ввода); Disabled — 1 — компонент недоступен; Inactive — 2 — компонент неактивен (окно находится вне фокуса ввода). Получить текущую палитру компонента позволяет его метод palette(). Чтобы изменить цвет для какой-либо роли и состояния, следует воспользоваться методом setColor() класса QPalette. Формат метода: setColor([<Состояние>, ]<Роль>, <Цвет>) В параметре <Роль> указывается, для какого элемента изменяется цвет. Например, атрибут Window (или Background) изменяет цвет фона, а WindowText (или Foreground) — цвет текста. Полный список атрибутов имеется в документации по классу QPalette (см. https://doc.qt.io/ qt-5/qpalette.html). В параметре <Цвет> указывается цвет элемента. В качестве значения можно указать атрибут из класса QtCore.Qt (например, black, white и т. д.) или экземпляр класса QColor (например, QColor("red"), QColor("#ff0000"), QColor(255, 0, 0) и др.). После настройки палитры необходимо вызвать метод setPalette() компонента и передать этому методу измененный объект палитры. Следует помнить, что компоненты-потомки по умолчанию имеют прозрачный фон и не перерисовываются автоматически. Чтобы вклю- Глава 18. Управление окном приложения 385 чить перерисовку, необходимо передать значение True методу setAutoFillBackground()окна. Изменить цвет фона также можно с помощью CSS-атрибута background-color. Для этого следует передать таблицу стилей в метод setStyleSheet() компонента. Таблицы стилей могут быть внешними (подключение через командную строку), установленными на уровне приложения (с помощью метода setStyleSheet() класса QApplication) или установленными на уровне компонента (с помощью метода setStyleSheet() класса QWidget). Атрибуты, установленные последними, обычно перекрывают значения аналогичных атрибутов, ука- занных ранее. Если вы занимались веб-программированием, то язык CSS (каскадные табли- цы стилей) вам уже знаком, а если нет, придется дополнительно его изучить. Создадим окно с надписью. Для активного окна установим зеленый цвет, а для неактивно- го — красный. Цвет фона надписи сделаем белым. Для изменения фона окна используем палитру, а для изменения цвета фона надписи — CSS-атрибут background-color (лис- тинг 18.8). Листинг 18.8. Изменение цвета фона окна # -*- coding: utf-8 -*- from PyQt5 import QtCore, QtGui, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Изменение цвета фона окна") window.resize(300, 100) pal = window.palette() pal.setColor(QtGui.QPalette.Normal, QtGui.QPalette.Window, QtGui.QColor("#008800")) pal.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Window, QtGui.QColor("#ff0000")) window.setPalette(pal) label = QtWidgets.QLabel("Текст надписи") label.setAlignment(QtCore.Qt.AlignHCenter) label.setStyleSheet("background-color: #ffffff;") label.setAutoFillBackground(True) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(label) window.setLayout(vbox) window.show() sys.exit(app.exec_()) 18.11. Вывод изображения в качестве фона В качестве фона окна (или компонента) можно использовать изображение. Для этого необ- ходимо получить текущую палитру компонента с помощью метода palette(), а затем вы- звать метод setBrush() класса QPalette. Формат метода: setBrush([<Состояние>, ]<Роль>, ) 386 Часть II. Библиотека PyQt 5 Первые два параметра аналогичны соответствующим параметрам в методе setColor(), ко- торый мы рассматривали в предыдущем разделе. В третьем параметре указывается кисть — экземпляр класса QBrush из модуля QtGui. Форматы конструктора класса: <Объект> = QBrush(<Стиль кисти>) <Объект> = QBrush(<Цвет>[, <Стиль кисти>=SolidPattern]) <Объект> = QBrush(<Цвет>, ) <Объект> = QBrush() <Объект> = QBrush() <Объект> = QBrush() <Объект> = QBrush() В параметре <Стиль кисти> указываются атрибуты из класса QtCore.Qt, задающие стиль кисти, — например: NoBrush, SolidPattern, Dense1Pattern, Dense2Pattern, Dense3Pattern, Dense4Pattern, Dense5Pattern, Dense6Pattern, Dense7Pattern, CrossPattern и др. С по- мощью этого параметра можно сделать цвет сплошным (SolidPattern) или имеющим тек- стуру (например, атрибут CrossPattern задает текстуру в виде сетки). В параметре <Цвет> указывается цвет кисти. В качестве значения можно указать атрибут из класса QtCore.Qt (например, black, white и т. д.) или экземпляр класса QColor (например, QColor("red"), QColor("#ff0000"), QColor(255, 0, 0) и др.). При этом установка сплошного цвета фона окна может выглядеть так: pal = window.palette() pal.setBrush(QtGui.QPalette.Normal, QtGui.QPalette.Window, QtGui.QBrush(QtGui.QColor("#008800"), QtCore.Qt.SolidPattern)) window.setPalette(pal) Параметры и позволяют передать объекты изображений. Конструкторы этих классов принимают путь к файлу — абсолютный или относительный. Параметр позволяет создать кисть на основе другой кисти, а параметр — на основе градиента, представленного объектом класса QGradient (см. главу 24). После настройки палитры необходимо вызвать метод setPalette() и передать ему изме- ненный объект палитры. Следует помнить, что компоненты-потомки по умолчанию имеют прозрачный фон и не перерисовываются автоматически. Чтобы включить перерисовку, не- обходимо передать значение True в метод setAutoFillBackground()Указать, какое изображение используется в качестве фона, также можно с помощью CSS- атрибутов background и background-image. С помощью CSS-атрибута background-repeat можно дополнительно указать режим повтора фонового рисунка. Он может принимать зна- чения repeat (повтор по горизонтали и вертикали), repeat-x (повтор только по горизонта- ли), repeat-y (повтор только по вертикали) и no-repeat (не повторяется). Создадим окно с надписью. Для активного окна установим одно изображение (с помощью изменения палитры), а для надписи — другое (с помощью CSS-атрибута background-image) (листинг 18.9). Листинг 18.9. Использование изображения в качестве фона # -*- coding: utf-8 -*- from PyQt5 import QtCore, QtGui, QtWidgets import sys Глава 18. Управление окном приложения 387 app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowTitle("Изображение в качестве фона") window.resize(300, 100) pal = window.palette() pal.setBrush(QtGui.QPalette.Normal, QtGui.QPalette.Window, QtGui.QBrush(QtGui.QPixmap("background1.jpg"))) window.setPalette(pal) label = QtWidgets.QLabel("Текст надписи") label.setAlignment(QtCore.Qt.AlignCenter) label.setStyleSheet("background-image: url(background2.jpg);") label.setAutoFillBackground(True) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(label) window.setLayout(vbox) window.show() sys.exit(app.exec_()) 18.12. Создание окна произвольной формы Чтобы создать окно произвольной формы, нужно выполнить следующие шаги: 1. Создать изображение нужной формы с прозрачным фоном и сохранить его, например, в формате PNG. 2. Создать экземпляр класса QPixmap, передав конструктору класса абсолютный или отно- сительный путь к изображению. 3. Установить изображение в качестве фона окна с помощью палитры. 4. Отделить альфа-канал с помощью метода mask() класса QPixmap5. Передать получившуюся маску в метод setMask() окна. 6. Убрать рамку окна, например, передав комбинацию следующих флагов: QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint Если для создания окна используется класс QLabel, то вместо установки палитры можно передать экземпляр класса QPixmap в метод setPixmap(), а маску — в метод setMask()Для примера создадим круглое окно с кнопкой, с помощью которой можно закрыть окно. Для использования в качестве маски создадим изображение в формате PNG, установим для него прозрачный фон и нарисуем белый круг, занимающий все это изображение. Окно выведем без заголовка и границ (листинг 18.10). Листинг 18.10. Создание окна произвольной формы # -*- coding: utf-8 -*- from PyQt5 import QtCore, QtGui, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget() window.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint) 388 Часть II. Библиотека PyQt 5 window.setWindowTitle("Создание окна произвольной формы") window.resize(300, 300) pixmap = QtGui.QPixmap("mask.png") pal = window.palette() pal.setBrush(QtGui.QPalette.Normal, QtGui.QPalette.Window, QtGui.QBrush(pixmap)) pal.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, QtGui.QBrush(pixmap)) window.setPalette(pal) window.setMask(pixmap.mask()) button = QtWidgets.QPushButton("Закрыть окно", window) button.setFixedSize(150, 30) button.move(75, 135) button.clicked.connect(QtWidgets.qApp.quit) window.show() sys.exit(app.exec_()) Получившееся окно можно увидеть на рис. 18.1. Рис. 18.1. Окно круглой формы 18.13. Всплывающие подсказки При работе с программой у пользователя могут возникать вопросы о предназначении того или иного компонента. Обычно для информирования пользователя служат надписи, распо- ложенные над компонентом или левее его. Но часто либо место в окне ограничено, либо вывод этих надписей портит весь дизайн окна. В таких случаях принято выводить текст подсказки в отдельном окне без рамки при наведении указателя мыши на компонент. Под- сказка автоматически скроется после увода курсора мыши или спустя определенное время. В PyQt нет необходимости создавать окно с подсказкой самому и следить за перемещения- ми указателя мыши — весь этот процесс автоматизирован и максимально упрощен. Чтобы создать всплывающие подсказки для окна или любого другого компонента и управлять ими, нужно воспользоваться следующими методами класса QWidget: Глава 18. Управление окном приложения 389 setToolTip(<Текст>) — задает текст всплывающей подсказки. В качестве параметра можно указать простой текст или HTML-код. Чтобы отключить вывод подсказки, доста- точно передать в этот метод пустую строку; toolTip() — возвращает текст всплывающей подсказки; setToolTipDuration(<Время>) — задает время, в течение которого всплывающая под- сказка будет присутствовать на экране. Значение должно быть указано в миллисекундах. Если задать значение -1, PyQt будет сама вычислять необходимое время, основываясь на длине текста подсказки (это поведение по умолчанию); toolTipDuration() — возвращает время, в течение которого всплывающая подсказка будет присутствовать на экране; setWhatsThis(<Текст>) — задает текст справки. Обычно этот метод используется для вывода информации большего объема, чем во всплывающей подсказке. У диалоговых окон в заголовке окна есть кнопка Справка, по нажатию которой курсор принимает вид стрелки со знаком вопроса, — чтобы в таком случае отобразить текст справки, следует нажать эту кнопку и щелкнуть на компоненте. Можно также сделать компонент актив- ным и нажать комбинацию клавиш +. В качестве параметра можно указать простой текст или HTML-код. Чтобы отключить вывод подсказки, достаточно передать в этот метод пустую строку; whatsThis() — возвращает текст справки. Создадим окно с кнопкой и зададим для них текст всплывающих подсказок и текст справки (листинг 18.11). Листинг 18.11. Всплывающие подсказки # -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget(flags=QtCore.Qt.Dialog) window.setWindowTitle("Всплывающие подсказки") window.resize(300, 70) button = QtWidgets.QPushButton("Закрыть окно", window) button.setFixedSize(150, 30) button.move(75, 20) button.setToolTip("Это всплывающая подсказка для кнопки") button.setToolTipDuration(3000) window.setToolTip("Это всплывающая подсказка для окна") button.setToolTipDuration(5000) button.setWhatsThis("Это справка для кнопки") window.setWhatsThis("Это справка для окна") button.clicked.connect(QtWidgets.qApp.quit) window.show() sys.exit(app.exec_()) 390 Часть II. Библиотека PyQt 5 18.14. Программное закрытие окна В предыдущих разделах для закрытия окна мы использовали слот quit() и метод exit([returnCode=0]) объекта приложения. Однако эти методы не только закрывают теку- щее окно, но и завершают выполнение всего приложения. Чтобы закрыть только текущее окно, следует воспользоваться методом close() класса QWidget. Метод возвращает значение True, если окно успешно закрыто, и False — в противном случае. Закрыть сразу все окна приложения позволяет слот closeAllWindows() класса QApplicationЕсли для окна атрибут WA_DeleteOnClose из класса QtCore.Qt установлен в значение True, после закрытия окна объект окна будет автоматически удален, в противном случае окно просто скрывается. Значение атрибута можно изменить с помощью метода setAttribute(): window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) После вызова метода close() или нажатия кнопки Закрыть в заголовке окна генерируется событие QEvent.Close. Если внутри класса определить метод с предопределенным названи- ем closeEvent(), это событие можно перехватить и обработать. В качестве параметра метод принимает объект класса QCloseEvent, который поддерживает методы accept() (позволяет закрыть окно) и ignore() (запрещает закрытие окна). Вызывая эти методы, можно контро- лировать процесс закрытия окна. В качестве примера закроем окно по нажатию кнопки (листинг 18.12). Листинг 18.12. Программное закрытие окна # -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import sys app = QtWidgets.QApplication(sys.argv) window = QtWidgets.QWidget(flags=QtCore.Qt.Dialog) window.setWindowTitle("Закрытие окна из программы") window.resize(300, 70) button = QtWidgets.QPushButton("Закрыть окно", window) button.setFixedSize(150, 30) button.move(75, 20) button.clicked.connect(window.close) window.show() sys.exit(app.exec_()) ПРИМЕЧАНИЕЗакрыв последнее окно приложения, мы тем самым автоматически завершим и само при- ложение. Не забываем об этом. 18.15. Использование таблиц стилей CSS для оформления окон Вернемся к методу setStyleSheet(), упомянутому в разд. 18.10 и предназначенному для задания таблиц стилей у приложений и отдельных элементов управления. С помощью таб- лиц стилей можно задавать не только цвет фона и фоновое изображение, но и другие пара- метры оформления. Глава 18. Управление окном приложения 391 Метод setStyleSheet() поддерживается классами QWidget (и всеми его подклассами) и QApplication. Следовательно, его можно вызвать у: самого приложения — тогда заданные в таблице стилей параметры оформления будут применены ко всем элементам управления всех окон приложения; отдельного окна — тогда эти параметры будут действовать в пределах данного окна; отдельного элемента управления — тогда они будут действовать только на этот элемент управления. При указании таблицы стилей у приложения и окна можно использовать привычный нам по CSS формат объявления стилей: <Селектор> {<Определение стилей>} <Селектор> записывается в следующем формате: <Основной селектор>[<Дополнительный селектор>][<Псевдокласс>][<Псевдоселектор>] Параметр <Основной селектор> указывает на класс элемента управления. Его можно указать в одном из следующих форматов: * (звездочка) — указывает на все элементы управления (универсальный селектор). На- пример, так можно задать для всех элементов управления зеленый цвет текста: * {color: green;} <Класс> — указывает на элементы управления, относящиеся к заданному <Классу> и его подклассам. Задание красного цвета текста для всех элементов управления, относящихся к классу QAbstractButton и его подклассам, т. е. для командных кнопок, флажков и пе- реключателей, осуществляется так: QAbstractButton {color: red;} .<Класс> — указывает только на элементы управления, относящиеся к заданному <Классу>, но не к его подклассам. Указание полужирного шрифта для всех элементов управления, относящихся к классу QPushButton (командных кнопок), но не для его под- классов, осуществляется так: .QPushButton {font-weight: bold;} Параметр <Дополнительный селектор> задает дополнительные параметры элемента управле- ния. Его форматы: [<Свойство>="<Значение>"] — указанное <Свойство> элемента управления должно иметь заданное <Значение>. Так мы задаем полужирный шрифт для кнопки, чье свойство default имеет значение true, т. е. для кнопки по умолчанию: QPushButton[default="true"] {font-weight: bold;} #<Имя> — указывает на элемент управления, для которого было задано <Имя><Имя>можно задать вызовом у элемента управления метода setObjectName(<Имя>), а полу- чить — вызовом метода objectName(). Так выполняется указание красного цвета текста для кнопки с именем btnRed: QPushButton#btnRed {color: red;} Параметр <Псевдокласс> указывает на отдельную составную часть сложного элемента управления. Он записывается в формате ::<Обозначение составной части>. Вот пример ука- зания графического изображения для кнопки разворачивания раскрывающегося списка (обозначение этой составной части — down-arrow): QComboBox::down-arrow {image: url(arrow.png);} 392 Часть II. Библиотека PyQt 5 Параметр <Псевдоселектор> указывает на состояние элемента управления (должна ли быть кнопка нажата, должен ли флажок быть установленным и т. п.). Он может быть записан в двух форматах: :<Обозначение состояния> — элемент управления должен находиться в указанном со- стоянии. Вот пример указания белого цвета фона для кнопки, когда она нажата (это состояние имеет обозначение pressed): QPushButton:pressed {background-color: white;} :!<Обозначение состояния> — элемент управления должен находиться в любом состоя- нии, кроме указанного. Вот пример указания желтого цвета фона для кнопки, когда она не нажата: QPushButton:!pressed {background-color: yellow;} Можно указать сразу несколько псевдоселекторов, расположив их непосредственно друг за другом — тогда селектор будет указывать на элемент управления, находящийся одно- временно во всех состояниях, которые обозначены этими селекторами. Вот пример ука- зания черного цвета фона и белого цвета текста для кнопки, которая нажата и над кото- рой находится курсор мыши (обозначение — hover): QPushButton:pressed:hover {color: white; background-color: black;} Если нужно указать стиль для элемента управления, вложенного в другой элемент управле- ния, применяется следующий формат указания селектора: <Селектор "внешнего" элемента><Разделитель><Селектор вложенного элемента> Поддерживаются два варианта параметра <Разделитель>: пробел — <Вложенный элемент> не обязательно должен быть вложен непосредственно во <"Внешний">. Так мы указываем зеленый цвет фона для всех надписей (QLabel), вложен- ных в группу (QGroupBox) и вложенные в нее элементы: QGroupBox QLabel {background-color: green;} > — <Вложенный элемент> обязательно должен быть вложен непосредственно во <"Внеш- ний">. Так мы укажем синий цвет текста для всех надписей, непосредственно вложенных в группу: QGroupBox>QLabel {color: blue;} В стиле можно указать сразу несколько селекторов, записав их через запятую — тогда стиль будет применен к элементам управления, на которые указывают эти селекторы. Вот пример задания зеленого цвета фона для кнопок и надписей: QLabel, QPushButton {background-color: green;} В CSS элементы страницы наследуют параметры оформления от их родителей. Но в PyQt это не так. Скажем, если мы укажем для группы красный цвет текста: app.setStyleSheet("QGroupBox {color: red;}") вложенные в эту группу элементы не унаследуют его и будут иметь цвет текста, заданный по умолчанию. Нам придется задать для них нужный цвет явно: app.setStyleSheet("QGroupBox, QGroupBox * {color: red;}") Начиная с версии PyQt 5.7, поддерживается возможность указать библиотеке, что все элементы-потомки должны наследовать параметры оформления у родителя. Для этого дос- таточно вызвать у класса QCoreApplication статический метод setAttribute, передав ему Глава 18. Управление окном приложения 393 в качестве первого параметра значение атрибута AA_UseStyleSheetPropagationInWidgetStyles класса QtCore.Qt, а в качестве второго параметра — значение True: QtCore.QCoreApplication.setAttribute( QtCore.Qt.AA_UseStyleSheetPropagationInWidgetStyles, True) Чтобы отключить такую возможность, достаточно вызвать этот метод еще раз, указав в нем вторым параметром FalseИ, наконец, при вызове метода setStyleSheet() у элемента управления, для которого следу- ет задать таблицу стилей, в последней не указываются ни селектор, ни фигурные скобки — они просто не нужны. Отметим, что в случае PyQt, как и в CSS, также действуют правила каскадности. Так, таб- лица стилей, заданная для окна, имеет больший приоритет, нежели таковая, указанная для приложения, а стиль, что был задан для элемента управления, имеет наивысший приоритет. Помимо этого, более специфические стили имеют больший приоритет, чем менее специфи- ческие; так, стиль с селектором, в чей состав входит имя элемента управления, перекроет стиль с селектором любого другого типа. За более подробным описанием поддерживаемых PyQt псевдоклассов, псевдоселекторов и особенностей указания стилей для отдельных классов элементов управления обращайтесь по интернет-адресу https://doc.qt.io/qt-5/stylesheet-reference.html. Листинг 18.13 показывает пример задания таблиц стилей для элементов управления разны- ми способами. Результат выполнения приведенного в нем кода можно увидеть на рис. 18.2. Листинг 18.13. Использование таблиц стилей для указания оформления # -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys app = QtWidgets.QApplication(sys.argv) # На уровне приложения задаем синий цвет текста для надписей, вложенных в группы, # и курсивное начертание текста кнопок app.setStyleSheet("QGroupBox QLabel {color: blue;} QPushButton {font-style: italic}") window = QtWidgets.QWidget() window.setWindowTitle("Таблицы стилей") # На уровне окна задаем зеленый цвет текста для надписи с именем first и # красный цвет текста для надписи, на которую наведен курсор мыши window.setStyleSheet("QLabel#first {color: green;} QLabel:hover {color: red;}") window.resize(200, 150) # Создаем три надписи lbl1 = QtWidgets.QLabel("Зеленый текст") # Указываем для первой надписи имя first lbl1.setObjectName("first") lbl2 = QtWidgets.QLabel("Полужирный текст") # Для второй надписи указываем полужирный шрифт lbl2.setStyleSheet("font-weight: bold") lbl3 = QtWidgets.QLabel("Синий текст") # Создаем кнопку btn = QtWidgets.QPushButton("Курсивный текст") 394 Часть II. Библиотека PyQt 5 # Создаем группу box = QtWidgets.QGroupBox("Группа") # Создаем контейнер, помещаем в него третью надпись и вставляем в группу bbox = QtWidgets.QVBoxLayout() bbox.addWidget(lbl3) box.setLayout(bbox) # Создаем еще один контейнер, помещаем в него две первые надписи, группу и кнопку и # вставляем в окно mainbox = QtWidgets.QVBoxLayout() mainbox.addWidget(lbl1) mainbox.addWidget(lbl2) mainbox.addWidget(box) mainbox.addWidget(btn) window.setLayout(mainbox) # Выводим окно и запускаем приложение window.show() sys.exit(app.exec_()) Рис. 18.2. Пример использования таблиц стилей ГЛ А В А 19 Обработка сигналов и событий При взаимодействии пользователя с окном возникают события — своего рода извещения о том, что пользователь выполнил какое-либо действие или в самой системе возникло неко- торое условие. В ответ на события система генерирует определенные сигналы, которые можно рассматривать как представления системных событий внутри библиотеки PyQt. Сигналы являются важнейшей составляющей приложения с графическим интерфейсом, поэтому необходимо знать, как назначить обработчик сигнала, как удалить его, а также уметь правильно обработать событие. Сигналы, которые генерирует тот или иной компо- нент, мы будем рассматривать при изучении конкретного компонента. 19.1. Назначение обработчиков сигналов Чтобы обработать какой-либо сигнал, необходимо сопоставить ему функцию или метод класса, который будет вызван при возникновении события и станет его обработчиком. Каждый сигнал, поддерживаемый классом, соответствует одноименному атрибуту этого класса (отметим, что это именно атрибуты класса, а не атрибуты экземпляра). Так, сигнал clicked, генерируемый при щелчке мышью, соответствует атрибуту clicked. Каждый такой атрибут хранит экземпляр особого класса, представляющего соответствующий сигнал. Чтобы назначить сигналу обработчик, следует использовать метод connect(), унаследован- ный от класса QObject. Форматы вызова этого метода таковы: <Компонент>.<Сигнал>.connect(<Обработчик>[, <Тип соединения>]) <Компонент>.<Сигнал>[<Тип>].connect(<Обработчик>[, <Тип соединения>]) Здесь мы назначаем <Обработчик> для параметра <Сигнал>, генерируемого параметром <Компонент>. В качестве обработчика можно указать: ссылку на пользовательскую функцию; ссылку на метод класса; ссылку на экземпляр класса, в котором определен метод __call__(); анонимную функцию; ссылку на слот класса. Вот пример назначения функции on_clicked_button() в качестве обработчика сигнала clicked кнопки button: button.clicked.connect(on_clicked_button) 396 Часть II. Библиотека PyQt 5 Сигналы могут принимать произвольное число параметров, каждый из которых может от- носиться к любому типу данных. При этом бывает и так, что в классе существуют два сиг- нала с одинаковыми наименованиями, но разными наборами параметров. Тогда следует дополнительно в квадратных скобках указать <Тип> данных, принимаемых сигналом, — либо просто написав его наименование, либо задав его в виде строки. Например, оба сле- дующих выражения назначают обработчик сигнала, принимающего один параметр логиче- ского типа: button.clicked[bool].connect(on_clicked_button) button.clicked["bool"].connect(on_clicked_button) Одному и тому же сигналу можно назначить произвольное количество обработчиков. Это иллюстрирует код из листинга 19.1, где сигналу clicked кнопки назначены сразу четыре обработчика. Листинг 19.1. Назначение сигналу нескольких обработчиков # -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys def on_clicked(): print("Кнопка нажата. Функция on_clicked()") class MyClass(): def __init__(self, x=0): self.x = x def __call__(self): print("Кнопка нажата. Метод MyClass.__call__()") print("x =", self.x) def on_clicked(self): print("Кнопка нажата. Метод MyClass.on_clicked()") obj = MyClass() app = QtWidgets.QApplication(sys.argv) button = QtWidgets.QPushButton("Нажми меня") # Назначаем обработчиком функцию button.clicked.connect(on_clicked) # Назначаем обработчиком метод класса button.clicked.connect(obj.on_clicked) # Назначаем обработчиком ссылку на экземпляр класса button.clicked.connect(MyClass(10)) # Назначаем обработчиком анонимную функцию button.clicked.connect(lambda: MyClass(5)()) button.show() sys.exit(app.exec_()) В четвертом обработчике мы использовали анонимную функцию. В ней мы сначала создаем объект класса MyClass, передав ему в качестве параметра число 5, после чего сразу же вы- зываем его как функцию, указав после конструктора пустые скобки: button.clicked.connect(lambda: MyClass(5)()) Глава 19. Обработка сигналов и событий 397 Результат выполнения в окне консоли при щелчке на кнопке: Кнопка нажата. Функция on_clicked() Кнопка нажата. Метод MyClass.on_clicked() Кнопка нажата. Метод MyClass.__call__() x = 10 Кнопка нажата. Метод MyClass.__call__() x = 5 Классы PyQt 5 поддерживают ряд методов, специально предназначенных для использова- ния в качестве обработчиков сигналов. Такие методы называются слотами. Например, класс QApplication поддерживает слот quit(), завершающий текущее приложение. В лис- тинге 19.2 показан код, использующий этот слот. Листинг 19.2. Использование слота # -*- coding: utf-8 -*- from PyQt5 import QtWidgets import sys app = QtWidgets.QApplication(sys.argv) button = QtWidgets.QPushButton("Завершить работу") button.clicked.connect(app.quit) button.show() sys.exit(app.exec_()) Любой пользовательский метод можно сделать слотом, для чего необходимо перед его оп- ределением вставить декоратор @pyqtSlot(). Формат декоратора: @QtCore.pyqtSlot(*<Типы данных>, name=None, result=None) В параметре <Типы данных> через запятую указываются типы данных параметров, прини- маемых слотом, — например: bool или int. При задании типа данных C++ его название не- обходимо указать в виде строки. Если метод не принимает параметров, параметр <Типы дан- ных> не указывается. В именованном параметре name можно передать название слота в виде строки — это название станет использоваться вместо названия метода, а если параметр name не задан, название слота будет совпадать с названием метода. Именованный параметр result предназначен для указания типа данных, возвращаемых методом, — если параметр не задан, то метод ничего не возвращает. Чтобы создать перегруженную версию слота, декоратор указывается последовательно несколько раз с разными типами данных. Пример использования декоратора @pyqtSlot() приведен в листинге 19.3. Листинг 19.3. Использование декоратора @pyqtSlot() # -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets import sys class MyClass(QtCore.QObject): def __init__(self): QtCore.QObject.__init__(self) 398 Часть II. Библиотека PyQt 5 @QtCore.pyqtSlot() def on_clicked(self): print("Кнопка нажата. Слот on_clicked()") @QtCore.pyqtSlot(bool, name="myslot") def on_clicked2(self, status): print("Кнопка нажата. Слот myslot(bool)", status) obj = MyClass() app = QtWidgets.QApplication(sys.argv) button = QtWidgets.QPushButton("Нажми меня") button.clicked.connect(obj.on_clicked) button.clicked.connect(obj.myslot) button.show() sys.exit(app.exec_()) PyQt не требует обязательного превращения в слот метода, который будет использоваться как обработчик сигнала. Однако это рекомендуется сделать, т. к. вызов слота в этом случае выполняется быстрее, чем вызов обычного метода. Необязательный параметр <Тип соединения> метода connect() определяет тип соединения между сигналом и обработчиком. На этот параметр следует обратить особое внимание при использовании в приложении нескольких потоков, т. к. изменять GUI-поток из другого потока нельзя. В параметре можно указать один из следующих атрибутов класса QtCore.Qt: AutoConnection — 0 — значение по умолчанию. Если источник сигнала и обработчик находятся в одном потоке, то оно эквивалентно значению DirectConnection, а если в разных потоках, то — QueuedConnection; DirectConnection — 1 — обработчик вызывается сразу после генерации сигнала и вы- полняется в потоке его источника; QueuedConnection — 2 — сигнал помещается в очередь обработки событий, а его обра- ботчик выполняется в потоке приемника сигнала; BlockingQueuedConnection — 4 — аналогично значению QueuedConnection за тем исклю- чением, что поток блокируется на время обработки сигнала. Обратите внимание, что ис- точник и обработчик сигнала обязательно должны быть расположены в разных потоках; UniqueConnection — 0x80 — указывает, что обработчик можно назначить только один раз. Этот атрибут с помощью оператора | может быть объединен с любым из представ- ленных ранее флагов: # Эти два обработчика будут успешно назначены и выполнены button.clicked.connect(on_clicked) button.clicked.connect(on_clicked) # А эти два обработчика назначены не будут button.clicked.connect(on_clicked, Qt.Core.Qt.AutoConnection | QtCore.Qt.UniqueConnection) button.clicked.connect(on_clicked, Qt.Core.Qt.AutoConnection | QtCore.Qt.UniqueConnection) # Тем не менее, эти два обработчика будут назначены, поскольку они разные button.clicked.connect(on_clicked, Qt.Core.Qt.AutoConnection | QtCore.Qt.UniqueConnection) button.clicked.connect(obj.on_clicked, Qt.Core.Qt.AutoConnection | QtCore.Qt.UniqueConnection) Глава 19. Обработка сигналов и событий 399 19.2. Блокировка и удаление обработчика Для блокировки и удаления обработчиков предназначены следующие методы класса QObject: blockSignals(<Флаг>) — временно блокирует прием сигналов, если параметр имеет зна- чение True, и снимает блокировку, если параметр имеет значение False. Метод возвра- щает логическое представление предыдущего состояния соединения; signalsBlocked() — возвращает значение True, если блокировка сигналов установлена, и False — в противном случае; disconnect() — удаляет обработчик. Форматы метода: <Компонент>.<Сигнал>.disconnect([<Обработчик>]) <Компонент>.<Сигнал>[<Тип>].disconnect([<Обработчик>]) Если параметр <Обработчик> не указан, удаляются все обработчики, назначенные ранее, в противном случае удаляется только указанный обработчик. Параметр <Тип> указывает- ся лишь в том случае, если существуют сигналы с одинаковыми именами, но прини- мающие разные параметры: button.clicked.disconnect() button.clicked[bool].disconnect(on_clicked_button) button.clicked["bool"].disconnect(on_clicked_button) Создадим окно с четырьмя кнопками (листинг 19.4). Для кнопки Нажми меня назначим обработчик сигнала clicked. Чтобы информировать о нажатии кнопки, выведем сообщение в окно консоли. Для кнопок Блокировать, Разблокировать и Удалить обработчик созда- дим обработчики, которые будут изменять статус обработчика для кнопки Нажми меня. Листинг 19.4. Блокировка и удаление обработчика # -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyWindow(QtWidgets.QWidget): def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.setWindowTitle("Блокировка и удаление обработчика") self.resize(300, 150) self.button1 = QtWidgets.QPushButton("Нажми меня") self.button2 = QtWidgets.QPushButton("Блокировать") self.button3 = QtWidgets.QPushButton("Разблокировать") self.button4 = QtWidgets.QPushButton("Удалить обработчик") self.button3.setEnabled(False) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.button1) vbox.addWidget(self.button2) vbox.addWidget(self.button3) vbox.addWidget(self.button4) self.setLayout(vbox) self.button1.clicked.connect(self.on_clicked_button1) 400 Часть II. Библиотека PyQt 5 self.button2.clicked.connect(self.on_clicked_button2) self.button3.clicked.connect(self.on_clicked_button3) self.button4.clicked.connect(self.on_clicked_button4) @QtCore.pyqtSlot() def on_clicked_button1(self): print("Нажата кнопка button1") @QtCore.pyqtSlot() def on_clicked_button2(self): self.button1.blockSignals(True) self.button2.setEnabled(False) self.button3.setEnabled(True) @QtCore.pyqtSlot() def on_clicked_button3(self): self.button1.blockSignals(False) self.button2.setEnabled(True) self.button3.setEnabled(False) @QtCore.pyqtSlot() def on_clicked_button4(self): self.button1.clicked.disconnect(self.on_clicked_button1) self.button2.setEnabled(False) self.button3.setEnabled(False) self.button4.setEnabled(False) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_()) Если нажать кнопку Нажми меня, в окно консоли будет выведена строка Нажата кнопка button1. Нажатие кнопки Блокировать производит блокировку обработчика — теперь при нажатии кнопки Нажми меня никаких сообщений в окно консоли не выводится. Отменить блокировку можно с помощью кнопки Разблокировать. Нажатие кнопки Удалить обра- ботчик производит полное удаление обработчика — в этом случае, чтобы обрабатывать нажатие кнопки Нажми меня, необходимо заново назначить обработчик. Также можно отключить генерацию сигнала, сделав компонент недоступным с помощью следующих методов из класса QWidget: setEnabled(<Флаг>) — если в параметре указано значение False, компонент станет не- доступным. Чтобы сделать компонент опять доступным, следует передать значение True; setDisabled(<Флаг>) — если в параметре указано значение True, компонент станет не- доступным. Чтобы сделать компонент опять доступным, следует передать значение FalseПроверить, доступен компонент или нет, позволяет метод isEnabled(). Он возвращает зна- чение True, если компонент доступен, и False — в противном случае. Глава 19. Обработка сигналов и событий 401 19.3. Генерация сигналов В некоторых случаях необходимо сгенерировать сигнал программно. Например, при запол- нении последнего текстового поля и нажатии клавиши можно имитировать нажатие кнопки ОK и тем самым выполнить подтверждение ввода пользователя. Осуществить гене- рацию сигнала из программы позволяет метод emit() класса QObject. Форматы этого ме- тода: <Компонент>.<Сигнал>.emit([<Данные>]) <Компонент>.<Сигнал>[<Тип>].emit([<Данные>]) Метод emit() всегда вызывается у объекта, которому посылается сигнал: button.clicked.emit() Сигналу и, соответственно, его обработчику можно передать данные, указав их в вызове метода emit(): button.clicked[bool].emit(False) button.clicked["bool"].emit(False) Также мы можем создавать свои собственные сигналы. Для этого следует определить в классе атрибут, чье имя совпадет с наименованием сигнала. Отметим, что это должен быть атрибут класса, а не экземпляра. Далее мы присвоим вновь созданному атрибуту результат, возвращенный функцией pyqtSignal() из модуля QtCore. Формат функции: <Объект сигнала> = pyqtSignal(*<Типы данных>[, name=<Имя сигнала>]) В параметре <Типы данных> через запятую указываются названия типов данных, передавае- мых сигналу, — например: bool или int: mysignal1 = QtCore.pyqtSignal(int) mysignal2 = QtCore.pyqtSignal(int, str) При использовании типа данных C++ его название необходимо указать в виде строки: mysignal3 = QtCore.pyqtSignal("QDate") Если сигнал не принимает параметров, параметр <Типы данных> не указывается. Сигнал может иметь несколько перегруженных версий, различающихся количеством и типом принимаемых параметров. В этом случае типы параметров указываются внутри квадратных скобок. Вот пример сигнала, передающего данные типа int или str: mysignal4 = QtCore.pyqtSignal([int], [str]) По умолчанию название создаваемого сигнала будет совпадать с названием атрибута клас- са. Однако мы можем указать для сигнала другое название, после чего он будет доступен под двумя названиями: совпадающим с именем атрибута класса и заданным нами. Для ука- зания названия сигнала применяется параметр name: mysignal = QtCore.pyqtSignal(int, name="mySignal") В качестве примера создадим окно с двумя кнопками (листинг 19.5), которым назначим обработчики сигнала clicked (нажатие кнопки). Внутри обработчика щелчка на первой кнопке сгенерируем два сигнала: первый будет имитировать нажатие второй кнопки, а вто- рой станет пользовательским, привязанным к окну. Внутри обработчиков выведем сообще- ния в окно консоли. 402 Часть II. Библиотека PyQt 5 Листинг 19.5. Генерация сигнала из программы # -*- coding: utf-8 -*- from PyQt5 import QtCore, QtWidgets class MyWindow(QtWidgets.QWidget): mysignal = QtCore.pyqtSignal(int, int) def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.setWindowTitle("Генерация сигнала из программы") self.resize(300, 100) self.button1 = QtWidgets.QPushButton("Нажми меня") self.button2 = QtWidgets.QPushButton("Кнопка 2") vbox = QtWidgets.QVBoxLayout() vbox.addWidget(self.button1) vbox.addWidget(self.button2) self.setLayout(vbox) self.button1.clicked.connect(self.on_clicked_button1) self.button2.clicked.connect(self.on_clicked_button2) self.mysignal.connect(self.on_mysignal) def on_clicked_button1(self): print("Нажата кнопка button1") # Генерируем сигналы self.button2.clicked[bool].emit(False) self.mysignal.emit(10, 20) def on_clicked_button2(self): print("Нажата кнопка button2") def on_mysignal(self, x, y): print("Обработан пользовательский сигнал mysignal()") print("x =", x, "y =", y) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = MyWindow() window.show() sys.exit(app.exec_()) Результат выполнения после нажатия первой кнопки: Нажата кнопка button1 Нажата кнопка button2 Обработан пользовательский сигнал mysignal() x = 10 y = 20 Вместо конкретного типа принимаемого сигналом параметра можно указать тип QVariant из модуля QtCore. В этом случае при генерации сигнала допускается передавать ему данные любого типа. Вот пример создания и генерирования сигнала, принимающего параметр любого типа: mysignal = QtCore.pyqtSignal(QtCore.QVariant) self.mysignal.emit(20) Глава 19. Обработка сигналов и событий 403 self.mysignal.emit("Привет!") self.mysignal.emit([1, "2"]) Сгенерировать сигнал можно не только с помощью метода emit(). Некоторые компоненты предоставляют методы, которые посылают сигнал. Например, у кнопок существует метод click(). Используя этот метод, инструкцию: button.clicked.emit() можно записать следующим образом: button.click() Более подробно такие методы мы будем рассматривать при изучении конкретных компо- нентов. 19.4. Передача данных в обработчик При назначении обработчика в метод connect() передается ссылка на функцию или метод. Если после названия функции (метода) указать внутри круглых скобок какой-либо пара- метр, то это приведет к вызову функции (метода) и вместо ссылки будет передан результат ее выполнения, что вызовет ошибку. Передать данные в обработчик можно следующими способами: создать анонимную функцию и внутри ее выполнить вызов обработчика с параметрами. Вот пример передачи обработчику числа 10: self.button1.clicked.connect(lambda : self.on_clicked_button1(10)) Если передаваемое обработчику значение вычисляется в процессе выполнения кода, переменную, хранящую это значение, следует указывать в анонимной функции как зна- чение по умолчанию, иначе функции будет передана ссылка на это значение, а не оно само: y = 10 self.button1.clicked.connect(lambda x=y: self.on_clicked_button1(x)) передать ссылку на экземпляр класса, внутри которого определен метод __call__()Передаваемое значение указывается в качестве параметра конструктора этого класса: class MyClass(): def __init__(self, x=0): self.x = x def __call__(self): print("x =", self.x) self.button1.clicked.connect(MyClass(10)) передать ссылку на обработчик и данные в функцию partial() из модуля functoolsФормат функции: partial(<Функция>[, *<Неименованные параметры>][, **<Именованные параметры>]) Пример передачи параметра в обработчик: from functools import partial self.button1.clicked.connect(partial(self.on_clicked_button1, 10)) 404 Часть II. Библиотека PyQt 5 Если при генерации сигнала передается предопределенное значение, то оно будет дос- тупно в обработчике после остальных параметров. Назначим обработчик сигнала clicked, принимающего логический параметр, и дополнительно передадим число: self.button1.clicked.connect(partial(self.on_clicked_button1, 10)) Обработчик будет иметь следующий вид: def on_clicked_button1(self, x, status): print("Нажата кнопка button1", x, status) Результат выполнения: Нажата кнопка button1 10 False 19.5. Использование таймеров Таймеры позволяют через заданный интервал времени выполнять метод с предопределен- ным названием timerEvent(). Для назначения таймера используется метод startTimer()класса QObject. Формат метода: 1   ...   33   34   35   36   37   38   39   40   ...   83

Заголовок


30
Часть I. Основы языка Python
Листинг 1.5. Выделение инструкций внутри блока i = 1 while i < 11: print(i) i += 1 print("Конец программы")
Обратите внимание, что перед всеми инструкциями внутри блока расположено одинаковое количество пробелов. Именно так в языке Python выделяются блоки. Инструкции, перед которыми расположено одинаковое количество пробелов, являются телом блока. В нашем примере две инструкции выполняются десять раз. Концом блока является инструкция, пе- ред которой расположено меньшее количество пробелов. В нашем случае это функция print()
, которая выводит строку "Конец программы"
. Если количество пробелов внутри бло- ка окажется разным, то интерпретатор выведет сообщение о фатальной ошибке, и програм- ма будет остановлена. Так язык Python приучает программистов писать красивый и понят- ный код.
П
РИМЕЧАНИЕ
В языке Python принято использовать четыре пробела для выделения инструкций внутри блока.
Если блок состоит из одной инструкции, то допустимо разместить ее на одной строке с ос- новной инструкцией. Например, код: for i in range(1, 11): print(i) print("Конец программы") можно записать так: for i in range(1, 11): print(i) print("Конец программы")
Если инструкция является слишком длинной, то ее можно перенести на следующую строку, например, так:
в конце строки поставить символ
\
, после которого должен следовать перевод строки.
Другие символы (в том числе и комментарии) недопустимы. Пример: x = 15 + 20 \
+ 30 print(x)
поместить выражение внутри круглых скобок. Этот способ лучше, т. к. внутри круглых скобок можно разместить любое выражение. Пример: x = (15 + 20 # Это комментарий
+ 30) print(x)
определение списка и словаря можно разместить на нескольких строках, т. к. при этом используются квадратные и фигурные скобки соответственно. Пример определения списка: arr = [15, 20, # Это комментарий
30] print(arr)

Глава 1. Первые шаги
31
Пример определения словаря: arr = {"x": 15, "y": 20, # Это комментарий "z": 30} print(arr)
1.4. Комментарии
Комментарии предназначены для вставки пояснений в текст программы, интерпретатор полностью их игнорирует. Внутри комментария может располагаться любой текст, включая инструкции, которые выполнять не следует.
С
ОВЕТ
Помните — комментарии нужны программисту, а не интерпретатору Python. Вставка ком- ментариев в код позволит через некоторое время быстро вспомнить предназначение фрагмента кода.
В языке Python присутствует только однострочный комментарий. Он начинается с символа
#
:
# Это комментарий
Однострочный комментарий может начинаться не только с начала строки, но и распола- гаться после инструкции. Например, в следующем примере комментарий расположен после инструкции, предписывающей вывести надпись "Привет, мир!"
: print("Привет, мир!") # Выводим надпись с помощью функции print()
Если же символ комментария разместить перед инструкцией, то она не будет выполнена:
# print("Привет, мир!") Эта инструкция выполнена не будет
Если символ
#
расположен внутри кавычек или апострофов, то он не является символом комментария: print("# Это НЕ комментарий")
Так как в языке Python нет многострочного комментария, то комментируемый фрагмент часто размещают внутри утроенных кавычек (или утроенных апострофов):
"""
Эта инструкция выполнена не будет print("Привет, мир!")
"""
Следует заметить, что этот фрагмент кода не игнорируется интерпретатором, т. к. он не яв- ляется комментарием. В результате выполнения фрагмента будет создан объект строкового типа. Тем не менее, инструкции внутри утроенных кавычек выполнены не будут, поскольку интерпретатор сочтет их простым текстом. Такие строки являются строками документиро- вания, а не комментариями.
1.5. Дополнительные возможности IDLE
Поскольку в процессе изучения материала этой книги в качестве редактора мы будем ис- пользовать IDLE, рассмотрим дополнительные возможности этой среды разработки.
Как вы уже знаете, в окне Python Shell символы
>>>
означают приглашение ввести команду.
Введя команду, нажимаем клавишу — на следующей строке сразу отобразится ре-


32
Часть I. Основы языка Python зультат (при условии, что инструкция возвращает значение), а далее — приглашение для ввода новой команды. При вводе многострочной команды после нажатия клавиши редактор автоматически вставит отступ и будет ожидать дальнейшего ввода. Чтобы сооб- щить редактору о конце ввода команды, необходимо дважды нажать клавишу
1   2   3   4   5   6   7   8   9   ...   83

:
>>> for n in range(1, 3): print(n)
1 2
>>>
В предыдущем разделе мы выводили строку "Привет, мир!"
с помощью функции print()
В окне Python Shell это делать не обязательно — мы можем просто ввести строку и нажать клавишу для получения результата:
>>> "Привет, мир!"
'Привет, мир!'
>>>
Обратите внимание на то, что строки выводятся в апострофах. Этого не произойдет, если выводить строку с помощью функции print()
:
>>> print("Привет, мир!")
Привет, мир!
>>>
Учитывая возможность получить результат сразу после ввода команды, окно Python Shell можно использовать для изучения команд, а также в качестве многофункционального каль- кулятора:
>>> 12 * 32 + 54 438
>>>
Результат вычисления последней инструкции сохраняется в переменной
_
(одно подчерки- вание). Это позволяет производить дальнейшие расчеты без ввода предыдущего результата.
Вместо него достаточно ввести символ подчеркивания:
>>> 125 * 3 # Умножение
375
>>> _ + 50 # Сложение. Эквивалентно 375 + 50 425
>>> _ / 5 # Деление. Эквивалентно 425 / 5 85.0
>>>
При вводе команды можно воспользоваться комбинацией клавиш +<Пробел>. В ре- зультате будет отображен список, из которого можно выбрать нужный идентификатор.
Если при открытом списке вводить буквы, то показываться будут идентификаторы, начи- нающиеся с этих букв. Выбирать идентификатор необходимо с помощью клавиш <↑> и
<↓>. После выбора не следует нажимать клавишу , иначе это приведет к выполне- нию инструкции, — просто вводите инструкцию дальше, а список закроется. Такой же спи- сок будет автоматически появляться (с некоторой задержкой) при обращении к атрибутам объекта или модуля после ввода точки. Для автоматического завершения идентификатора

Глава 1. Первые шаги
33 после ввода первых букв можно воспользоваться комбинацией клавиш +>. При каждом последующем нажатии этой комбинации будет вставляться следующий идентифи- катор. Эти две комбинации клавиш очень удобны, если вы забыли, как пишется слово, или хотите, чтобы редактор закончил его за вас.
При необходимости повторно выполнить ранее введенную инструкцию ее приходится на- бирать заново. Можно, конечно, скопировать инструкцию, а затем вставить, но как вы мо- жете сами убедиться, в контекстном меню нет пунктов Copy (Копировать) и Paste (Вста- вить). Они расположены в меню Edit. Постоянно выбирать пункты из этого меню очень неудобно. Одним из решений проблемы является использование комбинации клавиш быст- рого доступа + (Копировать) и + (Вставить). Комбинации стандартны для Windows, и вы наверняка их уже использовали ранее. Но опять-таки, прежде чем ско- пировать инструкцию, ее предварительно необходимо выделить. Редактор IDLE избавляет нас от лишних действий и предоставляет комбинацию клавиш + для вставки пер- вой введенной инструкции, а также комбинацию +
для вставки последней инст- рукции. Каждое последующее нажатие этих клавиш будет вставлять следующую (или пре- дыдущую) инструкцию. Для еще более быстрого повторного ввода инструкции следует предварительно ввести ее первые буквы. В этом случае перебираться будут только инст- рукции, начинающиеся с этих букв.
1.6. Вывод результатов работы программы
Вывести результаты работы программы можно с помощью функции print()
. Функция име- ет следующий формат: print([<Объекты>][, sep=' '][, end='\n'][, file=sys.stdout][, flush=False])
Функция print()
преобразует объект в строку и посылает ее в стандартный вывод stdout
С помощью параметра file можно перенаправить вывод в другое место — например, в файл. При этом, если параметр flush имеет значение
False
, выводимые значения будут принудительно записаны в файл. Перенаправление вывода мы подробно рассмотрим при изучении файлов.
После вывода строки автоматически добавляется символ перевода строки: print("Строка 1") print("Строка 2")
Результат:
Строка 1
Строка 2
Если необходимо вывести результат на той же строке, то в функции print()
данные указы- ваются через запятую в первом параметре: print("Строка 1", "Строка 2")
Результат:
Строка 1 Строка 2
Как видно из примера, между выводимыми строками автоматически вставляется пробел.
С помощью параметра sep можно указать другой символ. Например, выведем строки без пробела между ними: print("Строка1", "Строка2", sep="")

34
Часть I. Основы языка Python
Результат:
Строка 1Строка 2
После вывода объектов в конце добавляется символ перевода строки. Если необходимо произвести дальнейший вывод на той же строке, то в параметре end следует указать другой символ: print("Строка 1", "Строка 2", end=" ") print("Строка 3")
# Выведет: Строка 1 Строка 2 Строка 3
Если, наоборот, необходимо вставить символ перевода строки, то функция print()
указы- вается без параметров: for n in range(1, 5): print(n, end=" ") print() print("Это текст на новой строке")
Результат выполнения:
1 2 3 4
Это текст на новой строке
Здесь мы использовали цикл for
, который позволяет последовательно перебирать элементы.
На каждой итерации цикла переменной n
присваивается новое число, которое мы выводим с помощью функции print()
, расположенной на следующей строке.
Обратите внимание, что перед функцией мы добавили четыре пробела. Как уже отмечалось ранее, таким образом в языке Python выделяются блоки. При этом инструкции, перед кото- рыми расположено одинаковое количество пробелов, представляют собой тело цикла. Все эти инструкции выполняются определенное количество раз. Концом блока является инст- рукция, перед которой расположено меньшее количество пробелов. В нашем случае это функция print()
без параметров, которая вставляет символ перевода строки.
Если необходимо вывести большой блок текста, его следует разместить между утроенными кавычками или утроенными апострофами. В этом случае текст сохраняет свое форматиро- вание: print("""Строка 1
Строка 2
Строка 3""")
В результате выполнения этого примера мы получим три строки:
Строка 1
Строка 2
Строка 3
Для вывода результатов работы программы вместо функции print()
можно использовать метод write()
объекта sys.stdout
: import sys # Подключаем модуль sys sys.stdout.write("Строка") # Выводим строку
В первой строке с помощью оператора import мы подключаем модуль sys
, в котором объ- явлен объект stdout
. Далее с помощью метода write()
этого объекта выводим строку. Сле-

Глава 1. Первые шаги
35 дует заметить, что метод не вставляет символ перевода строки, поэтому при необходимости следует добавить его самим с помощью символа
\n
: import sys sys.stdout.write("Строка 1\n") sys.stdout.write("Строка 2")
1.7. Ввод данных
Для ввода данных в Python 3 предназначена функция input()
, которая получает данные со стандартного ввода stdin
. Функция имеет следующий формат:
[<Значение> = ] input([<Сообщение>])
Для примера переделаем нашу первую программу так, чтобы она здоровалась не со всем миром, а только с нами (листинг 1.6).
Листинг 1.6. Пример использования функции input()
# -*- coding: utf-8 -*- name = input("Введите ваше имя: ") print("Привет,", name) input("Нажмите для закрытия окна")
Чтобы окно сразу не закрылось, в конце программы указан еще один вызов функции input()
. В этом случае окно не закроется, пока не будет нажата клавиша .
Вводим код и сохраняем файл, например, под именем test2.py
, а затем запускаем программу на выполнение с помощью двойного щелчка на значке файла. Откроется черное окно, в ко- тором мы увидим надпись: Введите ваше имя:. Вводим свое имя, например
Николай
, и на- жимаем клавишу . В результате будет выведено приветствие: Привет, Николай.
При использовании функции input()
следует учитывать, что при достижении конца файла или при нажатии комбинации клавиш +, а затем клавиши генерируется исключение
EOFError
. Если не предусмотреть обработку исключения, то программа аварий- но завершится. Обработать исключение можно следующим образом: try: s = input("Введите данные: ") print(s) except EOFError: print("Обработали исключение EOFError")
Если внутри блока try возникнет исключение
EOFError
, то управление будет передано в блок except
. После исполнения инструкций в блоке except программа нормально продол- жит работу.
В Python 2 для ввода данных применялись две функции: raw_input()
и input()
. Функция raw_input()
просто возвращала введенные данные, а функция input()
предварительно об- рабатывала данные с помощью функции eval()
и затем возвращала результат ее выполне- ния. В Python 3 функция raw_input()
была переименована в input()
, а прежняя функция input()
— удалена. Чтобы в Python 3 вернуться к поведению функции input()
в стиле
Python 2, необходимо передать значение в функцию eval()
явным образом:

36
Часть I. Основы языка Python
# -*- coding: utf-8 -*- result = eval(input("Введите инструкцию: ")) # Вводим: 2 + 2 print("Результат:", result) # Выведет: 4 input()
В
НИМАНИЕ
!
Функция eval() выполнит любую введенную инструкцию. Никогда не используйте этот код, если не доверяете пользователю.
Передать данные можно в командной строке, указав их после имени файла программы. Та- кие данные доступны через список argv модуля sys
. Первый элемент списка argv будет со- держать название файла запущенной программы, а последующие элементы — переданные данные. Для примера создадим файл test3.py в папке
C:\book
. Содержимое файла приведено в листинге 1.7.
Листинг 1.7. Получение данных из командной строки
# -*- coding: utf-8 -*- import sys arr = sys.argv[:] for n in arr: print(n)
Теперь запустим программу на выполнение из командной строки и передадим ей данные.
Для этого вызовем командную строку: выберем в меню Пуск пункт Выполнить, в открыв- шемся окне наберем команду cmd и нажмем кнопку OK — откроется черное окно команд- ной строки с приглашением для ввода команд. Перейдем в папку
C:\book
, набрав команду: cd C:\book
В командной строке должно появиться приглашение:
C:\book>
Для запуска нашей программы вводим команду:
C:\Python36\python.exe test3.py -uNik -p123
В этой команде мы передаем имя файла (
test3.py
) и некоторые данные (
-uNik и
-p123
).
Результат выполнения программы будет выглядеть так: test3.py
-uNik
-p123 1.8. Доступ к документации
При установке Python на компьютер помимо собственно интерпретатора копируется доку- ментация по этому языку в формате CHM. Чтобы открыть ее, в меню Пуск | Программы
(Все программы) | Python 3.6
нужно выбрать пункт Python 3.6 Manuals (32-bit) или
Python 3.6 Manuals (64-bit)
Если в меню Пуск | Программы (Все программы) | Python 3.6 выбрать пункт Python 3.6
Module Docs (32-bit)
или Python 3.6 Module Docs (64-bit), запустится сервер документов

Глава 1. Первые шаги
37 pydoc (рис. 1.11). Он представляет собой написанную на самом Python программу веб- сервера, выводящую результаты своей работы в веб-браузере.
Сразу после запуска pydoc откроется веб-браузер, в котором будет выведен список всех стандартных модулей, поставляющихся в составе Python. Щелкнув на названии модуля, представляющем собой гиперссылку, мы откроем страницу с описанием всех классов, функций и констант, объявленных в этом модуле.
Рис. 1.11. Окно pydoc
Чтобы завершить работу pydoc, следует переключиться в его окно (см. рис. 1.11), ввести в нем команду q
(от quit, выйти) и нажать клавишу — окно при этом автоматически закроется. А введенная там команда b
(от browser, браузер) повторно выведет в браузере страницу со списком модулей.
В окне Python Shell также можно отобразить документацию. Для этого предназначена функция help()
. В качестве примера отобразим документацию по встроенной функции input()
:
>>> help(input)
Результат выполнения:
Help on built-in function input in module builtins: input(prompt=None, /)
Read a string from standard input. The trailing newline is stripped.
The prompt string, if given, is printed to standard output without a trailing newline before reading input.
If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.
On *nix systems, readline is used if available.
С помощью функции help()
можно получить документацию не только по конкретной функции, но и по всему модулю сразу. Для этого предварительно необходимо подключить модуль. Например, подключим модуль builtins
, содержащий определения всех встроенных функций и классов, а затем выведем документацию по этому модулю:

38
Часть I. Основы языка Python
>>> import builtins
>>> help(builtins)
При рассмотрении комментариев мы говорили, что часто для комментирования большого фрагмента кода используются утроенные кавычки или утроенные апострофы. Такие строки не являются комментариями в полном смысле этого слова. Вместо комментирования фраг- мента создается объект строкового типа, который сохраняется в атрибуте
__doc__
. Функция help()
при составлении документации получает информацию из этого атрибута. Такие строки называются строками документирования.
В качестве примера создадим файл test4.py
, содержимое которого показано в листинге 1.8.
Листинг 1.8. Тестовый модуль test4.py
# -*- coding: utf-8 -*-
""" Это описание нашего модуля """ def func():
""" Это описание функции""" pass
Теперь подключим этот модуль и выведем содержимое строк документирования. Все эти действия выполняет код из листинга 1.9.
Листинг 1.9. Вывод строк документирования посредством функции help()
# -*- coding: utf-8 -*- import test4 # Подключаем файл test4.py help(test4) input()
Запустим эту программу из среды Python Shell. (Если запустить ее щелчком мыши, вывод будет выполнен в окне интерактивной оболочки, и результат окажется нечитаемым. Веро- ятно, это происходит вследствие ошибки в интерпретаторе.) Вот что мы увидим:
Help on module test4:
NAME test4 - Это описание нашего модуля
FUNCTIONS func()
Это описание функции
FILE d:\data\документы\работа\книги\python 3 и pyqt 5 разработка приложений ii\примеры\1\test4.py
Теперь получим содержимое строк документирования с помощью атрибута
__doc__
. Как это делается, показывает код из листинга 1.10.

Глава 1. Первые шаги
39
Листинг 1.10. Вывод строк документирования посредством атрибута __doc__
# -*- coding: utf-8 -*- import test4 # Подключаем файл test4.py print(test4.__doc__) print(test4.func.__doc__) input()
Результат выполнения:
Это описание нашего модуля
Это описание функции
Атрибут
__doc__
можно использовать вместо функции help()
. В качестве примера получим документацию по функции input()
:
>>> print(input.__doc__)
Результат выполнения:
Read a string from standard input. The trailing newline is stripped.
The prompt string, if given, is printed to standard output without a trailing newline before reading input.
If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.
On *nix systems, readline is used if available.
Получить список всех идентификаторов внутри модуля позволяет функция dir()
. Пример ее использования показывает код из листинга 1.11.
Листинг 1.11. Получение списка идентификаторов
# -*- coding: utf-8 -*- import test4 # Подключаем файл test4.py print(dir(test4)) input()
Результат выполнения:
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__',
'__package__', '__spec__', 'func']
Теперь получим список всех встроенных идентификаторов:
>>> import builtins
>>> print(dir(builtins))
Функция dir()
может не принимать параметров вообще. В этом случае возвращается спи- сок идентификаторов текущего модуля:
>>> print(dir())
Результат выполнения:
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__',
'__package__', '__spec__']

ГЛ А В А
2
Переменные
Все данные в языке Python представлены объектами. Каждый объект имеет тип данных и значение. Для доступа к объекту предназначены переменные. При инициализации в пере- менной сохраняется ссылка на объект (адрес объекта в памяти компьютера). Благодаря этой ссылке можно в дальнейшем изменять объект из программы.
2.1. Именование переменных
Каждая переменная должна иметь уникальное имя, состоящее из латинских букв, цифр и знаков подчеркивания, причем имя переменной не может начинаться с цифры. Кроме того, следует избегать указания символа подчеркивания в начале имени, поскольку идентифика- торам с таким символом определено специальное назначение. Например, имена, начинаю- щиеся с символа подчеркивания, не импортируются из модуля с помощью инструкции from module import *
, а имена, включающие по два символа подчеркивания — в начале и в кон- це, для интерпретатора имеют особый смысл.
В качестве имени переменной нельзя использовать ключевые слова. Получить список всех ключевых слов позволяет такой код:
>>> import keyword
>>> keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', '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']
Помимо ключевых слов, следует избегать совпадений со встроенными идентификаторами.
Дело в том, что, в отличие от ключевых слов, встроенные идентификаторы можно переоп- ределять, но дальнейший результат может стать для вас неожиданным:
>>> help(abs)
Help on built-in function abs in module builtins: abs(x, /)
Return the absolute value of the argument.
>>> help = 10
>>> help
10

Глава 2. Переменные
41
>>> help(abs)
Traceback (most recent call last):
File "
", line 1, in help(abs)
TypeError: 'int' object is not callable
В этом примере мы с помощью встроенной функции help() получаем справку по функции abs()
1   2   3   4   5   6   7   8   9   ...   83

. Далее переменной help присваиваем число 10. После переопределения идентифика- тора мы больше не можем пользоваться функцией help()
, т. к. это приведет к выводу сооб- щения об ошибке. По этой причине лучше избегать имен, совпадающих со встроенными идентификаторами. Очень часто подобная ошибка возникает при попытке назвать перемен- ную, в которой предполагается хранение строки, именем str
. Вроде бы логично, но str является часто используемым встроенным идентификатором и после такого переопределения поведение программы становится непредсказуемым. В редакторе IDLE встроенные иденти- фикаторы подсвечиваются фиолетовым цветом. Обращайте внимание на цвет перемен- ной — он должен быть черным. Если вы заметили, что переменная подсвечена, то название переменной следует обязательно изменить. Получить полный список встроенных иденти- фикаторов позволяет следующий код:
>>> import builtins
>>> print(dir(builtins))
Правильные имена переменных: x
, y1
, strName
, str_name
Неправильные имена переменных:
1y
,
ИмяПеременной
Последнее имя неправильное, т. к. в нем используются русские буквы. Хотя на самом деле такой вариант также будет работать, но лучше русские буквы все же не применять:
>>> ИмяПеременной = 10 # Лучше так не делать!!!
>>> ИмяПеременной
10
При указании имени переменной важно учитывать регистр букв: x
и
X
— разные перемен- ные:
>>> x = 10; X = 20
>>> x, X
(10, 20)
2.2. Типы данных
В Python 3 объекты могут иметь следующие типы данных:
bool
— логический тип данных. Может содержать значения
True или
False
, которые ведут себя как числа
1
и
0
соответственно:
>>> type(True), type(False)
(, )
>>> int(True), int(False)
(1, 0)

NoneType
— объект со значением
None
(обозначает отсутствие значения):
>>> type(None)


42
Часть I. Основы языка Python
В логическом контексте значение
None интерпретируется как
False
:
>>> bool(None)
False
int
— целые числа. Размер числа ограничен лишь объемом оперативной памяти:
>>> type(2147483647), type(999999999999999999999999)
(, )
float
— вещественные числа:
>>> type(5.1), type(8.5e-3)
(, )
complex
— комплексные числа:
>>> type(2+2j)

str
— Unicode-строки:
>>> type("Строка")

bytes
— неизменяемая последовательность байтов:
>>> type(bytes("Строка", "utf-8"))

bytearray
— изменяемая последовательность байтов:
>>> type(bytearray("Строка", "utf-8"))

list
— списки. Тип данных list аналогичен массивам в других языках программирова- ния:
>>> type( [1, 2, 3] )

tuple
— кортежи:
>>> type( (1, 2, 3) )

range
— диапазоны:
>>> type( range(1, 10) )

dict
— словари. Тип данных dict аналогичен ассоциативным массивам в других языках программирования:
>>> type( {"x": 5, "y": 20} )

set
— множества (коллекции уникальных объектов):
>>> type( {"a", "b", "c"} )


Глава 2. Переменные
43
frozenset
— неизменяемые множества:
>>> type(frozenset(["a", "b", "c"]))

ellipsis
— обозначается в виде трех точек или слова
Ellipsis
. Тип ellipsis использу- ется в расширенном синтаксисе получения среза:
>>> type(...), ..., ... is Ellipsis
(, Ellipsis, True)
>>> class C(): def __getitem__(self, obj): return obj
>>> c = C()
>>> c[..., 1:5, 0:9:1, 0]
(Ellipsis, slice(1, 5, None), slice(0, 9, 1), 0)
function
— функции:
>>> def func(): pass
>>> type(func)

module
— модули:
>>> import sys
>>> type(sys)

type
— классы и типы данных. Не удивляйтесь! Все данные в языке Python являются объектами, даже сами типы данных!
>>> class C: pass
>>> type(C)

>>> type(type(""))

Основные типы данных делятся на изменяемые и неизменяемые. К изменяемым типам относятся списки, словари и тип bytearray
. Пример изменения элемента списка:
>>> arr = [1, 2, 3]
>>> arr[0] = 0 # Изменяем первый элемент списка
>>> arr
[0, 2, 3]
К неизменяемым типам относятся числа, строки, кортежи, диапазоны и тип bytes
. Напри- мер, чтобы получить строку из двух других строк, необходимо использовать операцию кон- катенации, а ссылку на новый объект присвоить переменной:
>>> str1 = "авто"
>>> str2 = "транспорт"
>>> str3 = str1 + str2 # Конкатенация
>>> print(str3) автотранспорт

44
Часть I. Основы языка Python
Кроме того, типы данных делятся на последовательности и отображения. К последова- тельностям относятся строки, списки, кортежи, диапазоны, типы bytes и bytearray
, а к ото- бражениям — словари.
Последовательности и отображения поддерживают механизм итераторов, позволяющий произвести обход всех элементов с помощью метода
__next__()
или функции next()
. На- пример, вывести элементы списка можно так:
>>> arr = [1, 2]
>>> i = iter(arr)
>>> i.__next__() # Метод __next__()
1
>>> next(i) # Функция next()
2
Если используется словарь, то на каждой итерации возвращается ключ:
>>> d = {"x": 1, "y": 2}
>>> i = iter(d)
>>> i.__next__() # Возвращается ключ 'y'
>>> d[i.__next__()] # Получаем значение по ключу
1
На практике подобным способом не пользуются. Вместо него применяется цикл for
, кото- рый использует механизм итераторов незаметно для нас. Например, вывести элементы спи- ска можно так:
>>> for i in [1, 2]: print(i)
Перебрать слово по буквам можно точно так же. Для примера вставим тире после каждой буквы:
>>> for i in "Строка": print(i + " -", end=" ")
Результат:
С — т — р — о — к — а -
Пример перебора элементов словаря:
>>> d = {"x": 1, "y": 2}
>>> for key in d: print( d[key] )
Последовательности поддерживают также обращение к элементу по индексу, получение среза, конкатенацию (оператор
+
), повторение (оператор
*
) и проверку на вхождение (опе- ратор in
). Все эти операции мы будем подробно рассматривать по мере изучения языка.
2.3. Присваивание значения переменным
Присваивание — это занесение в переменную какого-либо значения (при этом значение, хранившееся в переменной ранее, теряется). Присваивание выполняется с помощью опера- тора
=
(знак равенства) таким образом:

Глава 2. Переменные
45
>>> x = 7 # Тип int
>>> y = 7.8 # Тип float
>>> s1 = "Строка" # Переменной s1 присвоено значение Строка
>>> s2 = 'Строка' # Переменной s2 также присвоено значение Строка
>>> b = True # Переменной b присвоено логическое значение True
В одной строке можно присвоить значение сразу нескольким переменным:
>>> x = y = 10 # Переменным x и y присвоено число 10
>>> x, y
(10, 10)
После присваивания значения в переменной сохраняется ссылка на объект, а не сам объект.
Это обязательно следует учитывать при групповом присваивании. Групповое присваивание можно использовать для чисел, строк и кортежей, но для изменяемых объектов этого делать нельзя. Пример:
>>> x = y = [1, 2] # Якобы создали два объекта
>>> x, y
([1, 2], [1, 2])
В этом примере мы создали список из двух элементов, присвоили его переменным x
и y
и теперь полагаем, что эти переменные хранят две разные копии упомянутого списка. Теперь попробуем изменить значение одного из элементов списка, что хранится в переменной y
:
>>> y[1] = 100 # Изменяем второй элемент списка
>>> x, y
([1, 100], [1, 100])
Как видно из примера, изменение значения элемента списка из переменной y
привело также к изменению значения того же элемента списка из переменной x
. То есть, обе переменные ссылаются на один и тот же объект, а не на два разных объекта. Чтобы получить два объек- та, необходимо производить раздельное присваивание:
>>> x = [1, 2]
>>> y = [1, 2]
>>> y[1] = 100 # Изменяем второй элемент
>>> x, y
([1, 2], [1, 100])
Проверить, ссылаются ли две переменные на один и тот же объект, позволяет оператор is
Если переменные ссылаются на один и тот же объект, оператор is возвращает значение
True
:
>>> x = y = [1, 2] # Один объект
>>> x is y
True
>>> x = [1, 2] # Разные объекты
>>> y = [1, 2] # Разные объекты
>>> x is y
False
Следует заметить, что в целях повышения эффективности кода интерпретатор производит кэширование малых целых чисел и небольших строк. Это означает, что если ста перемен- ным присвоено число 2, то, скорее всего, в этих переменных будет сохранена ссылка на один и тот же объект. Пример:

46
Часть I. Основы языка Python
>>> x = 2; y = 2; z = 2
>>> x is y, y is z
(True, True)
Посмотреть количество ссылок на объект позволяет метод getrefcount()
из модуля sys
:
>>> import sys # Подключаем модуль sys
>>> sys.getrefcount(2)
304
Когда число ссылок на объект становится равно нулю, объект автоматически удаляется из оперативной памяти. Исключением являются объекты, которые подлежат кэшированию.
Помимо группового, Python поддерживает позиционное присваивание. В этом случае пере- менные записываются через запятую слева от оператора
=
, а значения — через запятую справа. Пример позиционного присваивания:
>>> x, y, z = 1, 2, 3
>>> x, y, z
(1, 2, 3)
С помощью позиционного присваивания можно поменять значения переменных местами.
Пример:
>>> x, y = 1, 2
>>> x, y
(1, 2)
>>> x, y = y, x
>>> x, y
(2, 1)
По обе стороны оператора
=
могут быть указаны последовательности, к каковым относятся строки, списки, кортежи, диапазоны, типы bytes и bytearray
:
>>> x, y, z = "123" # Строка
>>> x, y, z
('1', '2', '3')
>>> x, y, z = [1, 2, 3] # Список
>>> x, y, z
(1, 2, 3)
>>> x, y, z = (1, 2, 3) # Кортеж
>>> x, y, z
(1, 2, 3)
>>> [x, y, z] = (1, 2, 3) # Список слева, кортеж справа
>>> x, y, z
(1, 2, 3)
Обратите внимание на то, что количество элементов справа и слева от оператора
=
должно совпадать, иначе будет выведено сообщение об ошибке:
>>> x, y, z = (1, 2, 3, 4)
Traceback (most recent call last):
File "
", line 1, in x, y, z = (1, 2, 3, 4)
ValueError: too many values to unpack (expected 3)

Глава 2. Переменные
47
Python 3 при несоответствии количества элементов справа и слева от оператора
= позволяет сохранить в переменной список, состоящий из лишних элементов. Для этого перед именем переменной указывается звездочка (
*
):
>>> x, y, *z = (1, 2, 3, 4)
>>> x, y, z
(1, 2, [3, 4])
>>> x, *y, z = (1, 2, 3, 4)
>>> x, y, z
(1, [2, 3], 4)
>>> *x, y, z = (1, 2, 3, 4)
>>> x, y, z
([1, 2], 3, 4)
>>> x, y, *z = (1, 2, 3)
>>> x, y, z
(1, 2, [3])
>>> x, y, *z = (1, 2)
>>> x, y, z
(1, 2, [])
Как видно из примера, переменная, перед которой указана звездочка, всегда получает в ка- честве значения список. Если для этой переменной не хватило значений, то ей присваивает- ся пустой список. Следует помнить, что звездочку можно указать только перед одной пере- менной, в противном случае возникнет неоднозначность и интерпретатор выведет сообще- ние об ошибке:
>>> *x, y, *z = (1, 2, 3, 4)
SyntaxError: two starred expressions in assignment
2.4. Проверка типа данных
Во многих языках программирования при создании переменной нужно указывать тип дан- ных, к которому должны относиться значения, присваиваемые этой переменной. Но в
Python этого делать не нужно, поскольку любая переменная может хранить значения любо- го типа.
Выяснить тип данных, к которому относится хранящееся в переменной значение, позволяет функция type(<Имя переменной>)
:
>>> type(a)

Проверить тип данных у значения, хранящегося в переменной, можно следующими спосо- бами:
сравнить значение, возвращаемое функцией type()
, с названием типа данных:
>>> x = 10
>>> if type(x) == int: print("Это целое число (тип int)")
проверить тип с помощью функции isinstance()
:
>>> s = "Строка"
>>> if isinstance(s, str): print("Это строка (тип str)")

48
Часть I. Основы языка Python
2.5. Преобразование типов данных
Над значением, относящимся к определенному типу, можно производить лишь операции, допустимые для этого типа данных. Например, можно складывать друг с другом числа, но строку сложить с числом нельзя — это приведет к выводу сообщения об ошибке:
>>> 2 + "25"
Traceback (most recent call last):
File "
", line 1, in
2 + "25"
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Для преобразования значения из одного типа данных в другой предназначены следующие функции:
bool([<Объект>])
— преобразует объект в логический тип данных:
>>> bool(0), bool(1), bool(""), bool("Строка"), bool([1, 2]), bool([])
(False, True, False, True, True, False)
int([<Объект>[, <Система счисления>]])
— преобразует объект в число. Во втором па- раметре можно указать систему счисления (значение по умолчанию — 10). Примеры:
>>> int(7.5), int("71")
(7, 71)
>>> int("71", 10), int("71", 8), int("0o71", 8), int("A", 16)
(71, 57, 57, 10)
Если преобразование невозможно, то генерируется исключение:
>>> int("71s")
Traceback (most recent call last):
File "
", line 1, in int("71s")
ValueError: invalid literal for int() with base 10: '71s'
float([<Число или строка>])
— преобразует целое число или строку в вещественное число:
>>> float(7), float("7.1")
(7.0, 7.1)
>>> float("Infinity"), float("-inf")
(inf, -inf)
>>> float("Infinity") + float("-inf") nan
str([<Объект>])
— преобразует объект в строку:
>>> str(125), str([1, 2, 3])
('125', '[1, 2, 3]')
>>> str((1, 2, 3)), str({"x": 5, "y": 10})
('(1, 2, 3)', "{'y': 10, 'x': 5}")
>>> str(bytes("строка", "utf-8"))
"b'\\xd1\\x81\\xd1\\x82\\xd1\\x80\\xd0\\xbe\\xd0\\xba\\xd0
\\xb0'"
>>> str(bytearray("строка", "utf-8"))

Глава 2. Переменные
49
"bytearray(b'\\xd1\\x81\\xd1\\x82\\xd1\\x80\\xd0\\xbe\\xd0
\\xba\\xd0\\xb0')"
str(<Объект>[, <Кодировка>[, <Обработка ошибок>]])
— преобразует объект типа bytes или bytearray в строку. В третьем параметре можно задать значение "strict"
(при ошибке возбуждается исключение
UnicodeDecodeError
— значение по умолчанию),
"replace"
(неизвестный символ заменяется символом, имеющим код
\uFFFD
) или "ignore"
(неизвестные символы игнорируются). Примеры:
>>> obj1 = bytes("строка1", "utf-8")
>>> obj2 = bytearray("строка2", "utf-8")
>>> str(obj1, "utf-8"), str(obj2, "utf-8")
('строка1', 'строка2')
>>> str(obj1, "ascii", "strict")
Traceback (most recent call last):
File "
", line 1, in str(obj1, "ascii", "strict")
UnicodeDecodeError: 'ascii' codec can't decode byte
0xd1 in position 0: ordinal not in range(128)
>>> str(obj1, "ascii", "ignore")
'1'
bytes(<Строка>, <Кодировка>[, <Обработка ошибок>])
— преобразует строку в объект типа bytes
. В третьем параметре могут быть указаны значения "strict"
(значение по умолчанию),
"replace"
или "ignore"
. Примеры:
>>> bytes("строка", "cp1251") b'\xf1\xf2\xf0\xee\xea\xe0'
>>> bytes("строка123", "ascii", "ignore") b'123'
bytes(<Последовательность>)
— преобразует последовательность целых чисел от 0 до
255 в объект типа bytes
. Если число не попадает в диапазон, возбуждается исключение
ValueError
:
>>> b = bytes([225, 226, 224, 174, 170, 160])
>>> b b'\xe1\xe2\xe0\xae\xaa\xa0'
>>> str(b, "cp866")
'строка'
bytearray(<Строка>, <Кодировка>[, <Обработка ошибок>])
— преобразует строку в объ- ект типа bytearray
. В третьем параметре могут быть указаны значения "strict"
(значе- ние по умолчанию),
"replace"
или "ignore"
:
>>> bytearray("строка", "cp1251") bytearray(b'\xf1\xf2\xf0\xee\xea\xe0')
bytearray(<Последовательность>)
— преобразует последовательность целых чисел от 0 до 255 в объект типа bytearray
. Если число не попадает в диапазон, возбуждается ис- ключение
ValueError
:
>>> b = bytearray([225, 226, 224, 174, 170, 160])
>>> b

50
Часть I. Основы языка Python bytearray(b'\xe1\xe2\xe0\xae\xaa\xa0')
>>> str(b, "cp866")
'строка'
list(<Последовательность>)
— преобразует элементы последовательности в список:
>>> list("12345") # Преобразование строки
['1', '2', '3', '4', '5']
>>> list((1, 2, 3, 4, 5)) # Преобразование кортежа
[1, 2, 3, 4, 5]
tuple(<Последовательность>)
— преобразует элементы последовательности в кортеж:
>>> tuple("123456") # Преобразование строки
('1', '2', '3', '4', '5', '6')
>>> tuple([1, 2, 3, 4, 5]) # Преобразование списка
(1, 2, 3, 4, 5)
В качестве примера рассмотрим возможность сложения двух чисел, введенных пользовате- лем. Как вы уже знаете, вводить данные позволяет функция input()
. Воспользуемся этой функцией для получения чисел от пользователя (листинг 2.1).
Листинг 2.1. Получение данных от пользователя
# -*- coding: utf-8 -*- x = input("x = ") # Вводим 5 y = input("y = ") # Вводим 12 print(x + y) input()
Результатом выполнения этого скрипта будет не число, а строка
512
. Таким образом, следу- ет запомнить, что функция input()
возвращает результат в виде строки. Чтобы просуммиро- вать два числа, необходимо преобразовать строку в число (листинг 2.2).
Листинг 2.2. Преобразование строки в число
# -*- coding: utf-8 -*- x = int(input("x = ")) # Вводим 5 y = int(input("y = ")) # Вводим 12 print(x + y) input()
1   2   3   4   5   6   7   8   9   10   ...   83

В этом случае мы получим число 17, как и должно быть. Однако если пользователь вместо числа введет строку, то программа завершится с фатальной ошибкой. Как обработать ошиб- ку, мы разберемся по мере изучения языка.
2.6. Удаление переменных
Удалить переменную можно с помощью инструкции del
: del <Переменная1>[, ..., <ПеременнаяN>]

Глава 2. Переменные
51
Пример удаления одной переменной:
>>> x = 10; x
10
>>> del x; x
Traceback (most recent call last):
File "
", line 1, in del x; x
NameError: name 'x' is not defined
Пример удаления нескольких переменных:
>>> x, y = 10, 20
>>> del x, y

ГЛ А В А
3
Операторы
Операторы позволяют произвести с данными определенные действия. Например, операто- ры присваивания служат для сохранения данных в переменной, математические операторы позволяют выполнить арифметические вычисления, а оператор конкатенации строк служит для соединения двух строк в одну. Рассмотрим операторы, доступные в Python 3, подробно.
3.1. Математические операторы
Производить операции над числами позволяют математические операторы:

+
— сложение:
>>> 10 + 5 # Целые числа
15
>>> 12.4 + 5.2 # Вещественные числа
17.6
>>> 10 + 12.4 # Целые и вещественные числа
22.4

-
— вычитание:
>>> 10 - 5 # Целые числа
5
>>> 12.4 - 5.2 # Вещественные числа
7.2
>>> 12 - 5.2 # Целые и вещественные числа
6.8

*
— умножение:
>>> 10 * 5 # Целые числа
50
>>> 12.4 * 5.2 # Вещественные числа
64.48
>>> 10 * 5.2 # Целые и вещественные числа
52.0

/
— деление. Результатом деления всегда является вещественное число, даже если про- изводится деление целых чисел. Обратите внимание на эту особенность, если вы раньше

Глава 3. Операторы
53 программировали на Python 2. В Python 2 при делении целых чисел остаток отбрасывал- ся и возвращалось целое число, в Python 3 поведение оператора изменилось:
>>> 10 / 5 # Деление целых чисел без остатка
2.0
>>> 10 / 3 # Деление целых чисел с остатком
3.3333333333333335
>>> 10.0 / 5.0 # Деление вещественных чисел
2.0
>>> 10.0 / 3.0 # Деление вещественных чисел
3.3333333333333335
>>> 10 / 5.0 # Деление целого числа на вещественное
2.0
>>> 10.0 / 5 # Деление вещественного числа на целое
2.0

//
— деление с округлением вниз. Вне зависимости от типа чисел остаток отбрасывается:
>>> 10 // 5 # Деление целых чисел без остатка
2
>>> 10 // 3 # Деление целых чисел с остатком
3
>>> 10.0 // 5.0 # Деление вещественных чисел
2.0
>>> 10.0 // 3.0 # Деление вещественных чисел
3.0
>>> 10 // 5.0 # Деление целого числа на вещественное
2.0
>>> 10 // 3.0 # Деление целого числа на вещественное
3.0
>>> 10.0 // 5 # Деление вещественного числа на целое
2.0
>>> 10.0 // 3 # Деление вещественного числа на целое
3.0

%
— остаток от деления:
>>> 10 % 5 # Деление целых чисел без остатка
0
>>> 10 % 3 # Деление целых чисел с остатком
1
>>> 10.0 % 5.0 # Операция над вещественными числами
0.0
>>> 10.0 % 3.0 # Операция над вещественными числами
1.0
>>> 10 % 5.0 # Операция над целыми и вещественными числами
0.0
>>> 10 % 3.0 # Операция над целыми и вещественными числами
1.0
>>> 10.0 % 5 # Операция над целыми и вещественными числами
0.0
>>> 10.0 % 3 # Операция над целыми и вещественными числами
1.0

54
Часть I. Основы языка Python

**
— возведение в степень:
>>> 10 ** 2, 10.0 ** 2
(100, 100.0)
унарный минус (–) и унарный плюс (
+
):
>>> +10, +10.0, -10, -10.0, -(-10), -(-10.0)
(10, 10.0, -10, -10.0, 10, 10.0)
Как видно из примеров, операции над числами разных типов возвращают число, имеющее более сложный тип из типов, участвующих в операции. Целые числа имеют самый простой тип, далее идут вещественные числа и самый сложный тип — комплексные числа. Таким образом, если в операции участвуют целое число и вещественное, то целое число будет автоматически преобразовано в вещественное число, затем будет произведена операция над вещественными числами, а результатом станет вещественное число.
При выполнении операций над вещественными числами следует учитывать ограничения точности вычислений. Например, результат следующей операции может показаться стран- ным:
>>> 0.3 - 0.1 - 0.1 - 0.1
-2.7755575615628914e-17
Ожидаемым был бы результат
0.0
, но, как видно из примера, мы получили совсем другой результат. Если необходимо производить операции с фиксированной точностью, следует использовать модуль decimal
:
>>> from decimal import Decimal
>>> Decimal("0.3") - Decimal("0.1") - Decimal("0.1") - Decimal("0.1")
Decimal('0.0')
3.2. Двоичные операторы
Двоичные операторы предназначены для манипуляции отдельными битами. Язык Python поддерживает следующие двоичные операторы:


— двоичная инверсия. Значение каждого бита заменяется на противоположное:
>>> x = 100 # 01100100
>>> x = x # 10011011

&
— двоичное И:
>>> x = 100 # 01100100
>>> y = 75 # 01001011
>>> z = x & y # 01000000
>>> "{0:b} & {1:b} = {2:b}".format(x, y, z)
'1100100 & 1001011 = 1000000'

|
— двоичное ИЛИ:
>>> x = 100 # 01100100
>>> y = 75 # 01001011
>>> z = x | y # 01101111
>>> "{0:b} | {1:b} = {2:b}".format(x, y, z)
'1100100 | 1001011 = 1101111'

Глава 3. Операторы
55

^
— двоичное исключающее ИЛИ:
>>> x = 100 # 01100100
>>> y = 250 # 11111010
>>> z = x ^ y # 10011110
>>> "{0:b} ^ {1:b} = {2:b}".format(x, y, z)
'1100100 ^ 11111010 = 10011110'

<<
— сдвиг влево — сдвигает двоичное представление числа влево на один или более разрядов и заполняет разряды справа нулями:
>>> x = 100 # 01100100
>>> y = x << 1 # 11001000
>>> z = y << 1 # 10010000
>>> k = z << 2 # 01000000

>>
— сдвиг вправо — сдвигает двоичное представление числа вправо на один или более разрядов и заполняет разряды слева нулями, если число положительное:
>>> x = 100 # 01100100
>>> y = x >> 1 # 00110010
>>> z = y >> 1 # 00011001
>>> k = z >> 2 # 00000110
Если число отрицательное, то разряды слева заполняются единицами:
>>> x = -127 # 10000001
>>> y = x >> 1 # 11000000
>>> z = y >> 2 # 11110000
>>> k = z << 1 # 11100000
>>> m = k >> 1 # 11110000 3.3. Операторы для работы с последовательностями
Для работы с последовательностями предназначены следующие операторы:

+
— конкатенация:
>>> print("Строка1" + "Строка2") # Конкатенация строк
Строка1Строка2
>>> [1, 2, 3] + [4, 5, 6] # Списки
[1, 2, 3, 4, 5, 6]
>>> (1, 2, 3) + (4, 5, 6) # Кортежи
(1, 2, 3, 4, 5, 6)

*
— повторение:
>>> "s" * 20 # Строки 'ssssssssssssssssssss'
>>> [1, 2] * 3 # Списки
[1, 2, 1, 2, 1, 2]
>>> (1, 2) * 3 # Кортежи
(1, 2, 1, 2, 1, 2)

56
Часть I. Основы языка Python
in
— проверка на вхождение. Если элемент входит в последовательность, то возвраща- ется логическое значение
True
:
>>> "Строка" in "Строка для поиска" # Строки
True
>>> "Строка2" in "Строка для поиска" # Строки
False
>>> 2 in [1, 2, 3], 4 in [1, 2, 3] # Списки
(True, False)
>>> 2 in (1, 2, 3), 6 in (1, 2, 3) # Кортежи
(True, False)
not in
— проверка на невхождение. Если элемент не входит в последовательность, воз- вращается
True
:
>>> "Строка" not in "Строка для поиска" # Строки
False
>>> "Строка2" not in "Строка для поиска" # Строки
True
>>> 2 not in [1, 2, 3], 4 not in [1, 2, 3] # Списки
(False, True)
>>> 2 not in (1, 2, 3), 6 not in (1, 2, 3) # Кортежи
(False, True)
3.4. Операторы присваивания
Операторы присваивания предназначены для сохранения значения в переменной. Приведем перечень операторов присваивания, доступных в языке Python:

=
— присваивает переменной значение:
>>> x = 5; x
5

+=
— увеличивает значение переменной на указанную величину:
>>> x = 5; x += 10 # Эквивалентно x = x + 10
>>> x
15
Для последовательностей оператор
+=
производит конкатенацию:
>>> s = "Стр"; s += "ока"
>>> print(s)
Строка

-=
— уменьшает значение переменной на указанную величину:
>>> x = 10; x -= 5 # Эквивалентно x = x — 5
>>> x
5

*=
— умножает значение переменной на указанную величину:
>>> x = 10; x *= 5 # Эквивалентно x = x * 5
>>> x
50

Глава 3. Операторы
57
Для последовательностей оператор
*=
производит повторение:
>>> s = "*"; s *= 20
>>> s
'********************'

/=
— делит значение переменной на указанную величину:
>>> x = 10; x /= 3 # Эквивалентно x = x / 3
>>> x
3.3333333333333335
>>> y = 10.0; y /= 3.0 # Эквивалентно y = y / 3.0
>>> y
3.3333333333333335

//=
— деление с округлением вниз и присваиванием:
>>> x = 10; x //= 3 # Эквивалентно x = x // 3
>>> x
3
>>> y = 10.0; y //= 3.0 # Эквивалентно y = y // 3.0
>>> y
3.0

%=
— деление по модулю и присваивание:
>>> x = 10; x %= 2 # Эквивалентно x = x % 2
>>> x
0
>>> y = 10; y %= 3 # Эквивалентно y = y % 3
>>> y
1

**=
— возведение в степень и присваивание:
>>> x = 10; x **= 2 # Эквивалентно x = x ** 2
>>> x
100 3.5. Приоритет выполнения операторов
В какой последовательности будет вычисляться приведенное далее выражение? x = 5 + 10 * 3 / 2
Это зависит от приоритета выполнения операторов. В данном случае последовательность вычисления выражения будет такой:
1. Число 10 будет умножено на 3, т. к. приоритет оператора умножения выше приоритета оператора сложения.
2. Полученное значение будет поделено на 2, т. к. приоритет оператора деления равен при- оритету оператора умножения (а операторы с равными приоритетами выполняются сле- ва направо), но выше, чем у оператора сложения.

58
Часть I. Основы языка Python
3. К полученному значению будет прибавлено число 5, т. к. оператор присваивания
=
имеет наименьший приоритет.
4. Значение будет присвоено переменной x
>>> x = 5 + 10 * 3 / 2
>>> x
20.0
С помощью скобок можно изменить последовательность вычисления выражения: x = (5 + 10) * 3 / 2
Теперь порядок вычислений станет иным:
1. К числу 5 будет прибавлено 10.
2. Полученное значение будет умножено на 3.
3. Полученное значение будет поделено на 2.
4. Значение будет присвоено переменной x
>>> x = (5 + 10) * 3 / 2
>>> x
22.5
Перечислим операторы в порядке убывания приоритета:
1.
-x
,
+x
,
x
,
**
— унарный минус, унарный плюс, двоичная инверсия, возведение в сте- пень. Если унарные операторы расположены слева от оператора
**
, то возведение в сте- пень имеет больший приоритет, а если справа — то меньший. Например, выражение:
-10 ** -2 эквивалентно следующей расстановке скобок:
-(10 ** (-2))
2.
*
,
%
,
/
,
//
— умножение (повторение), остаток от деления, деление, деление с округле- нием вниз.
3.
+
, – — сложение (конкатенация), вычитание.
4.
<<
,
>>
— двоичные сдвиги.
5.
&
— двоичное И.
6.
^
— двоичное исключающее ИЛИ.
7.
|
— двоичное ИЛИ.
8.
=
,
+=
,
-=
,
*=
,
/=
,
//=
,
%=
,
**=
— присваивание.

ГЛ А В А
4
Условные операторы и циклы
Условные операторы позволяют в зависимости от значения логического выражения выпол- нить отдельный участок программы или, наоборот, не выполнить его. Логические выраже- ния возвращают только два значения:
True
(истина) или
False
(ложь), которые ведут себя как целые числа
1
и
0
соответственно:
>>> True + 2 # Эквивалентно 1 + 2 3
>>> False + 2 # Эквивалентно 0 + 2 2
Логическое значение можно сохранить в переменной:
>>> x = True; y = False
>>> x, y
(True, False)
Любой объект в логическом контексте может интерпретироваться как истина (
True
) или как ложь (
False
). Для определения логического значения можно использовать функцию bool()
Значение
True возвращает следующие объекты:
любое число, не равное нулю:
>>> bool(1), bool(20), bool(-20)
(True, True, True)
>>> bool(1.0), bool(0.1), bool(-20.0)
(True, True, True)
не пустой объект:
>>> bool("0"), bool([0, None]), bool((None,)), bool({"x": 5})
(True, True, True, True)
Следующие объекты интерпретируются как
False
:
число, равное нулю:
>>> bool(0), bool(0.0)
(False, False)
пустой объект:
>>> bool(""), bool([]), bool(())
(False, False, False)

60
Часть I. Основы языка Python
значение
None
:
>>> bool(None)
False
4.1. Операторы сравнения
Операторы сравнения используются в логических выражениях. Приведем их перечень:

==
— равно:
>>> 1 == 1, 1 == 5
(True, False)

!=
— не равно:
>>> 1 != 5, 1 != 1
(True, False)

<
— меньше:
>>> 1 < 5, 1 < 0
(True, False)

>
— больше:
>>> 1 > 0, 1 > 5
(True, False)

<=
— меньше или равно:
>>> 1 <= 5, 1 <= 0, 1 <= 1
(True, False, True)

>=
— больше или равно:
>>> 1 >= 0, 1 >= 5, 1 >= 1
(True, False, True)
in
— проверка на вхождение в последовательность:
>>> "Строка" in "Строка для поиска" # Строки
True
>>> 2 in [1, 2, 3], 4 in [1, 2, 3] # Списки
(True, False)
>>> 2 in (1, 2, 3), 4 in (1, 2, 3) # Кортежи
(True, False)
Оператор in можно также использовать для проверки существования ключа словаря:
>>> "x" in {"x": 1, "y": 2}, "z" in {"x": 1, "y": 2}
(True, False)
not in
— проверка на невхождение в последовательность:
>>> "Строка" not in "Строка для поиска" # Строки
False
>>> 2 not in [1, 2, 3], 4 not in [1, 2, 3] # Списки
(False, True)

Глава 4. Условные операторы и циклы
61
>>> 2 not in (1, 2, 3), 4 not in (1, 2, 3) # Кортежи
(False, True)
is
— проверяет, ссылаются ли две переменные на один и тот же объект. Если перемен- ные ссылаются на один и тот же объект, оператор is возвращает значение
True
:
>>> x = y = [1, 2]
>>> x is y
True
>>> x = [1, 2]; y = [1, 2]
>>> x is y
False
Следует заметить, что в целях повышения эффективности интерпретатор производит кэширование малых целых чисел и небольших строк. Это означает, что если ста пере- менным присвоено число 2, то в этих переменных, скорее всего, будет сохранена ссылка на один и тот же объект:
>>> x = 2; y = 2; z = 2
>>> x is y, y is z
(True, True)
is not
— проверяет, ссылаются ли две переменные на разные объекты. Если это так, возвращается значение
True
:
>>> x = y = [1, 2]
>>> x is not y
False
>>> x = [1, 2]; y = [1, 2]
>>> x is not y
True
Значение логического выражения можно инвертировать с помощью оператора not
:
>>> x = 1; y = 1
>>> x == y
True
>>> not (x == y), not x == y
(False, False)
Если переменные x
и y
равны, возвращается значение
True
, но так как перед выражением стоит оператор not
, выражение вернет
False
. Круглые скобки можно не указывать, по- скольку оператор not имеет более низкий приоритет выполнения, чем операторы сравнения.
В логическом выражении можно указывать сразу несколько условий:
>>> x = 10
>>> 1 < x < 20, 11 < x < 20
(True, False)
Несколько логических выражений можно объединить в одно большое с помощью следую- щих операторов:
and
— логическое И. Если x
в выражении x and y интерпретируется как
False
, то воз- вращается x
, в противном случае — y
. Примеры:
>>> 1 < 5 and 2 < 5 # True and True == True
True

62
Часть I. Основы языка Python
>>> 1 < 5 and 2 > 5 # True and False == False
False
>>> 1 > 5 and 2 < 5 # False and True == False
False
>>> 10 and 20, 0 and 20, 10 and 0
(20, 0, 0)
or
— логическое ИЛИ. Если x
в выражении x or y интерпретируется как
False
, то воз- вращается y
, в противном случае — x
. Примеры:
>>> 1 < 5 or 2 < 5 # True or True == True
True
>>> 1 < 5 or 2 > 5 # True or False == True
True
>>> 1 > 5 or 2 < 5 # False or True == True
True
>>> 1 > 5 or 2 > 5 # False or False == False
False
>>> 10 or 20, 0 or 20, 10 or 0
(10, 20, 10)
>>> 0 or "" or None or [] or "s"
's'
Следующее выражение вернет
True только в том случае, если оба выражения вернут
True
: x1 == x2 and x2 != x3
А это выражение вернет
True
, если хотя бы одно из выражений вернет
True
: x1 == x2 or x3 == x4
Перечислим операторы сравнения в порядке убывания приоритета:
1.
<
,
>
,
<=
,
>=
,
==
,
!=
,
<>
, is
, is not
, in
, not in
2. not
— логическое отрицание.
3. and
— логическое И.
4. or
— логическое ИЛИ.
4.2. Оператор ветвления if...else
Оператор ветвления if...else позволяет в зависимости от значения логического выраже- ния выполнить отдельный участок программы или, наоборот, не выполнить его. Оператор имеет следующий формат: if <Логическое выражение>:
<Блок, выполняемый, если условие истинно>
[elif <Логическое выражение>:
<Блок, выполняемый, если условие истинно>
]
[else:
<Блок, выполняемый, если все условия ложны>
]

Глава 4. Условные операторы и циклы
63
Как вы уже знаете, блоки внутри составной инструкции выделяются одинаковым количест- вом пробелов (обычно четырьмя). Концом блока является инструкция, перед которой рас- положено меньшее количество пробелов. В некоторых языках программирования логиче- ское выражение заключается в круглые скобки. В языке Python это делать необязательно, но можно, т. к. любое выражение может быть расположено внутри круглых скобок. Тем не менее, круглые скобки следует использовать только при необходимости разместить условие на нескольких строках.
1   ...   4   5   6   7   8   9   10   11   ...   83