Изучение Python: от нуля до мастера. Часть 2
Nuances of programmingПеревод статьи TK: Learning Python: From Zero to Hero
Предудышие части: Часть 1
Содержание:
- Объекты и классы
- Атрибуты как данные объектов
- Методы как поведение объектов
- Использование Python получателя и установщика, а также свойство @property
- Инкапсуляция: сокрытие информации
- Наследование: поведение и характеристики
Классы и объекты
Немного теории:
Объекты это представление предметов из реальной жизни, например машин, собак, велосипедов. У объектов есть две основных характеристики: данные и поведение.
У машин есть данные, например количество колёс или сидячих мест. Также у них есть поведение: они могут разгоняться, останавливаться, показывать оставшееся количество топлива и другое.
В объектно-ориентированном программировании мы идентифицируем данные как атрибуты, а поведение как методы. Ещё раз:
Данные → Атрибуты; Поведение → Методы
Класс это как чертёж, из которого создаются уникальные объекты. В реальном мире есть множество объектов с похожими характеристиками. Например, машины. Все они имеют какую-то марку или модель(точно так же как и двигатель, колёса, двери и так далее). Каждая машина была построена из похожего набора чертежей и деталей.
Активировать объектно-ориентированный режим Python
Python, как объектно-ориентированный язык программирования, имеет следующие концепции: классы и объекты.
Класс - это чертёж, модель для его объектов.
Ещё раз, класс - это просто модель, или способ для определения атрибутов и поведения(о которых мы говорили в теории выше). Например, класс машины будет иметь свои собственные атрибуты, которые определяют какие объекты являются машинами. Количество колёс, тип топлива, количество сидячих мест и максимальная скорость - всё это является атрибутами машин.
Держа это в уме, давайте посмотрим на синтаксис Python для классов:
class Vehicle: pass
Мы определяем классы class-блоком и на этом всё. Легко, не так ли?
Объекты это экземпляры классов. Мы создаём экземпляр тогда, когда даём классу имя.
car = Vehicle() print(car) # <__main__.Vehicle instance at 0x7fb1de6c2638>
Здесь car это объект(экземпляр) класса Vehicle.
Помните, что наш класс машин имеет следующие атрибуты: количество колёс, тип топлива, количество сидячих мест и максимальная скорость. Мы задаём все атрибуты когда создаём объект машины. В коде ниже, мы описываем наш класс таким образом, чтобы он принимал данные в тот момент, когда его инициализируют:
class Vehicle: def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity): self.number_of_wheels = number_of_wheels self.type_of_tank = type_of_tank self.seating_capacity = seating_capacity self.maximum_velocity = maximum_velocity
Мы используем метод init. Мы называем этот конструктор-методом. Таким образом, когда мы создаём объект машины, мы можем ещё и определить его атрибуты. Представьте, что нам нравится модель Tesla S и мы хотим создать её как наш объект. У неё есть четыре колеса, она работает на электрической энергии, есть пять сидячих мест и максимальная скорость составляет 250 км/ч. Давайте создадим такой объект:
tesla_model_s = Vehicle(4, 'electric', 5, 250)
Четыре колеса + электрический "вид топлива" + пять сидений + 250 км/ч как максимальная скорость.
Все атрибуты заданы. Но как нам теперь получить доступ к значениям этих атрибутов? Мы посылаем объекту сообщению с запросом атрибутов. Мы называем это метод. Это поведение объекта. Давайте воплотим эту идею:
class Vehicle: def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity): self.number_of_wheels = number_of_wheels self.type_of_tank = type_of_tank self.seating_capacity = seating_capacity self.maximum_velocity = maximum_velocity def number_of_wheels(self): return self.number_of_wheels def set_number_of_wheels(self, number): self.number_of_wheels = number
Это реализация двух методов: number_of_wheels и set_number_of_wheels. Мы называем их получатель и установщик. Потому что получатель принимает значение атрибута, а установщик задаёт ему новое значение.
В Python мы можем реализовать это используя @property для описания получателя и установщика. Посмотрим на это в коде:
class Vehicle: def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity): self.number_of_wheels = number_of_wheels self.type_of_tank = type_of_tank self.seating_capacity = seating_capacity self.maximum_velocity = maximum_velocity @property def number_of_wheels(self): return self.number_of_wheels @number_of_wheels.setter def number_of_wheels(self, number): self.number_of_wheels = number
Далее мы можем использовать методы как атрибуты:
tesla_model_s = Vehicle(4, 'electric', 5, 250) print(tesla_model_s.number_of_wheels) # 4 tesla_model_s.number_of_wheels = 2 # setting number of wheels to 2 print(tesla_model_s.number_of_wheels) # 2
Это немного отличается от описания методов. Эти методы работают как атрибуты. Например, когда мы задаём количество колёс, то не применяем два как параметр, а устанавливаем значение двойки для number_of_wheels. Это один из способ написать получать и установщик в Python.
Ещё мы можем использовать методы для других вещей, например создать метод "make_noise"(пошуметь).
Давайте посмотрим:
class Vehicle: def __init__(self, number_of_wheels, type_of_tank, seating_capacity, maximum_velocity): self.number_of_wheels = number_of_wheels self.type_of_tank = type_of_tank self.seating_capacity = seating_capacity self.maximum_velocity = maximum_velocity def make_noise(self): print('VRUUUUUUUM')
Когда мы вызовем этот метод, он просто вернётся строку "VRRRRUUUUM".
tesla_model_s = Vehicle(4, 'electric', 5, 250) tesla_model_s.make_noise() # VRUUUUUUUM
Инкапсуляция: сокрытие информации
Инкапсуляция - это механизм, который ограничивает свободный доступ к данным и методам объекта. Но в то же время, это упрощает доступ к данным(методам объекта).
"Инкапсуляция может использоваться для сокрытия данных и функций. Под определением инкапсуляции имеется ввиду то, что внутреннее представление объекта сокрыто от просмотра вне определения объекта." - Википедия
Вся внутренняя реализация объекта недоступна извне. Только сам объект может взаимодействовать со своими внутренними данными.
Для начала нам нужно понять как работают публичные и не-публичные переменные и методы.
Публичные экземпляры данных
Для Python-класса мы можем инициализировать публичный экземпляр переменной внутри нашего конструктор-метода. Давайте посмотрим:
Внутри конструктор-метода:
class Person: def __init__(self, first_name): self.first_name = first_name
Здесь мы применяем значение first_name как аргумент для публичного экземпляра переменной.
tk = Person('TK') print(tk.first_name) # => TK
Внутри класса:
class Person: first_name = 'TK'
Здесь нам не нужно применять first_name как аргумент, а все экземпляры объектов будут иметь заранее прописанный атрибут класса. В нашем случае first_name будет заполнено строкой "TK".
tk = Person() print(tk.first_name) # => TK
Круто. Теперь мы узнали как можно использовать публичные экземпляры переменных и атрибуты класса. Ещё одна интересная особенность публичных данных в том, что мы можем управлять значениями переменных. Что я имею в виду под этим? Наш объект может управлять значением переменной: получать и устанавливать значения переменной.
Помня о классе person зададим значение для переменной first_name
tk = Person('TK') tk.first_name = 'Kaio' print(tk.first_name) # => Kaio
Вот и всё. Мы просто задали другое значение(kaio) экземпляру переменной first_name и оно обновилось. И всё на этом. Поскольку это публичная переменная, то мы можем делать это так.
Не-публичные экземпляры данных
Мы не используем термин "приватный", поскольку в Python нет действительно приватных атрибутов(если только не задаваться тяжёлой целью создать их). - PEP 8
Точно так же, как с публичными экземплярами переменных, мы можем объявить и не-публичные экземпляры. Оба внутри конструктор-метода или внутри класса. Синтаксис несколько отличается: не-публичные экземпляры переменных должны начинаться с нижнего подчёркивания("_") перед именем переменной.
"'Приватный' экземпляр данных, доступ к которому открыт только изнутри, не существует в Python. Тем не менее, есть условность, которая выполняется в большей части Python-кода: имена с префиксом "_"(например, "_spam") должны обрабатываться как не-публичные части API(будь то функция, метод или какие-то данные)" - Python Software Foundation
Вот пример:
class Person: def __init__(self, first_name, email): self.first_name = first_name self._email = email
Увидели переменную email? Вот так мы описываем не-публичную переменную:
tk = Person('TK', 'tk@mail.com') print(tk._email) # tk@mail.com
Мы имеем доступ и может обновить это. Не-публичные переменные это условность, при которой эти переменные обрабатываются как не-публичная часть API.
Таким образом мы создаём метод, который позволяет нам вносить изменения внутри определения класса. Давайте реализуем два метода(email и update_email), чтобы понять это:
class Person: def __init__(self, first_name, email): self.first_name = first_name self._email = email def update_email(self, new_email): self._email = new_email def email(self): return self._email
Теперь мы имеем доступ и можем обновить значения не-публичных переменных используя эти методы. Посмотрим:
tk = Person('TK', 'tk@mail.com') print(tk.email()) # => tk@mail.com tk._email = 'new_tk@mail.com' print(tk.email()) # => tk@mail.com tk.update_email('new_tk@mail.com') print(tk.email()) # => new_tk@mail.com
- Мы объявили новый объект, в котором first_name заполнено строкой "TK" и email заполнено строкой "tk@mail.com"
- Выводим email получая доступ к не-публичной переменной через метод
- Пробуем задать новый email извне нашего класса
- Нам нужно обращаться в не-публичной переменной как к не-публичной части API
- Обновляем нашу не-публичную переменную с нашим методом экземпляра
- Успех! Мы можем обновить это внутри нашего метода с помощью метода-помощника
Публичные методы
Публичные методы мы тоже можем использовать вне класса:
class Person: def __init__(self, first_name, age): self.first_name = first_name self._age = age def show_age(self): return self._age
Давайте протестируем это:
tk = Person('TK', 25) print(tk.show_age()) # => 25
Прекрасно. Мы можем использовать это без каких-либо проблем.
Не-публичные методы
Но не-публичные методы мы не можем использовать так просто. Давайте реализуем тот же класс Person, но теперь метод show_age станет не-публичным с нижним подчёркиванием.
class Person: def __init__(self, first_name, age): self.first_name = first_name self._age = age def _show_age(self): return self._age
А теперь попробуем вызвать этот не-публичный метод с помощью нашего объекта:
tk = Person('TK', 25) print(tk._show_age()) # => 25
У нас есть доступ и мы можем обновить это. Не-публичные методы это просто условность, при которых они обрабатываются как не-публичная часть API.
Здесь пример того, как мы можем использовать это:
class Person: def __init__(self, first_name, age): self.first_name = first_name self._age = age def show_age(self): return self._get_age() def _get_age(self): return self._age tk = Person('TK', 25) print(tk.show_age()) # => 25
Здесь у нас есть не-публичный метод _get_age и публичный метод show_age. show_age может использоваться нашим объектом(вне класса), в то время как _get_age используется только внутри определения нашего класса(внутри метода show_age). Но опять же, в виду условностей.
Вывод об инкапсуляции
С помощью инкапсуляции мы можем убедиться, что наша внутренняя реализация объекта сокрыта от внешних манипуляций.
Наследование: поведение и характеристики
Разные объекты могут иметь некоторую схожесть, обладать поведением и характеристиками.
Например, я унаследовал какие-то характеристики и поведение от своего отца. Я получил его глаза и волосы в качестве своих характеристик, а его нетерпеливость и интровертность в качестве своего поведения.
В объектно-ориентированном программировании классы могут наследовать простые характеристики(данные) и поведение(методы) от других классов.
Давайте посмотрим другой пример и реализуем его в Python.
Представьте машину. Количество колёс, сидячих мест и максимальная скорость - всё это атрибуты машины. Мы можем сказать, что класс электромашины наследует эти схожие характеристики от обычного класса машины.
class Car: def __init__(self, number_of_wheels, seating_capacity, maximum_velocity): self.number_of_wheels = number_of_wheels self.seating_capacity = seating_capacity self.maximum_velocity = maximum_velocity
Наш класс машины реализует:
my_car = Car(4, 5, 250) print(my_car.number_of_wheels) print(my_car.seating_capacity) print(my_car.maximum_velocity)
Один раз реализовав, мы можем использовать все созданные экземпляры переменных. Неплохо.
В Python, мы применяем класс-родитель к нашему классу-наследнику как параметр. Класс электромашины может наследоваться от класса машины.
class ElectricCar(Car): def __init__(self, number_of_wheels, seating_capacity, maximum_velocity): Car.__init__(self, number_of_wheels, seating_capacity, maximum_velocity)
Вот так просто. Нам не нужно реализовывать какой-либо другой метод, потому что этот класс уже имеет его(унаследовав от класса машины). Давайте докажем это:
my_electric_car = ElectricCar(4, 5, 250) print(my_electric_car.number_of_wheels) # => 4 print(my_electric_car.seating_capacity) # => 5 print(my_electric_car.maximum_velocity) # => 250
Прекрасно.
На этом всё!
Мы изучили множество вещей о базах Python:
- Как работают переменные Python
- Как работают блоки условий
- Как работают циклы(while и for)
- Как использовать списки: коллекции | массивы
- Коллекция-словарь в виде ключ-значение
- Как мы можем проводить итерации через эту структуры данных
- Объекты и классы
- Атрибуты как данные объектов
- Методы как поведение объектов
- Использование Python получателя и установщика, а также свойство @property
- Инкапсуляция: сокрытие информации
- Наследование: поведение и характеристики
Поздравляем! Вы освоили эту насыщенную и необходимую часть языка Python.
Статью перевёл Дмитрий Хирш