Главная страница
Навигация по странице:

  • 21.5. Анализ экземпляра

  • 21.6. Приватный и защищенный доступ

  • 21.7. Простая программа, моделирующая поток посетителей

  • 21.9. Упражнения

  • 22.1. Подсчет остановок

  • Как устроен Python. Как устроен Python. Харрисон. Харрисон Мэтт


    Скачать 5.41 Mb.
    НазваниеХаррисон Мэтт
    АнкорКак устроен Python
    Дата05.02.2022
    Размер5.41 Mb.
    Формат файлаpdf
    Имя файлаКак устроен Python. Харрисон.pdf
    ТипДокументы
    #352210
    страница17 из 21
    1   ...   13   14   15   16   17   18   19   20   21
    205
    раметре number
    . Фактически Python берет на себя все хлопоты, связанные с параметром self
    , и передает его автоматически.
    ПРИМЕЧАНИЕ
    Когда вы используете вызов вида chair.load(3),
    во внутренней реализации используется вызов следующего вида:
    Chair.load(chair, 3).
    Вы можете опробовать этот способ и убедиться, что он работает, но посту- пать так на практике не рекомендуется, потому что такой код хуже читается и занимает больше места.
    21.5. Анализ экземпляра
    Если у вас имеется экземпляр и вы хотите узнать его атрибуты, есть несколько вариантов. Информацию можно посмотреть в документации
    (если она существует). Можно прочитать код, в котором определяется класс. Наконец, можно воспользоваться функцией dir
    , которая проана- лизирует его за вас:
    >>> dir(chair)
    ['__class__', '__delattr__', '__dict__', '__dir__',
    '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
    '__gt__', '__hash__', '__init__', '__le__', '__lt__',
    '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',
    '__repr__', '__setattr__', '__sizeof__', '__str__',
    '__subclasshook__', '__weakref__', 'count', 'id', 'load',
    'max_occupants', 'unload']
    Напомним, что функция dir перечисляет атрибуты объекта. Обратив- шись к документации dir
    , вы увидите, что приведенное выше определе- ние dir не совсем правильно. В документации говорится:
    «…возвращает упорядоченный по алфавиту список имен, включающий
    (некоторые) атрибуты заданного объекта, а также атрибуты, доступные
    из него».
    help(dir)
    (наше выделение)

    206
    Глава 21. Классы
    Функция выводит атрибуты, доступные из объекта. Фактическое состо- яние экземпляра хранится в атрибуте
    __dict__
    — словаре, связывающем имена атрибутов со значениями:
    >>> chair.__dict__
    {'count': 3, 'id': 21}
    Итак, в экземпляре на самом деле хранятся только атрибуты count и id
    , а другие атрибуты доступны через класс. Где же хранится класс?
    В атрибуте
    __class__
    :
    >>> chair.__class__

    Важно, чтобы экземпляр знал свой класс, потому что в классе хранятся методы и атрибуты класса.
    21.6. Приватный и защищенный доступ
    В некоторых языках существует концепция приватных атрибутов и ме- тодов. Предполагается, что эти методы являются подробностями реали- зации и не должны вызываться конечными пользователями. Более того, язык программирования может блокировать доступ к ним.
    Python не пытается в чем-то препятствовать пользователям. Предпола- гается, что вы взрослый человек и способны отвечать за свои действия.
    Если вы хотите получить к чему-либо доступ, вы сможете это сделать.
    Тем не менее будьте готовы принять последствия.
    Программисты Python понимают, что в объекте может быть удобно хранить состояние и методы, являющиеся подробностями реализации.
    Чтобы конечный пользователь понимал, что к этим компонентам не сто- ит обращаться, их имена начинаются с символа подчеркивания. Ниже приведен класс со вспомогательным методом
    ._check
    , который не пред- назначен для вызова всеми желающими:
    >>> class CorrectChair:
    ... ''' A Chair on a chairlift '''
    ... max_occupants = 4

    21.7. Простая программа, моделирующая поток посетителей
    207
    ... def __init__(self, id):
    ... self.id = id
    ... self.count = 0
    ... def load(self, number):
    ... new_val = self._check(self.count + number)
    ... self.count = new_val
    ... def unload(self, number):
    ... new_val = self._check(self.count - number)
    ... self.count = new_val
    ... def _check(self, number):
    ... if number < 0 or number > self.max_occupants:
    ... raise ValueError('Invalid count:{}'.format(
    ... number))
    ... return number
    Метод
    ._check считается приватным — к нему должны обращаться только экземпляры. Приватные методы вызываются методами
    .load и
    .unload класса. При желании вы сможете вызвать их за пределами класса. Тем не менее делать этого не следует — все компоненты с символом подчеркива- ния считаются подробностями реализации, которые могут отсутствовать в будущих версиях класса.
    21.7. Простая программа, моделирующая
    поток посетителей
    Воспользуемся классом для моделирования потока лыжников на горно- лыжном курорте. Мы сделаем ряд базовых допущений — например, что на каждом кресле могут с равной вероятностью ехать от 0 до max_occupants лыжников. Класс включает подъемник, загружает его и работает в бес- конечном цикле. Четыре раза в секунду выводится текущая статистика:
    import random import time class CorrectChair:
    ''' A Chair on a chairlift '''
    max_occupants = 4

    208
    Глава 21. Классы def __init__(self, id):
    self.id = id self.count = 0
    def load(self, number):
    new_val = self._check(self.count + number)
    self.count = new_val def unload(self, number):
    new_val = self._check(self.count - number)
    self.count = new_val def _check(self, number):
    if number < 0 or number > self.max_occupants:
    raise ValueError('Invalid count:{}'.format(
    number))
    return number
    NUM_CHAIRS = 100
    chairs = []
    for num in range(1, NUM_CHAIRS + 1):
    chairs.append(CorrectChair(num))
    def avg(chairs):
    total = 0
    for c in chairs:
    total += c.count return total/len(chairs)
    in_use = []
    transported = 0
    while True:
    # загрузка loading = chairs.pop(0)
    in_use.append(loading)
    in_use[-1].load(random.randint(0, CorrectChair.max_occupants))
    # выгрузка if len(in_use) > NUM_CHAIRS / 2:
    unloading = in_use.pop(0)
    transported += unloading.count unloading.unload(unloading.count)
    chairs.append(unloading)

    21.9. Упражнения
    209
    print('Loading Chair {} Count:{} Avg:{:.2} Total:{}'.format
    (loading.id, loading.count, avg(in_use), transported))
    time.sleep(.25)
    Эта программа будет бесконечно выводить количество лыжников на подъемнике. Данные выводятся на терминал, но функция print может быть заменена кодом вывода данных в CSV-файл.
    Изменив всего два числа (глобальное значение
    NUM_CHAIRS
    и атрибут клас- са
    CorrectChair.max_occupants
    ), вы сможете изменить поведение модели для моделирования большего или меньшего подъемника. Вызов random.
    randint можно заменить функцией, которая более точно представляет распределение нагрузки.
    21.8. Итоги
    В этой главе классы были рассмотрены более подробно. Мы обсудили терминологию, связанную с классами. Можно говорить «объект» или
    «экземпляр»; эти термины можно считать синонимами. Каждый объект связан с некоторым классом. Класс — своего рода фабрика, определяю- щая поведение объектов/экземпляров.
    Объект создается специальным методом, который называется конструк- тором. Этому методу присваивается имя
    __init__
    . Вы также можете определять собственные методы классов.
    При создании класса необходимо как следует поразмыслить. Какими атрибутами должен обладать класс? Если атрибут остается постоянным для всех объектов, определите его в классе. Если атрибут уникален для объекта, задайте его в конструкторе.
    21.9. Упражнения
    1. Представьте, что вы проектируете приложение для банка. Как должна выглядеть модель клиента? Какими атрибутами она долж- на обладать? Какие методы она должна поддерживать?
    2. Представьте, что вы создаете игру из серии Super Mario. Нужно определить класс для представления героя игры Марио. Как он бу-

    210
    Глава 21. Классы дет выглядеть? Если вы не знакомы с играми серии Super Mario, используйте свою любимую видео- или настольную игру для моде- лирования игрока.
    3. Создайте класс для моделирования твитов (сообщений в «Твитте- ре»). Если вы не знаете, что такое «Твиттер», в Википедии при- водится следующее определение
    1
    : «[…] социальная сеть для пу- бличного обмена сообщениями при помощи веб-интерфейса, SMS, средств мгновенного обмена сообщениями или сторонних про- грамм-клиентов для пользователей интернета любого возраста».
    4. Создайте класс для моделирования бытового электроприбора (то- стер, стиральная машина, холодильник и т. д.).
    1
    https://ru.wikipedia.org/wiki/Твиттер

    22
    Субклассирование
    Помимо группировки состояния и операций, классы также обеспечивают повторное использование кода. Если у вас уже имеется класс, а вам нужен другой класс, слегка отличающийся от него поведением, один из спосо- бов повторного использования заключается в субклассировании. Класс, от которого производится субклассирование, называется суперклассом
    (другое распространенное название суперкласса — родительский класс).
    Предположим, вы хотите создать кресло, способное вмещать шесть лыжников. Чтобы создать класс
    Chair6
    , моделирующий кресло для шести человек, — более специализированную версию
    Chair
    , можно вос- пользоваться субклассированием. Субклассы позволяют программисту
    наследовать методы родительских классов и переопределять методы, которые нужно изменить.
    Ниже приведен класс
    Chair6
    , который является субклассом
    CorrectChair
    :
    >>> class Chair6(CorrectChair):
    ... max_occupants = 6
    Обратите внимание: родительский класс
    CorrectChair заключается в кру- глые скобки после имени класса. Заметим, что
    Chair6
    не определяет кон- структор в своем теле, однако вы можете создавать экземпляры класса:
    >>> sixer = Chair6(76)
    Как Python создает объект, если в классе не определен конструктор? Вот что происходит: когда Python ищет метод
    .__init__
    , поиск начинается

    212
    Глава 22. Субклассирование
    Субклассы class Chair6(CorrectChair):
    max_occupants = 6
    Код
    Что делает компьютер
    CorrectChair
    Id:1aea
    __class__:type max_occupants
    __init__
    Id:1ca8
    __class__:function
    Chair6 6
    Int
    4
    Int
    Id:1ce4
    __class__:type max_occupants
    __bases__
    Id:1cb2
    __class__:tuple
    ( ,)
    Рис. 22.1. Атрибут __bases__ в субклассе. Связь между субклассом и его родительскими классами позволяет искать атрибуты в четко определенном порядке. Если атрибут определен в экземпляре субкласса, то используется этот атрибут. Если нет, то после экземпляра поиск продолжается в классе (__class__) экземпляра. Если и эта попытка оказывается неудачной, поиск осуществляется в родительских классах (__bases__)

    Субклассирование
    213
    Субклассы sixer = Chair6(76)
    Код
    Что делает компьютер
    CorrectChair
    Id:1aea
    __class__:type max_occupants
    __init__
    Id:1ca8
    __class__:function
    Chair6
    sixer
    6
    Int
    4
    Int
    Id:1ce4
    Id:1cf1
    Id
    Count max_occupants
    __bases__
    Id:1cb2
    __class__:tuple
    __class__:type
    __class__:Chair6
    ( ,)
    Int
    0 76
    Int
    Рис. 22.2. Создание экземпляра субкласса. Обратите внимание: экземпляр содержит указатель на свой класс, а класс — указатели на все родительские классы (атрибут __bases__)

    214
    Глава 22. Субклассирование с
    Chair6
    . Так как класс
    Chair6
    содержит только атрибут max_occupants
    ,
    Python не найдет здесь метод
    .__init__
    . Но поскольку
    Chair6
    является субклассом
    CorrectChair
    , он обладает атрибутом
    __bases__
    с перечисле- нием базовых классов, сведенных в кортеж:
    >>> Chair6.__bases__
    (__main__.CorrectChair,)
    Затем Python ищет конструктор в базовых классах. Он находит конструк- тор в
    CorrectChair и использует его для создания нового класса.
    Такой же поиск происходит при вызове
    .load для экземпляра. У экзем- пляра нет атрибута, соответствующего имени метода, поэтому Python проверяет класс экземпляра. В
    Chair6
    тоже нет метода
    .load
    , поэтому
    Python ищет атрибут в базовом классе
    CorrectChair
    . Здесь метод
    .load вызывается со слишком большим значением, что приводит к ошибке
    ValueError
    :
    >>> sixer.load(7)
    Traceback (most recent call last):
    File "/tmp/chair.py", line 30, in
    sixer.load(7)
    File "/tmp/chair.py", line 13, in load new_val = self._check(self.count + number)
    File "/tmp/chair.py", line 23, in _check number))
    ValueError: Invalid count:7
    Python находит метод в базовом классе, но вызов метода
    ._check приво- дит к ошибке
    ValueError
    22.1. Подсчет остановок
    Иногда лыжнику не удается нормально сесть на подъемник. В таких случаях оператор замедляет движение или останавливает подъемник, чтобы помочь лыжнику. Мы можем воспользоваться Python для создания нового класса, который будет подсчитывать количество таких остановок.
    Предположим, при каждом вызове
    .load должна вызываться функция, которая возвращает логический признак того, произошла остановка или

    22.2. super
    215
    нет. В параметрах функции передается количество лыжников и объект кресла.
    Ниже приведен класс, который получает функцию is_stalled в конструк- торе. Эта функция будет вызываться при каждом вызове
    .load
    :
    >>> class StallChair(CorrectChair):
    ... def __init__(self, id, is_stalled):
    ... super().__init__(id)
    ... self.is_stalled = is_stalled
    ... self.stalls = 0
    ... def load(self, number):
    ... if self.is_stalled(number, self):
    ... self.stalls += 1
    ... super().load(number)
    Чтобы создать экземпляр этого класса, необходимо предоставить функ- цию is_stalled
    . Следующая простая функция генерирует остановки в 10 % случаев:
    >>> import random
    >>> def ten_percent(number, chair):
    ... """Return True 10% of time"""
    ... return random.random() < .1
    Теперь можно создать экземпляр, указав функцию ten_percent в качестве параметра is_stalled
    :
    >>> stall42 = StallChair(42, ten_percent)
    22.2. super
    Напомним, что
    StallChair определяет свой собственный метод
    .__init__
    , который вызывается при создании экземпляра. Обратите внимание: первая строка конструктора выглядит так:
    super().__init__(id)
    При вызове super внутри метода вы получаете доступ к правильному родительскому классу. Строка в конструкторе позволяет вызвать кон- структор
    CorrectChair
    . Вместо того чтобы повторять логику назначения

    216
    Глава 22. Субклассирование
    Субклассы
    Код
    Что делает компьютер
    CorrectChair
    StallChair class StallChair(CorrectChair):
    def __init__(self, id, is_stalled):
    super().__init__(id)
    self.is_stalled = is_stalled self.stalls = 0
    def load(self, number):
    if self.is_stalled(number, self):
    self.stalls += 1
    super().load(number)
    Id:1aea
    __class__:type max_occupants
    __init__
    Id:1ca8
    __class__:function
    4
    Int
    Id:1ce4
    Id:1cfa
    __bases__
    __init__
    load
    Id:1cb2
    __class__:tuple
    __class__:type
    __class__:function
    Id:1cff
    __class__:function
    ( ,)
    Рис. 22.3. Код создания субкласса с измененными методами. Обратите внимание на использование super() для вызова метода родительского класса. Диаграмма показывает, какие объекты создаются при создании класса, который является субклассом

    22.3. Итоги
    217
    атрибутов id и count
    , вы можете использовать логику из родительского класса. Так как
    StallChair имеет дополнительные атрибуты, которые нужно задать для экземпляра, это можно сделать после вызова роди- тельского конструктора.
    Метод
    .load тоже содержит вызов super
    :
    def load(self, number):
    if self.is_stalled(number, self):
    self.stalls += 1
    super().load(number)
    В методе
    .load вы вызываете функцию is_stalled
    , чтобы определить, останавливался ли подъемник, после чего передаете управление исходной функциональности
    .load из
    CorrectChair при помощи super
    Размещение общего кода в одном месте (в базовом классе) сокращает количество ошибок и дублирований кода.
    ПРИМЕЧАНИЕ
    Ключевое слово super особенно полезно в двух случаях. Во-первых, при определении порядка разрешения методов в классах с несколькими родите- лями super гарантирует последовательность этого порядка. Во-вторых, при изменении базового класса super самостоятельно определит новый базовый класс, а это упрощает сопровождение кода.
    22.3. Итоги
    В этой главе рассматриваются субклассы — новые специализированные классы, использующие код из своих базовых классов (также называемых суперклассами или родительскими классами). Для любого метода, не реализованного в субклассе, Python использует функциональность роди- тельского класса. В реализации метода вы можете либо переопределить его полностью, либо включить вызов super
    . При вызове super вы полу- чаете доступ к родительскому классу, чтобы использовать содержащуюся в нем функциональность.

    218
    Глава 22. Субклассирование
    22.4. Упражнения
    1. Создайте класс, представляющий кошку. Что может делать кошка?
    Какими свойствами она обладает? Создайте субкласс кошки, пред- ставляющий тигра. Как изменится поведение субкласса?
    2. В предыдущей главе вы создали класс, представляющий Марио из видеоигры Super Mario Brothers. В последних изданиях игры можно было играть за других персонажей. Все они обладали сход- ной базовой функциональностью
    1
    , но различались способностями.
    Создайте базовый класс, представляющий персонажа, после чего реализуйте четыре субкласса — для Марио, Луиджи, Тода и Прин- цессы.
    Имя
    Марио
    Луиджи
    Тод
    Принцесса
    Скорость
    4 3
    5 2
    Прыжок
    4 5
    2 3
    Сила
    4 3
    5 2
    Специальное умение
    Парение в воздухе
    1
    https://www.mariowiki.com/Super_Mario_Bros._2#Playable_characters

    23
    Исключения
    Компьютеру можно приказать выполнить действие, которое он вы- полнить не может, — например, прочитать несуществующий файл или произвести деление на ноль. Python позволяет обработать такие исклю- чительные ситуации, возникающие в программе. В таких случаях Python
    выдает, или инициирует, исключение.
    Обычно при возникновении исключения Python прерывает выполнение и выводит трассировку стека, по которой можно определить, где имен- но возникла проблема. В трассировке стека указывается строка и файл ошибки:
    >>> 3/0
    Traceback (most recent call last):
    File "", line 1, in
    ZeroDivisionError: division by zero
    Из этой трассировки следует, что в строке 1 файла

    (имя «файла» интерпретатора) произошла ошибка деления на ноль. При возникно- вении исключения в ходе выполнения программы трассировка стека показывает, в каком файле и строке возникла проблема. Приведенный пример с интерпретатором не особенно полезен, потому что программа состоит всего из одной строки кода. Однако в больших программах возможна многоуровневая иерархия трассировки стека из-за того, что функции вызывают другие функции и методы.

    1   ...   13   14   15   16   17   18   19   20   21


    написать администратору сайта