Материалы для дальнейшего чтения

  • 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]
  1. ↑ Определенный выше класс имеет имя PapayaWhip и не наследует никакой другой класс. Имена классов, как правило, пишутся с большой буквы, НапримерВотТак, но это всего лишь соглашение, а не требование.
  2. ↑ Вы наверное уже догадались, что каждая строка в определении класса имеет отступ, также как и в случае с функциями, оператором условного перехода 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]
  1. ↑ Классы, по аналогии с модулями и функциями могут (и должны) иметь строки документации (docstrings).
  2. ↑ Метод __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'  
  1. Вы создаете новый экземпляр класса Fib (определенный в модуле fibonacci2) и присваиваете только что созданный объект переменной fib. Единственный переданный аргумент, 100, соответствует именованному аргументу max, в методе __init__() класса Fib.
  2. fib теперь является экземпляром класса Fib
  3. Каждый экземпляр класса имеет встроенный атрибут __class__, который указывает на класс объекта. Java программисты могут быть знакомы с классом Class, который содержит методы getName() и getSuperclass(), используемые для получения информации об объекте. В Python, метаданные такого рода доступны через соответствующие атрибуты, но используемая идея та же самая.
  4. Вы можете получить строку документации (docstring) класса, по аналогии с функцией и модулем. Все экземпляры класса имеют одну и ту же строку документации.
Для создания нового экземпляра класса в Python, просто вызовите класс, как если бы он был функцией, явные операторы, как например new в С++ или Java, в языке Python отсутствуют.

Переменные экземпляра

Перейдем к следующей строчке:

class Fib: def __init__(self, max): self.max = max 1.
  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.
  1. self.max определена в методе __init__()...
  2. …и использована в методе __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