Изучение Python: от нуля до мастера. Часть 2

Изучение 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
  1. Мы объявили новый объект, в котором first_name заполнено строкой "TK" и email заполнено строкой "tk@mail.com"
  2. Выводим email получая доступ к не-публичной переменной через метод
  3. Пробуем задать новый email извне нашего класса
  4. Нам нужно обращаться в не-публичной переменной как к не-публичной части API
  5. Обновляем нашу не-публичную переменную с нашим методом экземпляра
  6. Успех! Мы можем обновить это внутри нашего метода с помощью метода-помощника

Публичные методы

Публичные методы мы тоже можем использовать вне класса:

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.

Статью перевёл Дмитрий Хирш

Nuances of programming на Дзене!