Материалы для дальнейшего чтения
- PEP 255: Simple Generators
- Understanding Python’s «with» statement
- Closures in Python
- Числа Фибоначчи
- English Irregular Plural Nouns
Классы и итераторы
Погружение
Генераторы на самом деле — всего лишь частный случай итераторов. Функция, возвращающая (yields) значения, является простым и компактным способом получения функциональности итератора, без непосредственного создания итератора. Помните генератор чисел Фибоначчи? Вот набросок того, как мог бы выглядеть аналогичный итератор:
class Fib:
'''iterator that yields numbers in the Fibonacci sequence'''
def __init__(self, max):
self.max = max
def __iter__(self):
self.a = 0
self.b = 1
return self
def __next__(self):
fib = self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
return fib
Давайте рассмотрим этот пример более детально:
class Fib:
class? Что такое класс?
Определение классов
Python полностью объектно-ориентирован, то есть вы можете определять свои собственные классы, наследовать новые классы от своих или встроенных классов, и создавать экземпляры классов, которые уже определили.
Определить класс в Python просто. Также как и в случае с функциями, раздельное объявление интерфейса не требуется. Вы просто определяете класс и начинаете программировать. Определение класса в Python начинается с зарезервированного слова class, за которым следует имя (идентификатор) класса. Формально, это все, что необходимо, в случае, когда класс не должен быть унаследован от другого класса.
class PapayaWhip: | [K 1] |
pass | [K 2] |
- ↑ Определенный выше класс имеет имя PapayaWhip и не наследует никакой другой класс. Имена классов, как правило, пишутся с большой буквы, НапримерВотТак, но это всего лишь соглашение, а не требование.
- ↑ Вы наверное уже догадались, что каждая строка в определении класса имеет отступ, также как и в случае с функциями, оператором условного перехода if, циклом for или любым другим блоком кода. Первая строка без отступа находится вне блока class.
Класс PapayaWhip не содержит определений методов или атрибутов, но с точки зрения синтаксиса, тело класса не может оставаться пустым. В таких случаях используется оператор pass. В языке Python pass — зарезервированное слово, которое говорит интерпретатору: «идем дальше, здесь ничего нет». Это оператор не делающий ровным счетом ничего, но тем не менее являющийся удобным решением, когда вам нужно сделать заглушку для функции или класса.
Выражение pass в языке Python аналог пустого множества или фигурных скобок в языках Java или C++. |
Многие классы наследуются от других классов, но не этот. Многие классы определяют свои методы, но не этот. Класс в Python не обязан иметь ничего, кроме имени. В частности, людям знакомым с C++ может показаться странным, что у класса в Python отсутствуют в явном виде конструктор и деструктор. Несмотря на то, что это не является обязательным, класс в Python может иметь нечто, похожее на конструктор: метод __init__().
Метод __init__()
В следующем примере демонстрируется инициализация класса Fib, с помощью метода __init__().
class Fib: '''iterator that yields numbers in the Fibonacci sequence''' | [K 1] |
def __init__(self, max): | [K 2] |
- ↑ Классы, по аналогии с модулями и функциями могут (и должны) иметь строки документации (docstrings).
- ↑ Метод __init__() вызывается сразу же после создания экземпляра класса. Было бы заманчиво, но формально неверно, считать его «конструктором» класса. Заманчиво, потому что он напоминает конструктор класса в языке C++: внешне (общепринято, что метод __init__() должен быть первым методом, определенным для класса), и в действии (это первый блок кода, исполняемый в контексте только что созданного экземпляра класса). Неверно, потому что на момент вызова __init__() объект уже фактически является созданным, и вы можете оперировать корректной ссылкой на него (self)
Первым аргументов любого метода класса, включая метод __init__(), всегда является ссылка на текущий экземпляр класса. Принято называть этот аргумент self. Этот аргумент выполняет роль зарезервированного слова this в C++ или Java, но, тем не менее, в Python self не является зарезервированным. Несмотря на то, что это всего лишь соглашение, пожалуйста не называйте этот аргумент как либо еще.
В случае метода __init__(), self ссылается на только что созданный объект; в остальных методах — на экземпляр, метод которого был вызван. И, хотя вам необходимо явно указывать self при определении метода, при вызове этого не требуется; Python добавит его для вас автоматически.
Создание экземпляров
Для создания нового экземпляра класса в Python нужно вызвать класс, как если бы он был функцией, передав необходимые аргументы для метода __init__(). В качестве возвращаемого значения мы получим только что созданный объект.
>>> import fibonacci2 >>> fib = fibonacci2.Fib(100) | |
>>> fib | |
<fibonacci2.Fib object at 0x00DB8810> >>> fib.__class__ | |
<class 'fibonacci2.Fib'> >>> fib.__doc__ | |
'iterator that yields numbers in the Fibonacci sequence' |
- Вы создаете новый экземпляр класса Fib (определенный в модуле fibonacci2) и присваиваете только что созданный объект переменной fib. Единственный переданный аргумент, 100, соответствует именованному аргументу max, в методе __init__() класса Fib.
- fib теперь является экземпляром класса Fib
- Каждый экземпляр класса имеет встроенный атрибут __class__, который указывает на класс объекта. Java программисты могут быть знакомы с классом Class, который содержит методы getName() и getSuperclass(), используемые для получения информации об объекте. В Python, метаданные такого рода доступны через соответствующие атрибуты, но используемая идея та же самая.
- Вы можете получить строку документации (docstring) класса, по аналогии с функцией и модулем. Все экземпляры класса имеют одну и ту же строку документации.
Для создания нового экземпляра класса в Python, просто вызовите класс, как если бы он был функцией, явные операторы, как например new в С++ или Java, в языке Python отсутствуют. |
Переменные экземпляра
Перейдем к следующей строчке:
class Fib: def __init__(self, max): self.max = max | 1. |
- Что такое self.max? Это переменная экземпляра. Она не имеет ничего общего с переменной max, которую мы передали в метод __init__() в качестве аргумента. self.max является «глобальной» для всего экземпляра. Это значит, что вы можете обратиться к ней из других методов.
class Fib: def __init__(self, max): self.max = max | 1. |
. . . def __next__(self): fib = self.a if fib > self.max: | 2. |
- self.max определена в методе __init__()...
- …и использована в методе __next__().
Переменные экземпляра связаны только с одним экземпляром класса. Например, если вы создадите два экземпляра класса Fib с разными максимальными значениями, каждый из них будет помнить только свое собственное значение.
>>> import fibonacci2
>>> fib1 = fibonacci2.Fib(100)
>>> fib2 = fibonacci2.Fib(200)
>>> fib1.max
100
>>> fib2.max
200
Итератор чисел Фибоначчи
Теперь вы готовы узнать как создать итератор. Итератор это обычный класс, который определяет метод __iter__().
class Fib: ①
def __init__(self, max): ②
self.max = max
def __iter__(self): ③
self.a = 0
self.b = 1
return self
def __next__(self): ④
fib = self.a
if fib > self.max:
raise StopIteration ⑤
self.a, self.b = self.b, self.a + self.b
return fib ⑥
① Чтобы построить итератор с нуля,Fib должна быть классом, а не функцией.
Подробнее об итераторах
Погружение
HAWAII + IDAHO + IOWA + OHIO == STATES. Или, если записать это по-другому, 510199 + 98153 + 9301 + 3593 == 621246. Думаете, я брежу? Нет, это просто головоломка.
Позвольте мне привести разгадку.
HAWAII + IDAHO + IOWA + OHIO == STATES
510199 + 98153 + 9301 + 3593 == 621246
H = 5
A = 1
W = 0
I = 9
D = 8
O = 3
S = 6
T = 2
E = 4
Такие головоломки называются криптарифмами. Буквы составляют существующие слова, а если заменить каждую букву цифрой от 0 до 9, получится еще и правильное математическое равенство. Весь фокус в том, чтобы выяснить какая буква соответствует каждой цифре. Все вхождения каждой буквы должны заменяться одной и той же цифрой, одной цифре не могут соответствовать несколько букв и «слова» не могут начинаться с цифры 0.
В этой главе мы познакомимся с потрясающей программой на языке Python, написанной Рэймондом Хейттингером. Эта программа решает криптарифмические головоломки и состоит всего из 14 строк кода.
Наиболее известная криптарифмическая головоломка SEND + MORE = MONEY.
import re
import itertools
def solve(puzzle):
words = re.findall('[A-Z]+', puzzle.upper())
unique_characters = set(''.join(words))
assert len(unique_characters) <= 10, 'Too many letters'
first_letters = {word[0] for word in words}
n = len(first_letters)
sorted_characters = ''.join(first_letters) + \
''.join(unique_characters - first_letters)
characters = tuple(ord(c) for c in sorted_characters)
digits = tuple(ord(c) for c in '0123456789')
zero = digits[0]
for guess in itertools.permutations(digits, len(characters)):
if zero not in guess[:n]:
equation = puzzle.translate(dict(zip(characters, guess)))
if eval(equation):
return equation
if __name__ == '__main__':
import sys
for puzzle in sys.argv[1:]:
print(puzzle)
solution = solve(puzzle)
if solution:
print(solution)
Вы можете запустить программу из командной строки. В Linux это будет выглядеть так. (Выполнение программы может потребовать некоторого времени, зависящего от быстродействия вашего компьютера, а индикатор выполнения в программе отсутствует. Поэтому просто наберитесь терпения.)
you@localhost:~/diveintopython3/examples$ python3 alphametics.py "HAWAII + IDAHO + IOWA + OHIO == STATES"
HAWAII + IDAHO + IOWA + OHIO = STATES
510199 + 98153 + 9301 + 3593 == 621246
you@localhost:~/diveintopython3/examples$ python3 alphametics.py "I + LOVE + YOU == DORA"
I + LOVE + YOU == DORA
1 + 2784 + 975 == 3760
you@localhost:~/diveintopython3/examples$ python3 alphametics.py "SEND + MORE == MONEY"
SEND + MORE == MONEY
9567 + 1085 == 10652