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

  • Аналогия из реального мира: заполнение форм

  • Создание объектов на базе классов

  • Создание простого класса: WizCoin

  • Методы, __init__() и self

  • Приватные атрибуты и приватные методы

  • Чистыйкод дляпродолжающи х


    Скачать 7.85 Mb.
    НазваниеЧистыйкод дляпродолжающи х
    Дата13.05.2023
    Размер7.85 Mb.
    Формат файлаpdf
    Имя файлаPython_Chisty_kod_dlya_prodolzhayuschikh_2022_El_Sveygart.pdf
    ТипДокументы
    #1127485
    страница33 из 40
    1   ...   29   30   31   32   33   34   35   36   ...   40
    ЧАСТЬ III
    ОБЪЕКТНО-
    ОРИЕНТИРОВАННЫЙ
    PYTHON

    15
    Объектно-ориентированное
    программирование и классы
    Объектно-ориентированное программирование, или
    ООП,механизм языка программирования, позволяю- щий группировать переменные и функции в новые типы данных, называемые классами. На базе классов создаются
    объекты. Распределяя свой код по классам, можно разбить монолитную программу на меньшие части, которые проще по- нять и отладить.
    В небольших программах ООП добавляет не столько структуру, сколько рутину.
    Хотя некоторые языки (например, Java) требуют организации всего кода в классах,
    ООП-функциональность в Python не является обязательной. Программист может воспользоваться классами, если они ему нужны, или забыть про классы, если без них можно обойтись.
    В докладе разработчика Python Джека Дидериха (Jack Diederich) «Перестаньте писать классы» на конференции PyCon 2012 (https://youtu.be/o9pEzgHorH0/) рас- сматриваются некоторые ситуации, в которых программисты пишут классы, хотя можно было бы обойтись более простой функцией или модулем.
    Как бы то ни было, вам как программисту следует знать основы классов и их ис- пользования. Из этой главы вы узнаете, что такое классы, почему они используются в программах и какой синтаксис и концепции программирования лежат в их основе.
    ООП — обширная тема, и эта глава содержит только краткое введение в нее.

    Аналогия из реального мира: заполнение форм
    317
    Аналогия из реального мира: заполнение форм
    Скорее всего, вам неоднократно приходилось заполнять всевозможные формы, анкеты и бланки, бумажные или электронные: при посещении врача, для покупок в интернете или для приглашения на свадьбу. Формы предоставляют унифициро- ванный механизм сбора необходимой информации людьми или организациями.
    Разные формы предназначены для сбора разных видов информации. Врач описыва- ет состояние пациента, а на форме планирования свадьбы вы вводите информацию о приглашенных гостях.
    В Python термины «класс», «тип» и «тип данных» имеют одинаковый смысл.
    Класс, как и бумажная или электронная форма, представляет собой заготовку для создания объектов Python (также называемых экземплярами). Объекты содержат данные, которые представляют конкретного пациента, покупку в интернет-магазине или гостя на свадьбе. Классы напоминают пустые формы, а объекты, созданные на базе этих классов, — заполненные формы с реальными данными, которые требует форма. Так, на рис. 15.1 форму для подтверждения участия можно сравнить с клас- сом, а заполненную форму — с объектом.
    Классы и объекты также можно рассматривать как электронные таблицы (рис. 15.2).
    Просьба ответить до 16 июня.
    Просьба ответ ить до 16 июн я.
    Имя:
    Имя:
    Да, ябуду присутствовать.
    Да, ябуду присут ствовать.
    Нет, яне смогу присутствовать.
    Нет, яне смогу п рисутствовать.
    Количество гостей
    Количество гостей
    Элис Смит
    Классы
    Объекты
    1
    Рис. 15.1. Формы для приглашения гостей на свадьбу напоминают классы, а заполненные формы напоминают объекты

    318
    Глава 15.Объектно-ориентированное программирование и классы
    Рис. 15.2. Электронная таблица с данными гостей. Здесь RSVP означает
    «répondez s'il vous plaît» («просьба ответить»)
    Заголовки столбцов определяют класс, а каждая отдельная строка таблицы — объект.
    Классы и объекты часто приравнивают к моделям элементов реального мира, но не путайте карту с территорией. Содержимое класса зависит от того, что должна сделать программа. На рис. 15.3 изображены некоторые объекты разных классов, представляющие одного и того же человека. Не считая имени, они содержат со- вершенно разную информацию.
    Кроме того, информация, содержащаяся в классах, зависит от потребностей вашей программы. Во многих учебниках ООП для примера используется класс
    Car
    , но при этом авторы забывают, что набор сведений, включаемых в класс, полностью зависит от того, какое приложение вы пишете. Не существует обобщенного класса
    Car
    , кото- рый бы включал метод honkHorn()
    (подать сигнал) или атрибут numberOfCupholders
    (количество подставок для стаканов) только потому, что этими характеристиками обладают реальные машины. Может быть, вы пишете веб-приложение для авто- салона, видеоигру с гонками или модель дорожного движения. Класс
    Car для ав- тосалона может содержать атрибуты milesPerGallon
    (количество миль на галлон) или manufacturersSuggestedRetailPrice
    (рекомендованная цена производителя), подобно тому как имена этих атрибутов могут быть включены в заголовки столбцов

    Создание объектов на базе классов
    319
    в электронных таблицах автосалона. Но в видеоигре и модели дорожного движения этих атрибутов не будет, потому что здесь они не актуальны. Класс
    Car для видео- игры может содержать метод explodeWithLargeFireball()
    (эффектно взорваться), но в приложение для автосалона и модели дорожного движения он не попадет… хочется надеяться.
    Рис. 15.3. Четыре объекта, созданные на базе разных классов. Объекты представляют одного и того же человека в зависимости от того, какая информация о человеке нужна приложению
    Создание объектов на базе классов
    Вы уже использовали классы и объекты в Python, даже если не создавали их сами.
    Вспомните модуль datetime
    , который содержит класс с именем date
    . Объекты клас- са datetime.date
    (также называемые объектами datetime.date или объектами date
    ) представляют конкретную дату. Введите следующий фрагмент в интерактивной оболочке, чтобы создать объект класса datetime.date
    :
    >>> import datetime
    >>> birthday = datetime.date(1999, 10, 31) # Передать год, месяц и день.
    >>> birthday.year
    1999
    >>> birthday.month
    10
    >>> birthday.day
    31
    >>> birthday.weekday() # weekday() - метод, на что указывают круглые скобки.
    6

    320
    Глава 15.Объектно-ориентированное программирование и классы
    Атрибуты представляют собой переменные, связанные с объектом. Вызов datetime.
    date()
    создает новый объект date
    , инициализируемый аргументами
    1999
    ,
    10
    ,
    31
    , так что объект представляет дату 31 октября 1999 года. Эти аргументы присваиваются атрибу- там year
    , month и day класса date
    ; такие атрибуты присутствуют в каждом объекте date
    С подобной информацией метод weekday()
    класса может вычислить день недели.
    В приведенном примере он возвращает 6 — обозначение воскресенья, потому что согласно электронной документации Python возвращаемым значением weekday()
    является целое число от 0 (понедельник) до 6 (воскресенье). В документации так- же перечислены другие методы, содержащиеся в классе date
    . И хотя объект date содержит много атрибутов и методов, это все еще один объект, который можно сохранить в переменной, — такой как birthday в приведенном примере.
    Создание простого класса: WizCoin
    Создадим класс
    WizCoin
    , представляющий набор монет в вымышленной волшебной валюте. В этой валюте используются следующие номиналы: кнаты, сикли (29 кнатов) и галлеоны (17 сиклей, или 493 кната). Помните, что объекты класса
    WizCoin пред- ставляют количество монет разного номинала, а не денежную сумму. Условно говоря, этот класс сообщит, что у вас пять четвертаков и один гривенник, а не 1 р. 35 коп.
    Создайте файл с именем wizcoin.py и введите следующий код для создания класса
    WizCoin
    . Обратите внимание: имя метода
    __init__
    начинается и завершается двумя символами подчеркивания (метод
    __init__
    рассматривается в подразделе «Мето- ды, __init__() и self» этой главы):
    class WizCoin:

    def __init__(self, galleons, sickles, knuts):

    """Создание нового объекта WizCoin по значениям galleons, sickles и knuts."""
    self.galleons = galleons self.sickles = sickles self.knuts = knuts
    # ВНИМАНИЕ: методы __init__() НИКОГДА не содержат команду return.
    def value(self):

    """The value (in knuts) of all the coins in this WizCoin object."""
    return (self.galleons * 17 * 29) + (self.sickles * 29) + (self.knuts)
    def weightInGrams(self):

    """Возвращает вес монет в граммах."""
    return (self.galleons * 31.103) + (self.sickles * 11.34) + (self.knuts
    * 5.0)
    Программа определяет новый класс с именем
    WizCoin при помощи команды class

    При создании класса создается новый тип объектов. Использование команды

    Создание простого класса: WizCoin
    321
    class для определения класса напоминает команду def
    , задающую новые функции.
    Внутри блока кода, следующего за командой class
    , следуют три определения трех методов:
    __init__()
    (сокращение от initializer)

    , value()

    и weightInGrams()

    Обратите внимание: все методы имеют первый параметр с именем self
    , который будет рассмотрен в следующем разделе.
    По общепринятым соглашениям имена модулей (например, wizcoin в файле wiz- coin.py
    ) записываются в нижнем регистре, а имена классов (например,
    WizCoin
    ) на- чинаются с буквы верхнего регистра. К сожалению, некоторые классы стандартной библиотеки Python — такие как date
    — этому соглашению не следуют.
    Чтобы потренироваться в создании новых объектов класса
    WizCoin
    , введите сле- дующий исходный код в отдельном окне редактора и сохраните файл с именем wcexample1.py в одной папке с wizcoin.py
    :
    import wizcoin purse = wizcoin.WizCoin(2, 5, 99) # Целые числа передаются __init__().

    print(purse)
    print('G:', purse.galleons, 'S:', purse.sickles, 'K:', purse.knuts)
    print('Total value:', purse.value())
    print('Weight:', purse.weightInGrams(), 'grams')
    print()
    coinJar = wizcoin.WizCoin(13, 0, 0) # Целые числа передаются __init__().

    print(coinJar)
    print('G:', coinJar.galleons, 'S:', coinJar.sickles, 'K:', coinJar.knuts)
    print('Total value:', coinJar.value())
    print('Weight:', coinJar.weightInGrams(), 'grams')
    Вызовы
    WizCoin()

    и

    создают объекты
    WizCoin и выполняют для них код метода
    __init__()
    . В аргументах
    WizCoin()
    передаются три целых числа, которые пере- даются параметрам
    __init__()
    . Эти аргументы присваиваются атрибутам self.
    galleons
    , self.sickles и self.knuts объекта. Подобно тому как функция time.
    sleep()
    требует сначала импортировать модуль time и поставить префикс time.
    перед именем функции, мы также должны импортировать wizcoin и поставить префикс wizcoin.
    перед именем функции
    WizCoin()
    Результат выполнения программы выглядит приблизительно так:

    G: 2 S: 5 K: 99
    Total value: 1230
    Weight: 613.906 grams

    G: 13 S: 0 K: 0
    Total value: 6409
    Weight: 404.339 grams

    322
    Глава 15.Объектно-ориентированное программирование и классы
    Если вы получите сообщение об ошибке (например,
    ModuleNotFoundError:
    No module named
    'wizcoin'
    ), убедитесь в том, что файлу присвоено имя wizcoin.py и он находится в одной папке с wcexample1.py
    Объекты
    WizCoin не имеют полезного строкового представления, поэтому при вы- воде purse и coinJar выводится адрес памяти в угловых скобках. (Из главы 17 вы узнаете, как изменить выводимое представление.)
    Подобно тому как для объекта строки можно вызвать метод lower()
    , мы можем вызвать методы value()
    и weightInGrams()
    для объектов
    WizCoin
    , присвоенных переменным purse и coinJar
    . Эти методы вычисляют результат по значениям атрибутов galleons
    , sickles и knuts объекта.
    Классы и ООП упрощают сопровождение кода — то есть код проще читается, изменяет- ся и расширяется в будущем. Изучим методы и атрибуты этого класса более подробно.
    Методы, __init__() и self
    Методы представляют собой функции, связанные с объектами некоторого класса.
    Вспомните, что lower()
    является методом строк — это означает, что он вызывается для объектов строк. Метод lower()
    можно вызвать для строки (например,
    'Hello'.
    lower()
    ), но вызвать его для списка (например,
    ['dog',
    'cat'].lower()
    ) не полу- чится. Также обратите внимание на то, что метод указывается после имени объекта: правильным считается код 'Hello'.lower()
    , а не lower('Hello')
    . В отличие от мето- да lower()
    функция len()
    не связана с одним конкретным типом данных; при вызове len()
    можно передавать строки, списки, словари и многие другие типы объектов.
    Как было показано в предыдущем разделе, объект создается вызовом имени клас- са как функции. Эта функция называется функцией-конструктором (или просто
    конструктором), потому что она конструирует новый объект. Также говорят, что конструктор создает новый экземпляр класса.
    При вызове конструктора Python создает новый объект, а затем выполняет метод
    __init__()
    . Наличие метода
    __init__()
    в классе не обязательно, но он почти всегда присутствует. Именно в методе
    __init__()
    обычно задаются исходные значения атрибутов. Например, я снова приведу метод
    __init__()
    класса
    WizCoin
    :
    def __init__(self, galleons, sickles, knuts):
    """Создание нового объекта WizCoin по значениям galleons, sickles и knuts."""
    self.galleons = galleons self.sickles = sickles self.knuts = knuts
    # ВНИМАНИЕ: методы __init__() НИКОГДА не содержат команду return.
    Когда программа wcexample1.py вызывает
    WizCoin(2,
    5,
    99)
    , Python создает но- вый объект
    WizCoin и передает три аргумента (
    2
    ,
    5
    и
    99
    ) вызову
    __init__()
    . Но

    Создание простого класса: WizCoin
    323
    метод
    __init__()
    получает четыре параметра: self
    , galleons
    , sickles и knuts
    . Дело в том, что у каждого метода имеется первый параметр с именем self
    . Когда метод вызывается для объекта, этот объект автоматически передается в параметре self
    Остальные аргументы присваиваются параметрам как обычно. Если вы увидите со- общение об ошибке вида
    TypeError:
    __init__()
    takes
    3
    positional arguments but
    4
    were given
    (TypeError: __init__() получает 3 позиционных аргумента, но задано 4), скорее всего, вы забыли добавить параметр self в команду def метода.
    Присваивать первому параметру имя self необязательно; имя может быть любым.
    Однако имя self считается общепринятым, и выбор иного имени затруднит чтение вашего кода другими программистами Python. Когда вы читаете код, первый пара- метр self помогает быстро отличить методы от функций. Аналогичным образом, если в коде метода нигде не используется параметр self
    , это указывает на то, что, возможно, метод стоит оформить в виде функции.
    Аргументы
    2
    ,
    5
    и
    99
    в вызове
    WizCoin(2,
    5,
    99)
    не присваиваются атрибутам нового объекта автоматически; чтобы это произошло, необходимо включить три команды присваивания в
    __init__()
    . Часто параметрам
    __init__()
    присваиваются имена, со- впадающие с именами атрибутов, но наличие self в self.galleons означает, что это атрибут объекта, а galleons
    — параметр. Сохранение аргументов конструктора в атри- бутах объекта — одна из типичных задач метода
    __init__()
    класса. Вызов datetime.
    date()
    в предыдущем разделе выполнял аналогичную операцию, хотя тогда пере- давались три аргумента для атрибутов year
    , month и day создаваемого объекта date
    Ранее мы вызвали функции int()
    , str()
    , float()
    и bool()
    для преобразования между типами данных — например, вызов str(3.1415)
    возвращал строковое значение '3.1415'
    для значения с плавающей точкой
    3.1415
    . Ранее в тексте они описывались как функции, но int
    , str
    , float и bool в действительности являются классами, а int()
    , str()
    , float()
    и bool()
    — конструкторами, которые возвращают новые объекты целого числа, строки, числа с плавающей точкой и логического зна- чения. Руководство по стилю Python рекомендует использовать для имен классов
    «верблюжью» схему с первой буквой в верхнем регистре, хотя многие встроенные классы Python этому соглашению не следуют.
    Вызов функции-конструктора
    WizCoin()
    возвращает новый объект
    WizCoin
    , но ме- тод
    __init__()
    не может содержать команды return с возвращаемым значением. При попытке добавить возвращаемое значение выдается ошибка
    TypeError:
    __init__()
    should return
    None
    (TypeError: __init__() должен возвращать None).
    Атрибуты
    Атрибутами называются переменные, связанные с объектом. В документации Python атрибут описывается как «любое имя, следующее после точки». Вспомните выражение birthday.year из предыдущего раздела: атрибут year
    — имя, следующее после точки.

    324
    Глава 15.Объектно-ориентированное программирование и классы
    Каждый объект содержит собственный набор атрибутов. Когда программа wcexample1.py создает два объекта
    WizCoin и сохраняет их в переменных purse и coinJar
    , их атрибуты имеют разные значения. К ним можно обращаться и при- сваивать значения, как и к любой другой переменной. Чтобы потренироваться в присваивании значений атрибутов, откройте в редакторе окно с новым файлом и введите следующий код, сохранив его в файле wcexample2.py в одной папке с файлом wizcoin.py
    :
    import wizcoin change = wizcoin.WizCoin(9, 7, 20)
    print(change.sickles) # Выводит 7.
    change.sickles += 10
    print(change.sickles) # Выводит 17.
    pile = wizcoin.WizCoin(2, 3, 31)
    print(pile.sickles) # Выводит 3.
    pile.someNewAttribute = 'a new attr' # Создается новый атрибут.
    print(pile.someNewAttribute)
    При выполнении этой программы результат выглядит так:
    7 17 3
    a new attr
    Атрибуты объекта также можно сравнить с ключами словаря. Вы можете читать и изменять связанные с ними значения, а также присваивать новые атрибуты объ- екту. Формально методы также считаются атрибутами класса.
    Приватные атрибуты и приватные методы
    В таких языках, как C++ или Java, атрибуты могут помечаться как имеющие при-
    ватный уровень доступа. Это означает, что компилятор или интерпретатор позволит обращаться к атрибутам объектов этого класса только коду методов этого класса.
    В языке Python такого ограничения не существует. Все атрибуты и методы факти- чески имеют открытый уровень доступа: код за пределами класса может обратиться к любым атрибутам любых объектов этого класса и изменять их.
    Впрочем, приватный доступ полезен. Например, объекты класса
    BankAccount могут содержать атрибут balance
    , который должен быть доступен только для методов класса
    BankAccount
    . По этим причинам в Python принято начинать имена приват- ных атрибутов и методов с одного символа подчеркивания. Технически ничто не мешает коду за пределами класса обращаться к приватным атрибутам и методам, но на практике лучше обращаться к ним только из методов класса.

    Создание простого класса: WizCoin
    325
    Откройте в редакторе окно с новым файлом, введите следующий код и сохраните его с именем privateExample.py
    . В нем объекты класса
    BankAccount содержат приват- ные атрибуты
    _name и
    _balance
    , к которым должны обращаться напрямую только методы deposit()
    и withdraw()
    :
    class BankAccount:
    def __init__(self, accountHolder):
    # Методы BankAccount могут обращаться к self._balance, но код
    # за пределами класса этого делать не должен:
    self._balance = 0

    self._name = accountHolder

    with open(self._name + 'Ledger.txt', 'w') as ledgerFile:
    ledgerFile.write('Balance is 0\n')
    def deposit(self, amount):
    if amount <= 0:

    return # Отрицательные "зачисления" недопустимы.
    self._balance += amount with open(self._name + 'Ledger.txt', 'a') as ledgerFile:

    ledgerFile.write('Deposit ' + str(amount) + '\n')
    ledgerFile.write('Balance is ' + str(self._balance) + '\n')
    def withdraw(self, amount):
    if self._balance < amount or amount < 0:

    return # Не хватает средств на счете или снимается
    # отрицательная сумма.
    self._balance -= amount with open(self._name + 'Ledger.txt', 'a') as ledgerFile:

    ledgerFile.write('Withdraw ' + str(amount) + '\n')
    ledgerFile.write('Balance is ' + str(self._balance) + '\n')
    acct = BankAccount('Alice') # Создание учетного счета.
    acct.deposit(120) # _balance можно изменять через deposit()
    acct.withdraw(40) # _balance можно изменять через withdraw()
    # Изменение _name или _balance за пределами BankAccount нежелательно, но возможно:
    acct._balance = 1000000000

    acct.withdraw(1000)
    acct._name = 'Bob' # Теперь изменяется счет Боба!

    acct.withdraw(1000) # Операция регистрируется в BobLedger.txt!
    При выполнении программы privateExample.py создаваемые файлы содержат не- корректную информацию, потому что
    _balance и
    _name изменялись за пределами класса, что привело к недействительным состояниям. Файл
    AliceLedger.txt содержит непонятно откуда взявшуюся огромную сумму:
    Balance is 0
    Deposit 120
    Balance is 120
    Withdraw 40

    326
    Глава 15.Объектно-ориентированное программирование и классы
    Balance is 80
    Withdraw 1000
    Balance is 999999000
    Файл
    BobLedger.txt содержит необъяснимый баланс, хотя мы никогда не создавали объект
    BankAccount для пользователя
    Bob
    :
    Withdraw 1000
    Balance is 999998000
    Хорошо спроектированные классы в целом автономны, и они должны предоставлять методы для присваивания атрибутам допустимых значений. Атрибуты
    _balance и
    _name помечены как приватные

    и

    , а значение класса
    BankAccount должно изме- няться только при помощи методов deposit()
    и withdraw()
    . Эти два метода содержат проверки

    и

    , которые проверяют, что атрибут
    _balance не переводится в недействи- тельное состояние (например, ему не присваивается отрицательное целое значение).
    Методы также регистрируют каждую операцию для текущего баланса

    и

    Код за пределами класса, изменяющий эти атрибуты (например, команды acct._
    balance
    =
    1000000000

    или acct._name
    =
    'Bob'

    ), может перевести объект в некор- ректное состояние и создать ошибки. Соблюдение соглашений об использовании префикса
    _
    для приватного доступа упрощает отладку. Вы точно знаете: ошибку нужно искать в коде класса, а не в коде всей программы.
    В отличие от Java и других языков, Python не требует определения открытых get- и set-методов для приватных атрибутов. Вместо этого в Python используются свойства (см. главу 17).
    1   ...   29   30   31   32   33   34   35   36   ...   40


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