Объектно-ориентированное программирование (ООП) — это подход к организации программ, который объединяет многие концепции, представленные в этой главе. Подобно абстрактным типам данных, объекты создают границу абстракции между использованием и реализацией данных. Как и в случае с диспетчеризацией сообщений, объекты реагируют на запросы поведения. Подобно изменяемым структурам данных, у объектов есть локальное состояние, и они не могут быть доступны напрямую из глобальной среды.
Система объектов Python предоставляет новый синтаксис, облегчающий реализацию всех этих практических приёмов для организации программы. Однако система объектов не только обеспечивает удобство; она также добавляет новую метафору в программирование, где несколько частей программы взаимодействуют друг с другом. Каждый объект связывает локальное состояние и поведение таким образом, чтобы скрыть сложность за абстракцией данных. Наш пример ограничивающей программы создаёт эту метафору путём передачи сообщения перед ограничением и соединением. Система объектов Python использует новый способ выражения того, как различные части программы связаны друг с другом и как они общаются друг с другом. Объекты не только передают сообщения, но и разделяют поведение с другими объектами того же типа и наследуют характеристики от связанных типов.
Шаблон объектно-ориентированного программирования использует свой собственный словарь для усиления метафоры объекта. Мы уже видели, что объект — это данные со значениями методов и атрибутов, доступ к которым осуществляется через оператор точки. У каждого объекта есть тип, называемый классом. В Python можно определять новые классы, подобно определению функций.
2.5.1. Объекты и классы
Класс может служить шаблоном для всех объектов своего типа. Каждый объект является экземпляром определённого класса. Все объекты, которые мы использовали до сих пор, имеют встроенные типы, но мы можем определить новые классы, как мы определяем функции. Определение класса определяет атрибуты и методы, общие для объектов этого класса. Мы рассмотрим пример банковского счёта, чтобы представить определение класса.
При обсуждении локального состояния мы увидели, что банковский счёт естественно моделируется как имеющий переменное значение баланса. Банковский счёт должен иметь метод withdraw, который обновляет баланс счёта при наличии средств и возвращает запрошенную сумму. Мы хотим добавить дополнительные виды поведения, чтобы улучшить абстракцию счёта: банковский счёт должен быть способен возвращать свой текущий баланс, имя владельца счёта и принимать депозиты.
Класс Account позволяет нам создавать несколько экземпляров банковских счетов. Создание нового экземпляра объекта называется созданием экземпляра класса. Синтаксис создания экземпляра класса в Python аналогичен вызову функции. Здесь мы используем параметр «Джим» для вызова Account.
>>> a = Account('Jim')
Атрибуты объекта — это связанные с объектом пары имя-значение, к которым можно получить доступ через оператор точки. Атрибуты специфичны для конкретного объекта, а не для всех объектов класса, также называемые атрибутами экземпляра. Каждый экземпляр Account имеет свои собственные значения баланса и имени владельца, которые являются примером атрибута экземпляра. В более широком сообществе программистов атрибуты экземпляра могут также называться полями, атрибутами или переменными экземпляра.
>>> a.holder
'Jim'
>>> a.balance
0
Функция, которая выполняет действие над объектом или выполняет вычисление для объекта, называется методом. Побочные эффекты и возвращаемые значения метода могут зависеть или изменять другие атрибуты объекта. Например, deposit — это метод для объекта a класса Account. Он принимает один параметр, сумму, которую необходимо внести, изменяет атрибут баланса объекта и возвращает полученный остаток.
>>> a.deposit(15)
15
В ООП мы говорим, что метод можно вызывать для определённого объекта. Результатом вызова метода withdraw будет либо снятие денег успешно, уменьшение баланса и возврат, либо отказ в запросе, вывод сообщения об ошибке на счёт.
>>> a.withdraw(10) # The withdraw method returns the balance after withdrawal
5
>>> a.balance # The balance attribute has changed
5
>>> a.withdraw(10)
'Insufficient funds'
Как показано выше, поведение метода зависит от изменения атрибутов объекта. Два вызова withdraw с одним и тем же параметром возвращают разные результаты.
2.5.2. Определение классов
Определяемые пользователем классы создаются с помощью оператора class, который содержит одно предложение. Предложение класса определяет имя класса и базовый класс (обсуждается в разделе о наследовании), за которым следует блок определения атрибутов класса:
class <name>(<base class>):
<suite>
Когда выполняется оператор класса, создаётся новый класс, и он привязывается к в текущей среде первого кадра. Затем выполняется блок. Любое имя привязывается в блоке класса, создавая или изменяя атрибуты класса с помощью операторов def или присваивания.
Классы обычно организуются вокруг атрибутов экземпляра, которые представляют собой пары имя-значение, не связанные с самим классом, но связанные с каждым объектом класса. Определяя методы для создания экземпляров новых объектов, класс определяет поведение своих объектов.
Блок оператора class содержит операторы def, определяющие новые методы для класса. Метод, используемый для создания объектов, имеет специальное имя в Python, init, которое называется конструктором класса.
>>> class Account(object):
def __init__(self, account_holder):
self.balance = 0
self.holder = account_holder
Метод init класса Account имеет два параметра. Первый — self, привязанный к новому созданному объекту Account. Второй параметр, account_holder, привязан к переданному параметру при создании экземпляра.
Конструктор устанавливает атрибут balance равным 0. Он также привязывает атрибут holder к account_holder. Параметр account_holder является локальным именем метода init. С другой стороны, атрибут holder, связанный с помощью последнего оператора присваивания, остаётся постоянным, поскольку он используется в качестве атрибута self.
После определения класса Account мы можем создать его экземпляр:
>>> a = Account('Jim')
Этот вызов класса Account создаёт новый объект, который является экземпляром Account, а затем вызывает конструктор init с двумя параметрами: новым объектом и строкой 'Jim'. По соглашению мы используем имя self для обозначения первого параметра конструктора, потому что он привязан к создаваемому объекту. Это соглашение используется почти во всём коде Python.
Теперь мы можем использовать оператор точки для доступа к атрибутам balance и holder объекта.
>>> a.balance
0
>>> a.holder
'Jim'
Идентичность. Каждый новый экземпляр счёта имеет свой собственный атрибут баланса, значение которого не зависит от других объектов того же класса.
>>> b = Account('Jack')
>>> b.balance = 200
>>> [acc.balance for acc in (a, b)]
[0, 200]
Чтобы усилить это разделение, каждый объект пользовательского класса имеет уникальную идентичность. Идентичность объекта сравнивается с операторами is и is not.
>>> a is a
True
>>> a is not b
True
Хотя они были созданы с помощью одного и того же вызова, объекты, связанные с a и b, различны. Обычно присвоение объекта новой переменной не создаёт новый объект.
>>> c = a
>>> c is a
True
Новые объекты пользовательских классов создаются только тогда, когда класс (например, Account) вызывается для создания экземпляра.
Методы. Методы объекта также определяются с помощью операторов def в блоке класса. Ниже deposit и withdraw определены как методы класса Account:
>>> class Account(object):
def __init__(self, account_holder):
self.balance = 0
self.holder = account_holder
def deposit(self, amount):
self.balance = self.balance + amount
return self.balance
def withdraw(self, amount):
if amount > self.balance:
return 'Insufficient funds'
self.balance = self.balance - amount
return self.balance
Хотя определение метода и определение функции похожи по форме, определение метода имеет другой эффект. Значение функции, созданной оператором def в классе, привязывается к объявленному имени, но оно привязывается только как атрибут в локальной области класса. Это значение можно использовать в качестве метода для вызова в экземплярах класса с помощью оператора точки. Перевод текста на русский язык:
Каждый метод определения также содержит специальный первый параметр self
, который привязан к объекту, вызывающему метод. Например, предположим, что deposit
вызывается для определённого объекта Account
и передаётся значение объекта: сумма, которую нужно внести. Сам объект привязан к self
, а параметр привязан к amount
. Все вызываемые методы могут получить доступ к объекту через параметр self
и, следовательно, они могут получить доступ и изменить состояние объекта.
Для вызова этих методов мы снова используем оператор точки, как показано ниже:
>>> tom_account = Account('Tom')
>>> tom_account.deposit(100)
100
>>> tom_account.withdraw(90)
10
>>> tom_account.withdraw(90)
'Insufficient funds'
>>> tom_account.holder
'Tom'
Когда метод вызывается с помощью оператора точки, объект (в этом примере привязанный к tom_account
) выполняет двойную функцию. Во-первых, он определяет, какое имя имеет withdraw
; withdraw
не является именем в среде, а является локальным именем класса Account
. Во-вторых, когда вызывается метод withdraw
, он привязывается к первому параметру self
. Процесс разрешения оператора точки будет показан в следующем разделе.
Методы определения находятся в классе, а свойства экземпляра обычно присваиваются в конструкторе, оба из которых являются основными элементами объектно-ориентированного программирования. Эти две концепции во многом похожи на реализацию передачи значений данных в системе обмена сообщениями. Объект принимает сообщение с помощью оператора точки, но сообщение не является произвольным значением строки, а является местным именем класса. У объекта также есть именованные локальные значения состояния (свойства экземпляра), но это состояние можно получить и изменить с помощью оператора точки без необходимости использования оператора nonlocal
в реализации.
Основная концепция передачи сообщений заключается в том, что данные должны иметь поведение в ответ на сообщения, которые связаны с абстрактными типами, представленными этими сообщениями. Оператор точки является синтаксической особенностью Python, которая формирует метафору передачи сообщений. Использование языка с встроенной системой объектов имеет то преимущество, что передача сообщений может быть бесшовно интегрирована с другими языковыми функциями, такими как операторы присваивания. Нам не нужны разные сообщения для «получения» и «установки» связанных со свойствами имён значений; синтаксис языка позволяет нам напрямую использовать имена сообщений.
Выражение точки. Фрагмент кода, подобный tom_account.deposit
, называется выражением точки. Выражение точки состоит из выражения, точки и имени:
<expression> . <name>
<expression>
может быть любым допустимым выражением Python, но <name>
должно быть простым именем (а не выражением, которое оценивается как name
). Выражение точки использует предоставленное <name>
для получения значения атрибута объекта, имеющего значение <expression>
.
Встроенные функции getattr
также возвращают атрибут объекта по имени. Это эквивалентно оператору точки. Используя getattr
, мы можем использовать строку для поиска атрибута, подобно словарю рассылки:
>>> getattr(tom_account, 'balance')
10
Мы также можем использовать hasattr
для проверки наличия у объекта определённого именованного атрибута:
>>> hasattr(tom_account, 'deposit')
True
Атрибуты объекта включают все свойства экземпляра, а также все атрибуты, определённые в классе (включая методы). Методы требуют специальной обработки как атрибуты класса.
Методы и функции. Когда метод вызывается на объекте, объект неявно передаётся методу в качестве первого параметра. То есть объект, значение которого равно <expression>
слева от оператора точки, автоматически передаётся методу справа от оператора в качестве первого параметра. Поэтому объект привязывается к параметру self
.
Чтобы автоматически реализовать привязку self
, Python различает функции и связанные методы. Мы уже создали первые в начале этого курса, а вторые объединяются с экземпляром при вызове метода. Значение связанного метода уже связывает первую функцию с вызываемым экземпляром, и экземпляр будет назван self
при вызове метода.
Используя возвращаемое значение оператора точки в интерактивном интерпретаторе, мы можем увидеть их различия. Как атрибут класса, метод — это просто функция, но как атрибут экземпляра, это связанный метод:
>>> type(Account.deposit)
<class 'function'>
>>> type(tom_account.deposit)
<class 'method'>
Единственная разница между этими двумя результатами заключается в том, что первый является стандартной двоичной функцией с параметрами self
и amount
. Второй — унарный метод, который автоматически привязывает имя self
к объекту с именем tom_account
при вызове метода, а имя amount
будет привязано к переданному параметру метода. Эти два значения, будь то значение функции или значение связанного метода, связаны с одной и той же связанной функцией deposit
.
Мы можем вызвать deposit
двумя способами: как функцию или как связанный метод. В первом примере мы должны явно предоставить аргумент self
. Для последнего self
уже привязан.
>>> Account.deposit(tom_account, 1001) # The deposit function requires 2 arguments
1011
>>> tom_account.deposit(1000) # The deposit method takes 1 argument
2011
Поведение функции getattr
похоже на поведение оператора: её первый параметр — это объект, а второй параметр (имя) — это метод, определённый в классе. Затем getattr
возвращает значение связанного метода. С другой стороны, если первый параметр является классом, getattr
вернёт значение атрибута напрямую, поскольку это всего лишь функция.
Практическое руководство: соглашения об именах. Имена классов обычно записываются с заглавной буквы (также называемой верблюжьим регистром, потому что заглавные буквы в середине имени похожи на горб верблюда). Имена методов следуют соглашению об именах функций, используя подчёркивание для разделения строчных букв.
Иногда некоторые переменные экземпляра и методы связаны с согласованностью объекта и не должны быть видны или использоваться пользователем класса. Они не являются частью абстракции, определённой классом, а скорее частью реализации. Соглашение Python гласит, что если имя атрибута начинается с подчёркивания, оно доступно только внутри метода или класса и не может быть доступно пользователю класса.
Некоторые значения свойств разделяются всеми объектами определённого класса. Такие атрибуты связаны с самим классом, а не с каким-либо отдельным экземпляром класса. Например, пусть банк выплачивает проценты по остатку на счёте по фиксированной ставке. Эта ставка может измениться, но она является общим значением для всех счетов.
Атрибут класса создаётся с помощью инструкции class
, содержащей оператор присваивания, расположенный вне любого определения метода. В более широком сообществе разработчиков атрибуты класса также называются атрибутами класса или статическими переменными. Следующая инструкция class
создаёт атрибут класса с именем interest
для класса Account
:
>>> class Account(object):
interest = 0.02 # A class attribute
def __init__(self, account_holder):
self.balance = 0
self.holder = account_holder
# Additional methods would be defined here
Этот атрибут всё ещё можно получить через любой экземпляр класса.
>>> tom_account = Account('Tom')
>>> jim_account = Account('Jim')
>>> tom_account.interest
0.02
>>> jim_account.interest
0.02
Однако присвоение одного значения классу изменяет значение свойства для всех экземпляров класса.
>>> Account.interest = 0.04
>>> tom_account.interest
0.04
>>> jim_account.interest
0.04
Имена атрибутов. Мы уже ввели достаточно сложности в нашу систему объектов, поэтому нам необходимо определить, как имена разрешаются в конкретные атрибуты. В конце концов, у нас могут быть одноимённые атрибуты класса и экземпляра.
Как мы видели, выражение точки состоит из выражения, точки и имени:
<expression> . <name>
Чтобы разрешить выражение точки:
<expression>
слева от точки, чтобы создать объект выражения точки.<name>
будет соответствовать свойству экземпляра; если атрибут существует, будет возвращено его значение.<name>
не существует в свойстве экземпляра, он будет искать <name>
в классе, что создаст значение атрибута класса.Во время этого процесса разрешения атрибут экземпляра сначала ищется перед атрибутом класса, аналогично тому, как локальное имя имеет приоритет над глобальным. Определённые в классе методы ищутся на третьем этапе процесса разрешения и привязываются к выражению точки объекта. Процесс поиска имени в классе имеет дополнительные различия, которые возникают при введении наследования классов. Свойства атрибута класса interest влияют на атрибут экземпляра tom_account, но не влияют на экземпляр jim_account.
>>> Account.interest = 0.05 # изменение свойства класса
>>> tom_account.interest # изменяет экземпляры без одноимённых свойств экземпляра
0.05
>>> jim_account.interest # но существующее свойство экземпляра не затрагивается
0.08
При использовании парадигмы объектно-ориентированного программирования (ООП) мы часто обнаруживаем, что различные абстрактные структуры данных связаны между собой. В частности, мы обнаруживаем, что похожие классы различаются по степени специализации. Два класса могут иметь схожие атрибуты, но один представляет особый случай другого.
Например, мы можем захотеть реализовать текущий счёт, который отличается от стандартного счёта. Текущий счёт взимает дополнительную плату в размере 1 доллара за каждую операцию снятия средств и имеет более низкую процентную ставку. Здесь мы демонстрируем это поведение:
>>> ch = CheckingAccount('Tom')
>>> ch.interest # Более низкая процентная ставка для текущих счетов
0,01
>>> ch.deposit(20) # Депозиты такие же
20
>>> ch.withdraw(5) # снятие средств уменьшает баланс на дополнительную комиссию
14
CheckingAccount
является специализацией Account
. В терминологии ООП общий счёт выступает в качестве базового класса CheckingAccount
, а CheckingAccount
— в качестве дочернего класса (Account
) (термины «родительский класс» и «суперкласс» обычно эквивалентны «базовому классу», а «производный класс» обычно эквивалентен «дочернему классу»).
Дочерний класс наследует атрибуты базового класса, но может переопределять определённые атрибуты, включая определённые методы. Используя наследование, нам нужно только сосредоточиться на различиях между базовым классом и дочерним классом. Всё, что мы не указали в дочернем классе, будет автоматически предполагаться таким же, как и в базовом классе.
Наследование также играет важную роль в объектной метафоре, а не просто является практическим способом организации. Наследование означает выражение отношения «является» между классами, которое противоположно отношению «имеет». Текущий счёт является (is-a) особым типом счёта, поэтому разумно использовать CheckingAccount
, чтобы наследовать Account
. С другой стороны, банк имеет (has-a) список управляемых банковских счетов, поэтому ни то, ни другое не должно наследовать друг друга. Скорее, список объектов учётных записей должен естественным образом представлять атрибут экземпляров банковского счёта.
Мы указываем наследование, помещая базовый класс в круглые скобки после имени класса. Сначала мы предоставляем полную реализацию класса Account
, включая документацию для класса и методов.
>>> class Account(object):
"""Банковский счёт с неотрицательным балансом."""
interest = 0,02
def __init__(self, account_holder):
self.balance = 0
self.holder = account_holder
def deposit(self, amount):
"""Увеличить баланс счёта на сумму и вернуть новый баланс."""
self.balance = self.balance + amount
return self.balance
def withdraw(self, amount):
"""Уменьшить баланс счёта на сумму и вернуть новый баланс."""
if amount > self.balance:
return 'Недостаточные средства'
self.balance = self.balance - amount
return self.balance
Полная реализация CheckingAccount
:
>>> class CheckingAccount(Account):
"""Банковский счёт, который взимает плату за снятие средств."""
withdraw_charge = 1
interest = 0,01
def withdraw(self, amount):
return Account.withdraw(self, amount + self.withdraw_charge)
Здесь мы ввели атрибут класса withdraw_charge
, который специфичен для класса CheckingAccount
. Мы присваиваем более низкое значение атрибуту interest
. Мы также определяем новый метод withdraw
, чтобы переопределить поведение, определённое в объекте Account
. Нет никаких других операторов в классе statement
, и все остальные поведения наследуются от базового класса Account
.
>>> checking = CheckingAccount('Sam')
>>> checking.deposit(10)
10
>>> checking.withdraw(5)
4
>>> checking.interest
0,01
Выражение checking.deposit
является связанным методом для депозитов, определённым в классе Account
, и когда Python анализирует имя в выражении точки, экземпляр не имеет этого атрибута, он ищет его в классе. Фактически, при поиске имени в классе поведение поиска будет искать имя в каждом базовом классе в цепочке наследования исходного объекта. Мы можем рекурсивно определить этот процесс для поиска имени в классе:
В deposit
Python сначала ищет имя в экземпляре, затем в классе CheckingAccount
. Наконец, он находит его в Account
, где определён deposit
. Согласно нашим правилам оценки точечных выражений, поскольку deposit
найден в функции, определённой в классе checking
, выражение точки оценивается как связанный метод. Этот метод вызывается с параметром 10
, что приводит к вызову метода deposit
, связанного с self
, привязанным к объекту checking
, и amount
, привязанному к 10
.
Класс объекта всегда остаётся неизменным. Даже если deposit
был найден в классе Account
, deposit
вызывается для связанного метода с self
, привязанного к экземпляру CheckingAccount
, а не к экземпляру Account
.
Примечание переводчика: экземпляры
CheckingAccount
также являются экземплярамиAccount
, это утверждение неверно.
Вызов предков. Переопределённые атрибуты всё ещё можно получить через объекты класса. Например, мы можем вызвать метод Account
withdraw
с параметром, содержащим withdraw_charge
, чтобы реализовать метод withdraw
класса CheckingAccount
.
Обратите внимание, что мы вызываем self.withdraw_charge
вместо эквивалентного CheckingAccount.withdraw_charge
. Преимущество первого заключается в том, что дочерние классы могут переопределять стоимость снятия средств. Если это так, мы хотим, чтобы наша реализация withdraw
использовала новое значение, а не старое.
Python поддерживает концепцию наследования свойств от нескольких базовых классов для дочерних классов, что является особенностью языка, называемой множественным наследованием.
Предположим, мы унаследовали SavingsAccount
от Account
, взимая небольшую плату каждый раз, когда клиент вносит депозит.
>>> class SavingsAccount(Account):
deposit_charge = 2
def deposit(self, amount):
return Account.deposit(self, amount - self.deposit_charge)
Затем умный менеджер придумал AsSeenOnTVAccount
, который сочетает в себе лучшие характеристики CheckingAccount
и SavingsAccount
: сборы за снятие и внесение средств, а также более низкая процентная ставка. Он объединяет сберегательный счёт и текущий счёт в одном! «Если мы его построим», — пояснил менеджер, — «некоторые люди зарегистрируются и заплатят все эти сборы. Они даже получат от нас один доллар».
>>> class AsSeenOnTVAccount(CheckingAccount, SavingsAccount):
def __init__(self, account_holder):
self.holder = account_holder
self.balance = 1 # Бесплатный доллар! На самом деле, эта реализация завершена. Для вкладов и снятия средств используются соответствующие функции, определённые в `CheckingAccount` и `SavingsAccount`.
```py
>>> such_a_deal = AsSeenOnTVAccount("John")
>>> such_a_deal.balance
1
>>> such_a_deal.deposit(20) # комиссия 2 от SavingsAccount.deposit
19
>>> such_a_deal.withdraw(5) # комиссия 1 от CheckingAccount.withdraw
13
Как и ожидалось, однозначные ссылки будут правильно разрешены:
>>> such_a_deal.deposit_charge
2
>>> such_a_deal.withdraw_charge
1
Но что, если ссылка неоднозначна, например, ссылка на метод withdraw
, который определён в Account
и CheckingAccount
? Ниже приведён график наследования класса AsSeenOnTVAccount
. Каждая стрелка указывает от подкласса к базовому классу.
Для такого простого «ромба» Python анализирует имена слева направо, а затем вверх. В этом примере Python проверяет имена в следующем порядке, пока не найдёт атрибут с таким именем:
AsSeenOnTVAccount, CheckingAccount, SavingsAccount, Account, object
Не существует правильного решения проблемы порядка наследования, поскольку мы можем дать одному производному классу приоритет над другим. Однако любой язык программирования, поддерживающий множественное наследование, должен всегда выбирать один и тот же порядок, чтобы пользователи языка могли предсказать поведение программы.
Расширенное чтение. Python использует рекурсивный алгоритм, называемый C3 Method Resolution Ordering, для разрешения имён. Метод mro
используется для запроса порядка разрешения методов для любого класса.
>>> [c.__name__ for c in AsSeenOnTVAccount.mro()]
['AsSeenOnTVAccount', 'CheckingAccount', 'SavingsAccount', 'Account', 'object']
Этот алгоритм, используемый для запроса порядка разрешения методов, не является темой этого курса, но оригинальный автор Python использовал статью ссылка на оригинальную статью, чтобы описать его.
Система объектов Python разработана для упрощения абстракции данных и передачи сообщений. Специализированная грамматика классов, методов, наследования и точечных операторов позволяет нам создавать метафоры объектов в программе, что может улучшить нашу способность организовывать большие программы.
В частности, мы хотим, чтобы наша система объектов способствовала разделению внимания на разных уровнях. Каждый объект в программе инкапсулирует и управляет частью состояния программы, каждое утверждение класса определяет некоторые функции, которые реализуют часть общей логики программы. Абстрактные границы обеспечивают разделение между различными уровнями больших программ.
Объектно-ориентированное программирование хорошо подходит для моделирования систем, состоящих из взаимодействующих и отдельных частей. Например, взаимодействие различных пользователей в социальных сетях, взаимодействие различных ролей в играх и взаимодействие различных фигур в физических симуляциях. При представлении таких систем объекты в программах обычно естественным образом отображаются на объекты моделируемой системы, а классы используются для представления их типов и отношений.
С другой стороны, классы могут не обеспечивать наилучший механизм для реализации конкретной абстракции. Функциональные абстракции предоставляют более естественную метафору для выражения отношений ввода и вывода. Человеку не следует заставлять себя помещать каждую мельчайшую логику в программу в класс, особенно когда определение независимых функций для работы с данными становится естественным. Функции также способствуют разделению внимания.
Подобно Python, мультипарадигмальные языки позволяют программистам сопоставлять подходящие парадигмы с соответствующими проблемами. Определение того, когда вводить новые классы, а не новые функции, является важным приёмом проектирования в программной инженерии, требующим тщательного рассмотрения, для упрощения программ или их модульности.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )