Отображение текста в формате reStructured невозможно без установки Docutils.
Ссылки
======
- В питоне переменная -- это имя объекта.
- У одного объекта может быть много имён. Объект не знает о том, какие
у него бывают имена.
- Когда мы говорим: a = b, мы тем самым говорим питону, что мы хотим
тому объекту, который мы сейчас знаем под именем b дать ещё и имя a.
- После того, как мы поменяли объект, под каким бы именем мы к нему ни
обращались, мы будем видеть изменённый объект.
- Чтобы разобраться со сложными ситуациями, полезно рисовать картинки,
в которой в одной части рисуются имена переменных, а в другой --
объекты, которые в данный момент известны под соответствующими
именами.
- Питон никогда ничего не копирует, если мы только специально его об
этом не попросили.
- Техническим языком: питон всегда манипулирует ссылками и переменная
в питоне -- это ссылка. (Variable is reference).
В этой части мы разбирали такой пример из контрольной::
>>> a = [1, 2, 3]
>>> b = a
>>> b.append(4)
>>> a
Здесь мы в первой строке создаём список [1, 2, 3] и даём ему имя a. Во
второй строке мы говорим, что бы даём ему же ещё и имя b. В третьей
строке мы говорим, что мы дописываем 4 в конец списка, известного нам
под именем b (он же известен нами под именем a). В четвёртой строке мы
смотрим, какое значение нам известно под именем a -- это будет [1, 2,
3, 4].
Пространства имён
=================
- Метафора: в разных кругах общения под именем дядя Ваня может
пониматься разный человек, и это не вызывает ни у кого проблем. В
питоне есть понятие такого круга общения, оно называется пространство
имён (namespace).
- Каждый раз, когда мы запускаем функцию, создаётся новое пространство
имён для её локальных переменных. В нём сразу оказываются аргументы
функции. После завершения функции это пространство имён выбрасывается.
- Стопка таких пространств имён называется стеком вызовов (call stack,
примерно он же traceback).
- Пространство имён вне всех функций называется глобальным.
- На картинке мы это рисуем так: для каждого пространства имён мы
рисуем новый лист, в который мы вписываем имена переменных. При этом
все объекты, с которыми все работают, лежат в общей куче, и мы рисуем
стрелочки от имён переменных к объектам, на которые они ссылаются.
Пример из контрольной::
>>> a = b = [1, 2, 3, 4]
>>> def f(a):
... a = ['a', 'b', 'c']
>>> f(b)
>>> a
Здесь в первой строке мы в глобальном пространстве имён создаём
переменные a и b, которые указывают на список [1, 2, 3, 4]. Во
второй-третьей строке мы определяем функцию f. В четвёртой строке мы
вызываем функцию f и передаём ей содержимое переменной b; при этом мы
попадаем во вторую строку, заводим новое пространство имён, в котором
сразу создаём переменную a, которая указывает на то значение, которое
мы передали функции f (то есть на список [1, 2, 3, 4]). В третьей
строке мы создаём новый список ['a', 'b', 'c'] и говорим, что отныне в
нашей камере заведено называть словом a его. На этом функция
завершается и её пространство имён выбрасывается. В пятой строке мы
печатаем значение переменной a, которое не изменилось.
- Между более локальным и более глобальным пространством имён в питоне
существуют отношения:
- Запись (присвоение в переменную, создание переменной для аргумента
функции, создание переменной цикла и т.п.) всегда происходит в
наиболее локальное пространство имён.
- При чтении питон пытается найти значение в локальном пространстве
имён. Если ему удаётся, он его возвращает. Если ему не удаётся, он
ищет в более глобальном.
Пример из контрольной::
>>> a = b = [1, 2, 3, 4]
>>> def f():
... a.append('d')
>>> f()
>>> b
Здесь в первой строке мы в глобальном пространстве имён создаём
переменные a и b, которые указывают на список [1, 2, 3, 4]. Во
второй-третьей строке мы определяем функцию f. В четвёртой строке мы
вызываем функцию f; при этом создаётся новое пустое пространство имён.
Мы попадаем в третью строку; в ней мы ищем объект по имени a; в
локальном пространстве имён такого нет, находим его в глобальном; для
него мы вызываем метод append и дописываем в конец букву 'd'. NB: в
локальном пространстве имён при этом никакого a так и не появилось. На
этом функция завершается и её пространство имён выбрасывается. В пятой
строке мы печатаем значение переменной b: [1, 2, 3, 4, 'd'].
Объекты и классы
================
- Объект -- это значение, которое можно положить в переменную.
- На объекте можно вызвать метод (если такой метод у этого объекта
есть). Для этого нужно написать: объект точка имя_метода и в скобках
аргументы -- это вы и так знаете. Например::
>>> a = [1, 2, 3]
>>> a.append(4)
- Объект может иметь атрибуты. Атрибут -- это переменная внутри
объекта. Например, в питоне есть тип complex дляпредставления
комплексных чисел. Мы можем написать::
>>> a = 1 + 1j
>>> a.imag
1
- Встроенные типы не позволяют менять атрибуты объектов.
- Если у нас есть много подобным образом устроенных сущностей, мы
можем хранить данные о них в объектах. Для этого нам нужно сделать
класс, описывающий то, что у этих сущностей подобно. В простейшем
примере мы можем считать, что у сущностей нет ничего общего::
>>> class Chair(object):
... pass
- Пока что мы считаем в первой строке обязательной частью синтаксиса
всё, кроме названия класса: Chair.
- Теперь мы можем создать объект этого класса и использовать его для
хранения атрибутов::
>>> a = Chair()
>>> a.num_legs = 4
>>> a.color = "red"
>>> a.health = 100
>>> print a.health, "%"
100 %
- Каждый объект имеет своё собственное пространство имён атрибутов.
- Родительское пространство имён для пространства имён атрибутов
объекта -- это пространство имён его класса.
Например (продолжая прошлый фрагмент диалога с питоном)::
>>> Chair.color = "blue"
>>> b = Chair()
>>> a.color, b.color
("red", "blue")
>>> Chair.color = "green"
>>> a.color, b.color
("red", "green")
Здесь мы видим, что для объектов и классов сохраняется тот же принцип:
пишем всегда в локальное пространство имён(именно туда, куда сказали,
туда питон и пишет), а когда читаем, питон ищет сначала в локальном,
потом в родительском пространстве имён.
- При создании класса, можем исполнить код в его пространстве имён --
это код, который мы пишем в блоке определения класса (то, где сейчас
мы писали pass).
Например::
>>> class Chair(object):
... color = "black"
... health = 100
>>> a = Chair()
>>> b = Chair()
>>> a.health = 50
>>> a.color = "dirty"
>>> Chair.color = "red"
>>> a.health, b.health
(100, 100)
>>> a.color, b.color
("dirty", "red")
- Мы можем для своих классов определять методы. В этом случае, метод
-- это функция в пространстве имён класса.
- При определении метода первый его аргумент будет называться self.
Это тот объект, к которому мы применяем метод. Т.е. когда мы
где-нибудь говорим a.f(), то изнутри метода f() объект a будет виден
под именем self.
- Остальные аргументы при определении метода -- это те аргументы,
которые мы даём при вызове метода.
Например::
>>> class Chair(object):
... color = "black"
... health = 100
... def kick(self, force):
... self.health -= force
>>> a = Chair()
>>> a.color, a.health
("black", 100)
>>> a.color = "red"
>>> a.kick(20)
("red", 80)
- Таким образом, к атрибутам класса можно относиться как к значениям
по умолчанию для атрибутов объектов. (Но помните, что тут происходит
на самом деле, и опасайтесь класть в них изменяемые значения,
например, списки).
- Для улучшения читаемости кода бывает осмысленно для всех возможных атрибутов
- Классы в питоне принято называть с заглавной буквы.
- В питоне осмысленно использовать классы для:
- совсем технических целей: если у вас N сущностей, и у каждой из них
вы хотите хранить M параметров, то будет проще завести класс
(например, пустой), и хранить N объектов, в каждом из них по M
атрибутов. Получится проще, чем если хранить M списков по N значений.
- всё ещё технических целей: если у вас есть просто большая гора
значений и куча функций, которые работают то с одним подмножеством из
этих значений, то с другим, то может быть осмысленно сделать один
объект, и положить все эти значения в него. (Один пример этого мы
разберём, когда будем изучать графические интерфейсы).
- работы со структурами данных (односвязные списки, графы, деревья и
т.п.). Особенно хороши классы для деревьев с разметкой -- коими
являются синтаксические деревья.
- если у вас в задаче есть сущности, для каждой из которых можно
сказать, что она обладает такими-то атрибутами и ведёт себя так-то в
ответ на внешние раздражители (эта реакция станет методами), и
сущности взаимодействуют так-то (это тоже станет методами), то это
идеальный пример, для которого и сочиняли классы. Такие примеры
возникают в больших количествах, когда вы имеете дело с физическими
моделями или компьютерными играми.
Заготовки для домашнего задания
===============================
У нас есть сущности, которые ведут себя по-разному -- это части речи.
Поэтому каждая часть речи, видимо, станет классом.
На доске мы в итоге нарисовали такую заготовку::
nouns = [...]
class NounPhrase(object):
adjectives = []
prepositions = []
plural = False
word = "entity"
def text(self):
phrase = []
for part in self.adjectives + self.prepositions:
phrase.append(part.text())
# разобраться с множественным числом существительного
phrase.append(self.word)
return " ".join(phrase)
def new_noun_phrase():
phrase = NounPhrase()
phrase.word = random.choice(nouns)
phrase.plural = random.choice(True, False)
if random.random() > 0.5:
phrase.prepositions = [new_preposition()]
phrase.adjectives = ...
return phrase
Ещё осмысленно сделать класс для предложений::
class Sentence(object):
subject = None
verb_phrase = None
def new_sentence():
sentence = Sentence()
sentence.subject = new_noun_phrase()
sentence.subject.prepositions = []
# у подлежащего предлогов не бывает,
# и вообще, оно всегда в номинативе
sentence.verb = new_verb()
return sentence