Kodomo

Пользователь

Отображение текста в формате 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