Ооп питон 3: Объектно-ориентированное программирование. Классы и объекты

Содержание

Объектно-ориентированное программирование. Классы и объекты

Сегодня мы поговорим об объектно-ориентированном программировании и о его применении в python.

Объектно-ориентированное программирование (ООП) — парадигма программирования, в которой основными концепциями являются понятия объектов и классов.

Класс — тип, описывающий устройство объектов. Объект — это экземпляр класса. Класс можно сравнить с чертежом, по которому создаются объекты.

Python соответствует принципам объектно-ориентированного программирования. В python всё является объектами — и строки, и списки, и словари, и всё остальное.

Но возможности ООП в python этим не ограничены. Программист может написать свой тип данных (класс), определить в нём свои методы.

Это не является обязательным — мы можем пользоваться только встроенными объектами. Однако ООП полезно при долгосрочной разработке программы несколькими людьми, так как упрощает понимание кода.

Приступим теперь собственно к написанию своих классов на python. Попробуем определить собственный класс:

>>> # Пример простейшего класса, который ничего не делает
... class A:
...     pass

Теперь мы можем создать несколько экземпляров этого класса:

>>> a = A()
>>> b = A()
>>> a.arg = 1  # у экземпляра a появился атрибут arg, равный 1
>>> b.arg = 2  # а у экземпляра b - атрибут arg, равный 2
>>> print(a.arg)
1
>>> print(b.arg)
2
>>> c = A()
>>> print(c.arg)  # а у этого экземпляра нет arg
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'arg'

Классу возможно задать собственные методы:

>>> class A:
...     def g(self): # self - обязательный аргумент, содержащий в себе экземпляр
...                  # класса, передающийся при вызове метода,
...                  # поэтому этот аргумент должен присутствовать
...                  # во всех методах класса.
...         return 'hello world'
...
>>> a = A()
>>> a.g()
'hello world'

И напоследок еще один пример:

>>> class B:
...     arg = 'Python' # Все экземпляры этого класса будут иметь атрибут arg,
...                    # равный "Python"
...                    # Но впоследствии мы его можем изменить
...     def g(self):
...         return self.arg
...
>>> b = B()
>>> b.g()
'Python'
>>> B.g(b)
'Python'
>>> b.arg = 'spam'
>>> b.g()
'spam'

Объектно-ориентированное Программирование в Python

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

Содержание

Представьте сценарий, где вам нужно разработать болид Формулы-1 используя подход объектно-ориентированного программирования. Первое, что вам нужно сделать — это определить реальные объекты в настоящей гонке Формула-1. Какие аспекты в Формуле-1 обладают определенными характеристиками и могут выполнять ту или иную функцию?

Есть вопросы по Python?

На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!

Telegram Чат & Канал

Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!

Паблик VK

Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!

Один из очевидных ответов на этот вопрос — гоночный болид. Условный болид может обладать такими характеристиками как:

  • мощность двигателя;
  • марка;
  • модель;
  • производитель, и т. д.

Соответственно, болид можно запустить, остановить, ускорить, и так далее. Гонщик может быть еще одним объектом в Формуле-1. Гонщик имеет национальность, возраст, пол, и так далее, кроме этого, он обладает таким функционалом, как управление болидом, рулевое управление, переключение передач.

Как и в этом примере, в объектно-ориентированном программировании мы создадим объекты, которые будут соответствовать реальным аспектам.

Стоит обратить внимание на то, что объектно-ориентированное программирование — не зависящая от языка программирования концепция. Это общая концепция программирования и большинство современных языков, такие как Java, C#, C++ и Python поддерживают объектно-ориентированное программирование.

В этой статье мы разберем подробную инструкцию объектно-ориентированного программирования в Python, но перед этим, рассмотрим некоторые преимущества и недостатки объектно-ориентированного программирования.

Преимущества и недостатки ООП Python

Рассмотрим несколько основных преимуществ объектно-ориентированного программирования:

  1. Объектно-ориентированное программирование подразумевает повторное использование. Компьютерная программа написанная в форме объектов и классов может быть использована снова в других проектах без повторения кода;
  2. Использование модулярного подхода в объектно-ориентированном программировании позволяет получить читаемый и гибкий код;
  3. В объектно-ориентированном программировании каждый класс имеет определенную задачу. Если ошибка возникнет в одной части кода, вы можете исправить ее локально, без необходимости вмешиваться в другие части кода;
  4. Инкапсуляция данных (которую мы рассмотрим дальше в статье) вносит дополнительный уровень безопасности в разрабатываемую программу с использованием объектно-ориентированного подхода;

Хотя объектно-ориентированное программирование обладает рядом преимуществ, оно также содержит определенные недостатки, некоторые из них находятся в списке ниже:

  1. Для создания объектов необходимо иметь подробное представление о разрабатываемом программном обеспечении;
  2. Не каждый аспект программного обеспечения является лучшим решением для реализации в качестве объекта. Для новичков может быть тяжело прочертить линию в золотой середине;
  3. С тем, как вы вносите все новые и новые классы в код, размер и сложность программы растет в геометрической прогрессии;

В следующем разделе мы рассмотрим ряд самых важных концепций объектно-ориентированного программирования.

Как и следует из названия, объектно-ориентированное программирование — это речь об объектах. Однако, перед тем как создать объект, нам нужно определить его класс.

Класс

Класс в объектно-ориентированном программировании выступает в роли чертежа для объекта. Класс можно рассматривать как карту дома. Вы можете понять, как выглядит дом, просто взглянув на его карту.

Cам по себе класс не представляет ничего. К примеру, нельзя сказать что карта является домом, она только объясняет как настоящий дом должен выглядеть.

Отношение между классом и объектом можно представить более наглядно, взглянув на отношение между машиной и Audi. Да, Audi – это машина. Однако, нет такой вещи, как просто машина. Машина — это абстрактная концепция, которую также реализуют в Toyota, Honda, Ferrari, и других компаниях.

Ключевое слово class используется для создания класса в Python. Название класса следует за ключом class, за которым следует двоеточие. Тело класса начинается с новой строки, с отступом на одну вкладку влево.

Давайте рассмотрим, как мы можем создать самый простой класс в Python. Взглянем на следующий код:

# Создаем класс Car class Car: # создаем атрибуты класса name = «c200» make = «mercedez» model = 2008 # создаем методы класса def start(self): print («Заводим двигатель») def stop(self): print («Отключаем двигатель»)

# Создаем класс Car

class Car:

 

    # создаем атрибуты класса

    name = «c200»

    make = «mercedez»

    model = 2008

 

    # создаем методы класса

    def start(self):

        print («Заводим двигатель»)

 

    def stop(self):

        print («Отключаем двигатель»)

В примере выше мы создали класс под названием Car с тремя атрибутами: имя name, марка make и модель model. Наш класс также содержит два метода: start() и stop().

Объекты

Раннее мы поняли, что класс предоставляет чертеж объекта. Однако, чтобы на самом деле использовать объекты и методы класса, вам нужно создать объект из этого класса. Существует несколько методов и атрибутов класса, которые можно использовать вне объекта, мы рассмотрим их в следующем разделе.

Сейчас просто запомните, что по умолчанию, нам нужно создать объект класса перед тем, как мы сможем начать использовать его методы и атрибуты.

Объект также называется экземпляром. Тем не менее, процесс создания объекта класса называется инициализация. В Python, чтобы создать объект класса, нам просто нужно вписать название класса, с последующими открывающимися и закрывающимися скобками.

Давайте создадим объект класса Car, который мы создали в предыдущем разделе.

# Создаем объект класса Car под названием car_a car_a = Car() # Создаем объект класса Car под названием car_b car_b = Car()

# Создаем объект класса Car под названием car_a

car_a = Car()

 

# Создаем объект класса Car под названием car_b

car_b = Car()

В этом скрипте мы создали два объекта класса Car: car_a и car_b. Чтобы узнать тип созданных нами объектов, мы можем использовать метод type и передать ему названия наших объектов. Выполните следующий код:

В выдаче вы увидите:

Это говорит нам о том, что тип объекта car_b – класс Car.

На данный момент мы создали наш класс и соответствующие ему объекты. Теперь настало время получить доступ к атрибутам класса и вызвать метод класса при помощи объекта класса. Чтобы сделать это, вам нужно только вписать имя объекта, за которым следует оператор точка . и название атрибута или метода, к которому вы хотите получить доступ или вызов, соответственно. Давайте рассмотрим следующий пример:

В этом скрипте мы вызываем метод start() через объект car_b. Выдача будет выглядеть следующим образом:

Заводим двигатель

Заводим двигатель

Аналогично, вы можете получить доступ к атрибуту, пользуясь следующим синтаксисом:

В выдаче вы увидите значение атрибута модели, как показано ниже:

Атрибуты класса

В предыдущей секции мы разобрались, как

создавать объекты класса и как мы можем использовать эти объекты для получения доступа к атрибутам класса

В Python, каждый объект содержит определенные атрибуты по умолчанию и методы в дополнение к определенным пользователем атрибутами. Чтобы посмотреть на все атрибуты и методы объекта, используйте встроенную функцию под названием dir(). Попробуем взглянуть на все атрибуты объекта car_b, который мы создали в предыдущем разделе. Выполните следующий скрипт:

В выдаче вы увидите следующие атрибуты:

[‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘make’, ‘model’, ‘name’, ‘start’, ‘stop’]

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

[‘__class__’,

‘__delattr__’,

‘__dict__’,

‘__dir__’,

‘__doc__’,

‘__eq__’,

‘__format__’,

‘__ge__’,

‘__getattribute__’,

‘__gt__’,

‘__hash__’,

‘__init__’,

‘__init_subclass__’,

‘__le__’,

‘__lt__’,

‘__module__’,

‘__ne__’,

‘__new__’,

‘__reduce__’,

‘__reduce_ex__’,

‘__repr__’,

‘__setattr__’,

‘__sizeof__’,

‘__str__’,

‘__subclasshook__’,

‘__weakref__’,

‘make’,

‘model’,

‘name’,

‘start’,

‘stop’]

Эта встроенная функция очень полезна при изучении атрибутов

и функций объекта, особенно при использовании через REPL.

Атрибуты класса против атрибутов экземпляров

Атрибуты могут быть наглядно отнесены к двум типам:

  • атрибуты класса
  • атрибуты экземпляров

Атрибуты класса делятся среди всех объектов класса, в то время как атрибуты экземпляров являются собственностью экземпляра.
Помните, что экземпляр — это просто альтернативное название объекта.

Атрибуты экземпляра объявляются внутри любого метода, в то время как атрибуты класса объявляются вне любого метода.

Следующий пример прояснит эту разницу:

class Car: # создаем атрибуты класса car_count = 0 # создаем методы класса def start(self, name, make, model): print(«Двигатель заведен») self.name = name self.make = make self.model = model Car.car_count += 1

class Car:

 

    # создаем атрибуты класса

    car_count = 0

 

    # создаем методы класса

    def start(self, name, make, model):

        print(«Двигатель заведен»)

        self.name = name

        self.make = make

        self.model = model

        Car.car_count += 1

В указанном выше скрипте мы создаем класс Car с одним атрибутом класса под названием car_count и три атрибута экземпляра под названием name, make и model. Класс содержит один метод start(), который содержит наши три атрибута экземпляров. Значения атрибутов экземпляров переданы в качестве аргументов методу start(). Внутри метода start, атрибут car_count увеличен на один.

Стоит упомянуть, что внутри метода, атрибуты экземпляра ссылаются при помощи ключевого слова self, в то время как атрибуты класса ссылаются при помощи названия класса.

Давайте создадим объект класса

Car и вызовем метод start().

car_a = Car() car_a.start(«Corrola», «Toyota», 2015) print(car_a.name) print(car_a.car_count)

car_a = Car()  

car_a.start(«Corrola», «Toyota», 2015)  

print(car_a.name)  

print(car_a.car_count)

В скрипте выше мы вывели название атрибута экземпляра и атрибута класса car_count. В выдаче вы увидите, что атрибут car_count будет иметь значение 1, как показано ниже:

Двигатель заведен Corrola 1

Двигатель заведен

Corrola  

1

Теперь создадим еще один объект класса Car и вызываем метод start().

car_b = Car() car_b.start(«City», «Honda», 2013) print(car_b.name) print(car_b.car_count)

car_b = Car()  

car_b.start(«City», «Honda», 2013)  

print(car_b.name)  

print(car_b.car_count)

Сейчас если вы выведите значение атрибута car_count, вы увидите 2 в выдаче. Это связано с тем, что атрибут car_count является атрибутом класса и таким образом он разделяется между экземплярами. Объект car_a увеличил свое значение до 1, в то время как car_b увеличил свое значение еще раз, так что итоговое значение равняется 2. Выдача выглядит следующим образом:

Двигатель заведен City 2

Двигатель заведен

City  

2

Методы

Как мы выяснили ранее, в объектно-ориентированном программировании, методы используются для реализации функционалов объекта. В предыдущем разделе мы создали методы start() и stop() для класса Car. До этих пор, мы использовали объекты класса для вызова методов. Однако, есть тип методов, который может быть вызван напрямую при помощи имени класса. Такой метод называется статичным методом.

Статичные методы

Для объявления статического метода, вам нужно указать дескриптор @staticmethod перед названием метода, как показано ниже:

class Car: @staticmethod def get_class_details(): print («Это класс Car») Car.get_class_details()

class Car:

 

    @staticmethod

    def get_class_details():

        print («Это класс Car»)

 

Car.get_class_details()

В коде выше мы создали класс Car с одним статичным методом get_class_details(). Давайте вызовем этот метод, используя название класса.

Вы можете видеть что нам не нужно создавать экземпляр класса Car для вызова метода get_class_details(), вместо этого мы просто использовали название класса. Стоит упомянуть, что статические методы могут иметь доступ только к атрибутам класса в Python, вы не сможете обратиться к методам через self.

Возврат множественных значений из метода

Одна из лучших особенностей языка Python заключается в том, что методы класса могут возвращать множественные значения. Взгляните на следующий пример:

class Square: @staticmethod def get_squares(a, b): return a*a, b*b print(Square.get_squares(3, 5))

class Square:

 

    @staticmethod

    def get_squares(a, b):

        return a*a, b*b

 

print(Square.get_squares(3, 5))

В скрипте выше мы создали класс под названием Square со статичным методом get_squares(). Метод принимает два параметра. Он умножает каждый параметр на себя и возвращает оба результата при помощи оператора return. В выдаче указанного выше скрипта вы увидите квадраты 3 и 5.

Метод str

До этого момента мы выводили атрибуты при помощи метода print(). Посмотрим, что случится, если мы выведем объект класса.

Для этого нам нужно создать простой класс Car с одним методом и попытаться вывести объект класса в консоль. Выполним следующий скрипт:

class Car: # создание методов класса def start(self): print («Двигатель заведен») car_a = Car() print(car_a)

class Car:

 

    # создание методов класса

    def start(self):

        print («Двигатель заведен»)

 

car_a = Car()  

print(car_a)

В скрипте выше мы создали объект car_a класса Car и вывели его значение на экран. По сути мы относимся к объекту car_a как к строке. Выдача выглядит следующим образом:

<__main__.Car object at 0x000001CCCF4335C0>

<__main__.Car object at 0x000001CCCF4335C0>

Выдача показывает локацию памяти, где хранится наш объект. Каждый объект Python по умолчанию содержит метод __str__ . Когда вы используете объект в качестве строки, вызывается метод __str__ , который по умолчанию выводит локацию памяти объекта. Однако, вы также можете предоставить собственное определение метода __str__ . Например, как в следующем примере:

# создание класса Car class Car: # создание методов класса def __str__(self): return «Car class Object» def start(self): print («Двигатель заведен») car_a = Car() print(car_a)

# создание класса Car

class Car:

 

    # создание методов класса

    def __str__(self):

        return «Car class Object»

 

    def start(self):

        print («Двигатель заведен»)

 

car_a = Car()  

print(car_a)

В скрипте выше, мы переопределили метод __str__ , предоставив наше собственное определение метода. Теперь, если вы выведите объект car_a, вы увидите сообщение «Car class Object» в консоли. Это сообщение, которое мы внесли в наш пользовательский метод __str__ .

Использование этого метода позволяет вам создавать пользовательские и более осмысленные описания, когда объект выводится. Вы можете даже отобразить кое-какие данные внутри класса, такие как название класса Car.

Конструкторы

Конструктор — это специальный метод, который вызывается по умолчанию когда вы создаете объект класса.

Для создания конструктора вам нужно создать метод с ключевым словом __init__. Взгляните на следующий пример:

class Car: # создание атрибутов класса car_count = 0 # создание методов класса def __init__(self): Car.car_count +=1 print(Car.car_count)

class Car:

 

    # создание атрибутов класса

    car_count = 0

 

    # создание методов класса

    def __init__(self):

        Car.car_count +=1

        print(Car.car_count)

В скрипте выше мы создали класс Car с одним атрибутом класса car_count. Класс содержит конструктор, который увеличивает значение car_count и выводит итоговое значение на экран.

Теперь, когда объект класса Car будет создан, конструктор также будет вызван, значение car_count увеличится и отобразится на экране. Создадим простой объект и посмотрим, что выйдет:

car_a = Car() car_b = Car() car_c = Car()

car_a = Car()  

car_b = Car()  

car_c = Car()

В выдаче вы увидите выведенное значение 1, 2 и 3, поскольку для каждого объекта значение переменной car_count увеличивается и отображается на экране.

За исключением названия, конструктор может использоваться как обычный метод. Вы можете передавать и получать значения из конструктора. Он обычно используется таким образом, когда вам нужно инициализировать значения атрибута при создании экземпляра класса.

Локальные переменные против глобальных

Мы знаем, что есть два типа атрибутов Python: атрибуты экземпляра и атрибуты класса.

Атрибуты класса также называются переменными. В зависимости от области видимости, переменные также могут относиться к двум типам: локальные переменные и глобальные переменные.

Локальные переменные

Локальная переменная в классе — это переменная, доступ к которой возможен только внутри блока кода, в котором она определена. Например, если вы определите переменную внутри метода, к нему не удастся получить доступ откуда-либо вне метода. Посмотрим на следующий скрипт:

# создаем класс Car class Car: def start(self): message = «Двигатель заведен» return message

# создаем класс Car

class Car:  

    def start(self):

        message = «Двигатель заведен»

        return message

В скрипте выше мы создали локальную переменную message внутри метода start() класса Car. Теперь создадим объект класса Car и попытаемся получить доступ к локальной переменной message, как показано ниже:

car_a = Car() print(car_a.message)

car_a = Car()  

print(car_a.message)

Скрипт выше приводит к следующей ошибке AttributeError:

AttributeError: ‘Car’ object has no attribute ‘message’

AttributeError: ‘Car’ object has no attribute ‘message’

Это связано с тем, что мы не можем получить доступ к локальной переменной вне блока, где эта локальная переменная была определена.

Глобальная переменная

Глобальная переменная определяется вне любого блока, то есть метода, операторов-if, и тому подобное. Доступ к глобальной переменной может быть получен где угодно в классе. Рассмотрим следующий пример.

# создаем класс Car class Car: message1 = «Двигатель заведен» def start(self): message2 = «Автомобиль заведен» return message2 car_a = Car() print(car_a.message1)

# создаем класс Car

class Car:  

    message1 = «Двигатель заведен»

 

    def start(self):

        message2 = «Автомобиль заведен»

        return message2

 

car_a = Car()  

print(car_a.message1)

В этом скрипте мы создали глобальную переменную message1 и вывели ее значение на экран. В выдаче вы увидите значение переменной message1, выведенной без ошибки.

Обратите внимание на то, что существует разница между атрибутами класса и экземпляра, а также между глобальными и локальными переменными.

Атрибуты экземпляра и класса отличаются способом получения доступа к ним. Другими словами, речь идет об использовании названия класса и использовании названия экземпляра. С другой стороны, глобальные и локальные переменные отличаются своими областями видимости, другими словами, местами, где к ним может быть получен доступ.

Доступ к локальной переменной может быть получен только внутри метода. Хотя в этой статье локальные переменные и атрибуты экземпляров определяются внутри метода, локальные переменные определяются собственным ключевым словом.

Модификаторы доступа

Модификаторы доступа в Python используются для модификации области видимости переменных по умолчанию. Есть три типа модификаторов доступов в Python ООП:

  1. публичный — public;
  2. приватный — private;
  3. защищенный — protected.

Доступ к переменным с модификаторами публичного доступа открыт из любой точки вне класса, доступ к приватным переменным открыт только внутри класса, и в случае с защищенными переменными, доступ открыт только внутри того же пакета.

Для создания приватной переменной, вам нужно проставить префикс двойного подчеркивание __ с названием переменной.

Для создания защищенной переменной, вам нужно проставить префикс из одного нижнего подчеркивания _ с названием переменной. Для публичных переменных, вам не нужно проставлять префиксы вообще.

Давайте взглянем на публичные, приватные и защищенные переменные в действии. Выполните следующий скрипт:

class Car: def __init__(self): print («Двигатель заведен») self.name = «corolla» self.__make = «toyota» self._model = 1999

class Car:  

    def __init__(self):

        print («Двигатель заведен»)

        self.name = «corolla»

        self.__make = «toyota»

        self._model = 1999

Здесь мы создали простой класс Car с конструктором и тремя переменными: name, make, и model (название, марка и модель). Переменная name является публичной, в то время как переменные make и model являются приватными и защищенными, соответственно.

Давайте создадим объект класса Car и попытаемся получить доступ к переменной name. Выполним следующий скрипт:

car_a = Car() print(car_a.name)

car_a = Car()  

print(car_a.name)

Так как name является публичной переменной, мы можем получить к ней доступ не из класса. В выдаче вы увидите значение переменной name, выведенное в консоли.

Теперь попробуем вывести значение переменной make. Выполняем следующий скрипт:

В выдаче мы получим следующее уведомление об ошибке:

AttributeError: ‘Car’ object has no attribute ‘make’

AttributeError: ‘Car’ object has no attribute ‘make’

Мы рассмотрели большую часть основных концепций объектно-ориентированного программирования в предыдущих двух секциях. Теперь, поговорим о столбах объектно-ориентированного программирования:

  • Полиморфизм;
  • Наследование;
  • Инкапсуляция.

Наследование

Наследование в объектно-ориентированном программировании очень похоже на наследование в реальной жизни, где ребенок наследует те или иные характеристики его родителей в дополнение к его собственным характеристикам.

В объектно-ориентированном программировании, наследование означает отношение IS-A. Например, болид — это транспорт. Наследование это одна из самых удивительных концепций объектно-ориентированного программирования, так как оно подразумевает повторное использование.

Основная идея наследования в объектно-ориентированном программировании заключается в том, что класс может наследовать характеристики другого класса. Класс, который наследует другой класс, называется дочерним классом или производным классом, и класс, который дает наследие, называется родительским, или основным.

Рассмотрим на очень простой пример наследования. Выполним следующий скрипт:

# Создание класса Vehicle class Vehicle: def vehicle_method(self): print(«Это родительский метод из класса Vehicle») # Создание класса Car, который наследует Vehicle class Car(Vehicle): def car_method(self): print(«Это метод из дочернего класса»)

# Создание класса Vehicle

class Vehicle:  

    def vehicle_method(self):

        print(«Это родительский метод из класса Vehicle»)

 

# Создание класса Car, который наследует Vehicle

class Car(Vehicle):  

    def car_method(self):

        print(«Это метод из дочернего класса»)

В скрипте выше мы создаем два класса: Vehicle и Car, который наследует класс Vehicle. Чтобы наследовать класс, вам нужно только вписать название родительского класса внутри скобок, которая следует за названием дочернего класса. Класс Vehicle содержит метод vehicle_method(), а дочерний класс содержит метод car_method(). Однако, так как класс Car наследует класс Vehicle, он также наследует и метод vehicle_method().

Рассмотрим это на практике и выполним следующий скрипт:

car_a = Car() car_a.vehicle_method() # Вызываем метод родительского класса

car_a = Car()  

car_a.vehicle_method() # Вызываем метод родительского класса

В этом скрипте мы создали объект класса Car вызывали метод vehicle_method() при помощи объекта класса Car. Вы можете обратить внимание на то, что класс Car не содержит ни одного метода vehicle_method(), но так как он унаследовал класс Vehicle, который содержит vehicle_method(), класс Car также будет использовать его. Выдача выглядит следующим образом:

Это родительский метод из класса Vehicle

Это родительский метод из класса Vehicle

Множественное наследование Python

В Python, родительский класс может иметь несколько дочерних, и, аналогично, дочерний класс может иметь несколько родительских классов. Давайте рассмотрим первый сценарий. Выполним следующий скрипт:

# создаем класс Vehicle class Vehicle: def vehicle_method(self): print(«Это родительский метод из класса Vehicle») # создаем класс Car, который наследует Vehicle class Car(Vehicle): def car_method(self): print(«Это дочерний метод из класса Car») # создаем класс Cycle, который наследует Vehicle class Cycle(Vehicle): def cycleMethod(self): print(«Это дочерний метод из класса Cycle»)

# создаем класс Vehicle

class Vehicle:  

    def vehicle_method(self):

        print(«Это родительский метод из класса Vehicle»)

 

# создаем класс Car, который наследует Vehicle

class Car(Vehicle):  

    def car_method(self):

        print(«Это дочерний метод из класса Car»)

 

# создаем класс Cycle, который наследует Vehicle

class Cycle(Vehicle):  

    def cycleMethod(self):

        print(«Это дочерний метод из класса Cycle»)

В этом скрипте, родительский класс Vehicle наследуется двумя дочерними классами — Car и Cycle. Оба дочерних класса будут иметь доступ к vehicle_method() родительского класса. Запустите следующий скрипт, чтобы увидеть это лично:

car_a = Car() car_a.vehicle_method() # вызов метода родительского класса car_b = Cycle() car_b.vehicle_method() # вызов метода родительского класса

car_a = Car()  

car_a.vehicle_method() # вызов метода родительского класса

car_b = Cycle()  

car_b.vehicle_method() # вызов метода родительского класса

В выдаче вы увидите выдачу метода vehicle_method() дважды, как показано ниже:

Это родительский метод из класса Vehicle Это родительский метод из класса Vehicle

Это родительский метод из класса Vehicle

Это родительский метод из класса Vehicle

Вы можете видеть, как родительский класс наследуется двумя дочерними классами. Таким же образом, дочерний класс может иметь несколько родительских. Посмотрим на пример:

class Camera: def camera_method(self): print(«Это родительский метод из класса Camera») class Radio: def radio_method(self): print(«Это родительский метод из класса Radio») class CellPhone(Camera, Radio): def cell_phone_method(self): print(«Это дочерний метод из класса CellPhone»)

class Camera:  

    def camera_method(self):

        print(«Это родительский метод из класса Camera»)

 

class Radio:  

    def radio_method(self):

        print(«Это родительский метод из класса Radio»)

 

class CellPhone(Camera, Radio):  

     def cell_phone_method(self):

        print(«Это дочерний метод из класса CellPhone»)

В скрипте выше мы создали три класса: Camera, Radio, и CellPhone. Классы Camera и Radio наследуются классом CellPhone. Это значит, что класс CellPhone будет иметь доступ к методам классов Camera и Radio. Следующий скрипт подтверждает это:

cell_phone_a = CellPhone() cell_phone_a.camera_method() cell_phone_a.radio_method()

cell_phone_a = CellPhone()  

cell_phone_a.camera_method()  

cell_phone_a.radio_method()

Выдача будет выглядеть следующим образом:

Это родительский метод из класса Camera Это родительский метод из класса Radio

Это родительский метод из класса Camera

Это родительский метод из класса Radio

Полиморфизм

Термин полиморфизм буквально означает наличие нескольких форм. В контексте объектно-ориентированного программирования, полиморфизм означает способность объекта вести себя по-разному.

Полиморфизм в программировании реализуется через перегрузку метода, либо через его переопределение.

Перегрузка метода

Перегрузка метода относится к свойству метода вести себя по-разному, в зависимости от количества или типа параметров. Взглянем на очень простой пример перегрузки метода. Выполним следующий скрипт:

# создаем класс Car class Car: def start(self, a, b=None): if b is not None: print (a + b) else: print (a)

# создаем класс Car

class Car:  

   def start(self, a, b=None):

        if b is not None:

            print (a + b)

        else:

            print (a)

В скрипте выше, если метод start() вызывается передачей одного аргумента, параметр будет выведен на экран. Однако, если мы передадим 2 аргумента методу start(), он внесет оба аргумента и выведет результат суммы.

Попробуем с одним аргументом для начала:

car_a = Car() car_a.start(10)

car_a = Car()  

car_a.start(10)

В выдаче мы можем видеть 10. Теперь попробуем передать два аргумента:

В выдаче вы увидите 30.

Переопределение метода

Переопределение метода относится к наличию метода с одинаковым названием в дочернем и родительском классах. Определение метода отличается в родительском и дочернем классах, но название остается тем же. Давайте посмотрим на простой пример переопределения метода в Python.

# создание класса Vehicle class Vehicle: def print_details(self): print(«Это родительский метод из класса Vehicle») # создание класса, который наследует Vehicle class Car(Vehicle): def print_details(self): print(«Это дочерний метод из класса Car») # создание класса Cycle, который наследует Vehicle class Cycle(Vehicle): def print_details(self): print(«Это дочерний метод из класса Cycle»)

# создание класса Vehicle

class Vehicle:  

    def print_details(self):

        print(«Это родительский метод из класса Vehicle»)

 

# создание класса, который наследует Vehicle

class Car(Vehicle):  

    def print_details(self):

        print(«Это дочерний метод из класса Car»)

 

# создание класса Cycle, который наследует Vehicle

class Cycle(Vehicle):  

    def print_details(self):

        print(«Это дочерний метод из класса Cycle»)

В скрипте выше, классы Cycle и Car наследуют класс Vehicle. Класс Vehicle содержит метод print_details(), который переопределен дочерним классом. Теперь, если вы вызовите метод print_details(), выдача будет зависеть от объекта, через который вызывается метод. Выполните следующий скрипт, чтобы понять суть на деле:

car_a = Vehicle() car_a. print_details() car_b = Car() car_b.print_details() car_c = Cycle() car_c.print_details()

car_a = Vehicle()  

car_a. print_details()

 

car_b = Car()  

car_b.print_details()

 

car_c = Cycle()  

car_c.print_details()

Выдача будет выглядеть вот так:

Это родительский метод из класса Vehicle Это дочерний метод из класса Car Это дочерний метод из класса Cycle

Это родительский метод из класса Vehicle

Это дочерний метод из класса Car

Это дочерний метод из класса Cycle

Как вы видите, выдача отличается, к тому же метод print_details() вызывается через производные классы одного и того же базового класса. Однако, так как дочерние классы переопределены методом родительского класса, методы ведут себя по-разному.

Инкапсуляция

Инкапсуляция — это третий столп объектно-ориентированного программирования. Инкапсуляция просто означает скрытие данных. Как правило, в объектно-ориентированном программировании один класс не должен иметь прямого доступа к данным другого класса. Вместо этого, доступ должен контролироваться через методы класса.

Чтобы предоставить контролируемый доступ к данным класса в Python, используются модификаторы доступа и свойства. Мы уже ознакомились с тем, как действуют модификаторы доступа. В этом разделе мы посмотрим, как действуют свойства.

Предположим, что нам нужно убедиться в том, что модель автомобиля должна датироваться между 2000 и 2018 годом. Если пользователь пытается ввести значение меньше 2000 для модели автомобиля, значение автоматически установится как 2000, и если было введено значение выше 2018, оно должно установиться на 2018. Если значение находится между 2000 и 2018 — оно остается неизменным. Мы можем создать свойство атрибута модели, которое реализует эту логику. Взглянем на пример:

# создаем класс Car class Car: # создаем конструктор класса Car def __init__(self, model): # Инициализация свойств. self.model = model # создаем свойство модели. @property def model(self): return self.__model # Сеттер для создания свойств. @model.setter def model(self, model): if model < 2000: self.__model = 2000 elif model > 2018: self.__model = 2018 else: self.__model = model def getCarModel(self): return «Год выпуска модели » + str(self.model) carA = Car(2088) print(carA.getCarModel())

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

# создаем класс Car

class Car:

 

    # создаем конструктор класса Car

    def __init__(self, model):

        # Инициализация свойств.

        self.model = model

 

    # создаем свойство модели.

    @property

    def model(self):

        return self.__model

 

    # Сеттер для создания свойств.

    @model.setter

    def model(self, model):

        if model < 2000:

            self.__model = 2000

        elif model > 2018:

            self.__model = 2018

        else:

            self.__model = model

 

    def getCarModel(self):

        return «Год выпуска модели » + str(self.model)

 

carA = Car(2088)  

print(carA.getCarModel())

Свойство имеет три части. Вам нужно определить атрибут, который является моделью в скрипте выше. Затем, вам нужно определить свойство атрибута, используя декоратор @property. Наконец, вам нужно создать установщик свойства, который является дескриптором @model.setter в примере выше.

Теперь, если вы попробуете ввести значение выше 2018 в атрибуте модели, вы увидите, что значение установлено на 2018. Давайте проверим это. Выполним следующий скрипт:

car_a = Car(2088) print(car_a.get_car_model())

car_a = Car(2088)  

print(car_a.get_car_model())

Здесь мы передаем 2088 как значение для модели, однако, если вы введете значение для атрибута модели через функцию get_car_model(), вы увидите 2018 в выдаче.

Подведем итоги

В этой статье мы освоили часть важнейших основ объектно-ориентированного программирования. Этот тип программирования — один из самых популярных и используемых парадигм.

Важность объектно-ориентированного программирования отображается в том факт, что большая часть современных языков программирования так или иначе объектно-ориентированы или, по крайней мере, поддерживают объектно-ориентированное программирование.

Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.

E-mail: [email protected]

Образование
Universitatea Tehnică a Moldovei (utm.md)

  • 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
  • 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»

Объектно-ориентированное программирование на Python. Курс

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

Ключевыми понятиями объектно-ориентированного программирования являются «класс» и «объект». Это реально существующие в программном коде единицы, а не только обобщающие понятия. Объекты порождаются от своих классов. В языке программирования Python такие объекты принято называть экземплярами.

Наследование, полиморфизм и инкапсуляция – основные принципы, столпы объектно-ориентированного программирования. Наследование предполагает возможность определения дочерних классов, полиморфизм – разный способ реализации одного и того же, инкапсуляция – сокрытие и объединение данных и методов. Композиция реализуется реже, означает возможность создания объектов, составными частями которых являются объекты других классов.

Конструктором в ООП называют метод класса, который вызывается автоматически при создании объекта от этого класса. В то же время конструктор относится к методам перегрузки операторов. Имена таких методов регламентированы самим языком программирования, а их вызов происходит автоматически при участии объекта в тех или иных операциях. Например, сложении, извлечении элемента и др.

Итераторы – это особая разновидность объектов подобных коллекциям вроде списка, но которые не хранят в себе весь набор элементов, а генерируют только один при каждом обращении. В Python есть встроенные классы-типы данных, от которых создаются итераторы. Однако также можно определять собственные классы, чьи экземпляры будут обладать возможностями итераторов.

Курс включает 12 основных уроков + 3 дополнительных:

  1. Что такое объектно-ориентированное программирование

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

  3. Конструктор класса – метод __init__()

  4. Наследование

  5. Полиморфизм

  6. Инкапсуляция

  7. Композиция

  8. Перегрузка операторов

  9. Модули и пакеты

  10. Документирование кода

  11. Пример объектно-ориентированной программы на Python

  12. Особенности объектно-ориентированного программирования

  13. Статические методы

  14. Итераторы

  15. Генераторы

Курс с примерами решений практических работ и всеми уроками:
android-приложение, pdf-версия

Версия курса: октябрь 2020.

Классы в Python 3 и объекты — примеры наследования и что означает self

Классы в языке Python являются важнейшей частью объектно-ориентированного подхода к программированию. Класс описывает пользовательский тип данных, на основе которого в программе создаются однородные объекты. Как правило, они могут включать в себя некие свойства и методы, позволяющие реализовать их текущее состояние, а также поведение. В статье описаны классы для начинающих и чайников в Python 3, а так же чтобы освежить знания опытным программистам.

Объектно-ориентированное программирование

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

В свою очередь, ООП предлагает несколько иной способ реализации программ, представляя их в виде совокупности объектов, взаимодействующих между собой.

Благодаря такой особенности:

  • Улучшается восприятие поставленной задачи при работе над проектом;
  • Сокращается количество строк кода;
  • Уменьшается сложность написания кода.

Основными принципами ООП являются следующие механизмы: абстракция, инкапсуляция, наследование и полиморфизм. Для создания программ, обрабатывающих информацию в виде объектов, необходимо понимание, а также комплексное соблюдение всех четырех парадигм. Практическое применение каждой из них можно встретить в примерах из данной статьи.

Рассмотрим основные принципы ООП:

  • Абстракция выполняет основную задачу ООП, позволяя программисту формировать объекты определенного типа с различными характеристиками и поведением, благодаря применению классов.
  • Инкапсуляция помогает скрыть детали реализации конкретных объектов и защитить их свойства от постороннего вмешательства.
  • Наследование дает возможность расширять уже существующие классы за счет автоматического переноса их параметров и методов к новым структурам данных.
  • Полиморфизм используется для создания единого интерфейса, который имеет множество различных реализаций, зависящих от класса применяемого объекта.

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

Чтобы определить новый класс в своей программе, необходимо напечатать ключевое слово class, а после него добавить имя для создаваемой структуры данных, завершив ввод вставкой двоеточия. Следующий пример демонстрирует генерацию пустого класса с именем Example. Как можно заметить, в нем полностью отсутствует какая-либо информация.

class Example:
    pass
example = Example()

Несмотря на пустое тело класса Example, на его основе уже можно создать определенный объект, обладающий уникальным идентификатором. Последняя строка кода, находящегося выше, представляет собой пример генерации объекта с именем example и типом данных Example. Здесь используется оператор присваивания, а также пустые круглые скобки после названия класса, прямо как в вызове метода не имеющего никаких аргументов.

Определив новый класс, можно создавать сколько угодно объектов на его основе. Как уже было сказано выше, такая структура данных может включать в себя некие свойства, то есть переменные, которыми будет наделен каждый экземпляр класса. Ниже приведен простой пример класса и объекта Python 3. В примере описывается класс под названием Data со строкой word и числом number.

class Data:
    word = "Python"
    number = 3
data = Data()
print(data.word + " " + str(data.number))

Python 3

Если создать объект, основанный на классе Data, то он получит обе переменные, а также их значения, которые были определены изначально. Таким образом, был сгенерирован объект data. Получить доступ к его полям с именами word и number можно с помощью оператора точки, вызвав его через экземпляр класса. Функция print поможет вывести значения полей объекта data на экран. Не стоит забывать и о том, что число следует привести к строчному виду для того чтобы обработать его в методе print вместе с текстовым значением.

Помимо полей, пользовательский класс может включать в себя и методы, которыми будут наделены все его экземпляры. Вызвать выполнение определенного метода через созданный объект можно так же, как и получить доступ к его полям, то есть с помощью точки. Данный пример демонстрирует класс Data с функцией sayHello, которая выводит текст на экран.

class Data:
    def sayHello(self):
        print("Hello World!")
data = Data()
data.sayHello()

Hello World!

Для того чтобы вызвать метод sayHello, нужно создать объект, принадлежащий требуемому классу Data. После этого можно запустить функцию через сгенерированный экземпляр с идентификатором data, что позволит вывести небольшое текстовое сообщение.

Аргумент self

Рассмотрим зачем нужен и что означает self в функциях Python. Как можно было заметить, единственным атрибутом для метода из класса является ключевое слово self. Помещать его нужно в каждую функцию чтобы иметь возможность вызвать ее на текущем объекте. Также с помощью этого ключевого слова можно получать доступ к полям класса в описываемом методе. Self таким образом заменяет идентификатор объекта.

class Dog:
    name = "Charlie"
    noise = "Woof!"
    def makeNoise(self):
        print(self.name + " says: " + self.noise + " " + self.noise)
dog = Dog()
dog.makeNoise()

Charlie says: Woof! Woof!

Вверху представлен класс Dog, описывающий собаку. Он обладает полями name (имя) со стартовым значением «Charlie» и noise (шум), содержащим звук, который издает животное. Метод makeNoise заставляет собаку лаять, выдавая соответствующее сообщение на экран. Для этого в функции print используется получение доступа к полям name и noise. Далее необходимо создать экземпляр класса Dog и вызвать на нем makeNoise.

Конструктор

В предыдущих примерах кода все создаваемые объекты получали значения для своих полей напрямую из класса, так как они были заданы по умолчанию. Изменить внутренние данные любого объекта можно с помощью оператора доступа к свойствам объекта. Но существует возможность заранее определить поля для объекта, задав их во время его создания. Для этой цели в ООП используется конструктор, принимающий необходимые параметры. Следующий пример показывает работу конструктора во время инициализации объекта класса Dog.

class Dog:
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed
dog = Dog("Max", "German Shepherd")
print(dog.name + " is "+ dog.breed)

Max is German Shepherd

Внешне конструктор похож на обычный метод, однако вызвать его явным образом нельзя. Вместо этого он автоматически срабатывает каждый раз, когда программа создает новый объект для класса, в котором он расположен. Имя у каждого конструктора задается в виде идентификатора __init__. Получаемые им параметры можно присвоить полям будущего объекта, воспользовавшись ключевым словом self, как в вышеописанном примере.

Таким образом, класс Dog содержит два поля: name (имя) и breed (порода). Конструктор принимает параметры для изменения этих свойств во время инициализации нового объекта под названием dog. Каждый класс содержит в себе по крайней мере один конструктор, если ни одного из них не было задано явно. Однако в том случае, когда программист добавляет в свой класс конструктор с некими параметрами, конструктор, не обладающий параметрами, работать не будет. Чтобы им воспользоваться, нужно явно прописать его в классе.

Ключевой особенностью ООП является абстракция, благодаря которой есть возможность создавать частные объекты на основе общего класса, то есть определенного абстрактного понятия, такого как собака, поскольку она может иметь свое имя, породу, вес, рост.

Деструктор

Работа с деструктором, как правило, является прерогативой языков, предоставляющих более широкие возможности для управления памятью. Несмотря на грамотную работу сборщика мусора, обеспечивающего своевременное удаление ненужных объектов, вызов деструктора все еще остается доступным. Переопределить его можно в классе, задав имя __del__.

class Data:
    def __del__(self):
        print "The object is destroyed"
data = Data()
del(data)

The object is destroyed

Как и конструктор, деструктор может содержать некий пользовательский код, сообщающий об успешном завершении работы метода. В данном примере создается экземпляр класса Data и вызывается его деструктор, принимающий в качестве параметра сам объект.

Наследование

Возможность одному классу выступать в качестве наследника для другого, перенимая тем самым его свойства и методы, является важной особенностью ООП.

Благодаря этой важной особенности пропадает необходимость переписывания кода для подобных по назначению классов.

При наследовании классов в Python обязательно следует соблюдать одно условие: класс-наследник должен представлять собой более частный случай класса-родителя. В следующем примере показывается как класс Person (Человек) наследуется классом Worker (Работник). При описании подкласса в Python, имя родительского класса записывается в круглых скобках.

class Person:
    name = "John"
class Worker(Person):
    wage = 2000
human = Worker()
print(human.name + " earns $" + str(human.wage))

John earns $2000

Person содержит поле name (имя), которое передается классу Worker, имеющему свойство wage (заработная плата). Все условия наследования соблюдены, так как работник является человеком и также обладает именем. Теперь, создав экземпляр класса Worker под названием human, можно получить свободный доступ к полям из родительской структуры данных.

Множественное наследование

Наследовать можно не только один класс, но и несколько одновременно, обретая тем самым их свойства и методы. В данном примере класс Dog (Собака) выступает в роли подкласса для Animal (Животное) и Pet (Питомец), поскольку может являться и тем, и другим. От Animal Dog получает способность спать (метод sleep), в то время как Pet дает возможность играть с хозяином (метод play). В свою очередь, оба родительских класса унаследовали поле name от Creature (Создание). Класс Dog также получил это свойство и может его использовать.

class Creature:
    def __init__(self, name):
        self.name = name
class Animal(Creature):
    def sleep(self):
        print(self.name + " is sleeping")
class Pet(Creature):
    def play(self):
        print(self.name + " is playing")
class Dog(Animal, Pet):
    def bark(self):
        print(self.name + " is barking")
beast = Dog("Buddy")
beast.sleep()
beast.play()
beast.bark()

Buddy is sleeping
Buddy is playing
Buddy is barking

В вышеописанном примере создается объект класса Dog, получающий имя в конструкторе. Затем по очереди выполняются методы sleep (спать), play (играть) и bark (лаять), двое из которых были унаследованы. Способность лаять является уникальной особенностью собаки, поскольку не каждое животное или домашний питомец умеет это делать.

Абстрактные методы

Поскольку в ООП присутствует возможность наследовать поведение родительского класса, иногда возникает необходимость в специфической реализации соответствующих методов. В качестве примера можно привести следующий код, где классы Dog (Собака) и Cat (Кошка) являются потомками класса Animal (Животное). Как и положено, они оба наследуют метод makeNoise (шуметь), однако в родительском классе для него не существует реализации.

Все потому, что животное представляет собой абстрактное понятие, а значит не способно издавать какой-то конкретный звук. Однако для собаки и кошки данная команда зачастую имеет общепринятое значение. В таком случае можно утверждать, что метод makeNoise из Animal является абстрактным, поскольку не имеет собственного тела реализации.

class Animal:
    def __init__(self, name):
        self.name = name
    def makeNoise(self):
        pass
class Dog(Animal):
    def makeNoise(self):
        print(self.name + " says: Woof!")
class Cat(Animal):
    def makeNoise(self):
        print(self.name + " says: Meow!")
dog = Dog("Baxter")
cat = Cat("Oliver")
dog.makeNoise()
cat.makeNoise()

Baxter says: Woof!
Oliver says: Meow!

Как видно из примера, потомки Dog и Cat получают makeNoise, после чего переопределяют его каждый по-своему. В этом заключается суть полиморфизма, позволяющего изменять ход работы определенного метода исходя из нужд конкретного класса. При этом название у него остается общим для всех наследников, что помогает избежать путаницы с именами.

Статические методы

В предыдущих примерах все методы классов вызывались при помощи объектов, имеющих соответствующий тип. Однако пользоваться таким подходом неудобно, когда в программе нет нужды в обращении к каким-либо специфическим свойствам класса. К примеру, есть определенная структура Math, содержащая в себе методы для арифметических вычислений. Применять ее функции можно не создавая объект, если они помечены, как статические. Для того, чтобы отметить в классе метод как статический, в Python используется декоратор @staticmethod.

class Math:
    @staticmethod
    def inc(x):
        return x + 1
    @staticmethod
    def dec(x):
        return x - 1
print(Math.inc(10), Math.dec(10))

(11, 9)

В вышеописанном коде класс Math включает в себя два статических метода: inc, берущий число в качестве параметра и возвращающий результат, увеличенный на единицу, и dec, делающий все то же самое, только наоборот. Вызвать оба этих метода можно с помощью оператора точки, а также имени класса Math, где они были изначально реализованы.

Ограничение доступа

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

В такой ситуации помогает еще одна особенность ООП под названием инкапсуляция. Она предписывает применение приватных свойств класса, к которым отсутствует доступ за его пределами. Для управления содержимым объекта необходимо использовать специальные методы, именуемые getter (возвращает значение) и setter (устанавливает значение).

class Cat:
    __name = "Kitty"
    def get_name(self):
        return self.__name
    def set_name(self, name):
        self.__name = name
cat = Cat()
print(cat.get_name())
cat.set_name("Misty")
print(cat.get_name())

Kitty
Misty

Чтобы ограничить видимость полей, следует задать для них имя, начинающееся с двойного подчеркивания. В примере, продемонстрированном выше, класс Cat (Кошка) имеет закрытое свойство __name (имя), а также специальные методы get_name и set_name. Отличительной чертой такого подхода является возможность установить определенные рамки для вводимых значений. Например, можно запретить ввод отрицательного числа или пустой строки.

Свойства классов

С помощью специального механизма свойств класса можно внести корректировки в работу с оператором точки, присвоив ему собственные функции. В следующем примере представлен класс с приватным полем x, для которого написаны getter и setter. С помощью присвоения полю x специального значения функции property, получающей в качестве аргументов имена методов, можно настроить работу оператора точки согласно своим нуждам.

class Data:
    def __init__(self, x):
        self.__set_x(x)
    def __get_x(self):
        print("Get X")
        return self.__x
    def __set_x(self, x):
        self.__x = x
        print("Set X")
    x = property(__get_x, __set_x)
data = Data(10)
print(data.x)
data.x = 20
print(data.x)

Set X
Get X
10
20

Как видно из результатов выполнения кода, методы __get_x и __set_x работают с помощью оператора точки, вызываясь самостоятельно. Таким образом, упрощается написание кода при изменении свойств объекта, а также повышается уровень безопасности данных.

Перегрузка операторов

Для обработки примитивных типов данных в языках программирования используются специальные операторы. К примеру, арифметические операции выполняются при помощи обычных знаков плюс, минус, умножить, разделить. Однако при работе с собственными типами информации вполне может потребоваться помощь этих операторов. Благодаря специальным функциям, их можно самостоятельно настроить под свои задачи.

В данном примере создается класс Point (точка), обладающий двумя полями: x и y. Для сравнения двух разных объектов такого типа можно написать специальный метод либо же просто перегрузить соответствующий оператор. Для этого потребуется переопределить функцию __eq__ в собственном классе, реализовав новое поведение в ее теле.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
print(Point(2, 5) == Point(2, 5))
print(Point(3, 8) == Point(4, 6))

True
False

Переопределенный метод возвращает результат сравнения двух полей у различных объектов. Благодаря этому появилась возможность сравнивать две разных точки, пользуясь всего лишь обычным оператором. Результат его работы выводится при помощи метода print.

Аналогично сравнению, можно реализовать в Python перегрузку операторов сложения, вычитания и других арифметических и логических действий. Так же можно сделать перегрузку стандартных функций str и len.

Заключение

В данной статье были рассмотрены основные особенности работы с классами в языке Python и наглядно продемонстрированы наиболее важные принципы объектно-ориентированного программирования. Работа с классами позволяет представить все данные в программе в виде взаимодействующих между собой объектов, обладающих некими свойствами и поведением.

#17 — Основы ООП Python

В уроке мы приступим к изучению ООП в языке Python. ООП это объектно ориентированное программирование в основе которого является создание класса, а также объекта класса. Мы научимся создавать классы и объекты, а также создадим методы и поля для основного класса.

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

ООП простыми словами

Поскольку на примере все усвоить гораздо проще, то давайте за пример возьмем робота, которого постараемся описать за счёт классов в ООП.

Класс в случае с роботом – это его чертёж. Экземпляром класса (объектом) называет целый робот, который создан точно по чертежу.


Наследование – это добавление полезных опций к чертежу робота. К примеру, берем стандартный чертёж робота и дорисуем к нему лазеры, крылья и броню. Все эти дорисовки мы сделаем в классе наследнике, основной функционал которого взят из родительского класса.

Полиморфизм – это общий функционал для всех роботов и не важно что каждый робот может очень сильно отличаться друг от друга. К примеру, в главном классе мы указываем возможность передвижения для всех последующих роботов. Далее в классе наследнике мы можем дополнительно указать возможность левитации для робота, в другом же классе укажем возможность передвижения по воде и так далее. Получается, что есть общий функционал что записан в главном чертеже, но его можно переписать для каждого последующего робота (для каждого наследника).

А инкапсуляция является для нас бронёй, защищающей робота. Под пластырем брони находятся уязвимые элементы, вроде проводов и микросхем. После прикрытия брешей с помощью брони (protected или private), робот полностью защищён от внешних вмешательств. По сути, мы делаем доступ ко всем полям лишь за счёт методов, тем самым прямой доступ к полю будет закрыт.

У всех классов методы могут отличаться, как и поля с конструкторами. Каждый класс позволяет создавать любое количество разных объектов, все из них имеют собственные характеристики.

Создание классов

Для создания класса необходимо прописать ключевое слово class и далее название для класса. Общепринято начинать названия классов с буквы в верхнем регистре, но если этого не сделать, то ошибки не будет.

В любом классе можно создавать поля (переменные), методы (функции), а также конструкторы.

Создав новый класс и поместив туда какую-либо информацию мы можем создавать на основе него новые объекты. Объекты будут иметь доступ ко всем характеристикам класса.

Пример простого класса приведен ниже:

class Book:
	pass # Класс может ничего не возвращать

На основе такого класса мы можем создать множество объектов. Каждый объект в данном случае будет представлять из себя конкретную книжку. Для каждого объекта мы можем указать уникальные данные.

Чтобы создать объект нам потребуется следующий код:

obj_new = Some() # Создание объекта
obj_second = Some() # Создание 2 объекта

ООП в Python – принципы, классы, объекты, аттрибуты

Содержание:развернуть

ООП – самая используемая парадигма программирования. Это одновременно и особый способ мышления, и отдельная методика. Её концепцию проще всего понимать на примерах из реальной жизни. И это неспроста. Объектно-ориентированное программирование помогает представлять содержимое программы наиболее естественным для нашего мира способом.

Главным понятием ООП является понятие программного объекта. Вообще говоря, большинство сущностей на планете Земля – это некие объекты. И с частью из них мы взаимодействуем при помощи программирования. Банковский счёт, персонаж компьютерной игры или анимированный виджет сайта – всё это легко представить в виде объектов. Можно сказать, что объектно-ориентированное программирование позволяет смоделировать реальный объект в виде программного.

Множество объектов со схожими свойствами формируются в классы. Идея класса также является одной из основополагающих концепций ООП. Со стороны программы, класс – это всего лишь тип данных, но для программиста это куда более глубокая абстрактная структура. Но перейдём уже к конкретике.

💁‍♂️ Итак, мы – разработчики игр. Наша студия трудится над новым автосимулятором. В игре будут представлены разные виды транспорта: легковые автомобили, гоночные, грузовые и пассажирские. Все их можно описать одним словом – автотранспорт. Сделав это, мы абстрагировались от деталей и, таким образом, определили класс. Объектом этого класса может быть, как Бьюик 1968-го года, так и грузовой Freightliner Columbia желтого цвета.

У класса есть свойства и функции (в ООП их называют методами).

  • Свойства — это характеристики, присущие данному конкретному множеству объектов.
  • Методы — те действия, которые они могут совершать.

Свойствами класса «автотранспорт» могут быть, например: год выпуска, вид и цвет. На уровне объектов это будет выглядеть так: Бьюик Электра – это объект класса «Автотранспорт» со следующими свойствами:

  • вид – легковой автомобиль;
  • цвет – чёрный;
  • год выпуска – 1968.

Можно сказать, что объект – это вполне конкретный экземпляр класса

Помимо физических атрибутов, которые описывают внешний вид и характеристики транспортного средства, автомобили обладают между собой и другими фундаментальными сходствами. Например, все они могут ехать, тормозить, переключать скорости, поворачивать и сигналить. В нашем случае, всё это — методы класса «Автотранспорт». То есть действия, которые любые объекты данного класса могут выполнять.

Мы разрабатываем игру, поэтому предполагается, что машины в ней будут исправными. Значит, вполне естественно, что каждая из них может ехать и тормозить.

В Питоне класс «Автотранспорт» может выглядеть так:

# класс автотранспорт class MotorTransport(object): def __init__(self, color, year, auto_type): self.color = color self.year = year self.auto_type = auto_type # тормозить def stop(self): print("Pressing the brake pedal") # ехать def drive(self): print('WRRRRRUM!')

Теперь никто не помешает нам получить собственную красную феррари. Пусть и в симуляторе.

# создадим объект класса Автотранспорт ferrari_testarossa = MotorTransport('Red', 1987, 'passenger car') # жмём на газ и вперёд! ferrari_testarossa.drive() > WRRRRRUM!

Принципы ООП

Абстракция

Абстракция – это выделение основных, наиболее значимых характеристик объекта и игнорирование второстепенных.

Любой составной объект реального мира – это абстракция. Говоря «ноутбук», вам не требуется дальнейших пояснений, вроде того, что это организованный набор пластика, металла, жидкокристаллического дисплея и микросхем. Абстракция позволяет игнорировать нерелевантные детали, поэтому для нашего сознания это один из главных способов справляться со сложностью реального мира. Если б, подходя к холодильнику, вы должны были иметь дело с отдельно металлом корпуса, пластиковыми фрагментами, лакокрасочным слоем и мотором, вы вряд ли смогли бы достать из морозилки замороженную клубнику.

Полиморфизм

Полиморфизм подразумевает возможность нескольких реализаций одной идеи. Простой пример: у вас есть класс «Персонаж», а у него есть метод «Атаковать». Для воина это будет означать удар мечом, для рейнджера – выстрел из лука, а для волшебника – чтение заклинания «Огненный Шар». В сущности, все эти три действия – атака, но в программном коде они будут реализованы совершенно по-разному.

Наследование

Это способность одного класса расширять понятие другого, и главный механизм повторного использования кода в ООП. Вернёмся к нашему автосимулятору. На уровне абстракции «Автотранспорт» мы не учитываем особенности каждого конкретного вида транспортного средства, а рассматриваем их «в целом». Если же более детализировано приглядеться, например, к грузовикам, то окажется, что у них есть такие свойства и возможности, которых нет ни у легковых, ни у пассажирских машин. Но, при этом, они всё ещё обладают всеми другими характеристиками, присущими автотранспорту.

Мы могли бы сделать отдельный класс «Грузовик», который является наследником «Автотранспорта». Объекты этого класса могли бы определять все прошлые атрибуты (цвет, год выпуска), но и получить новые. Для грузовиков это могли быть грузоподъёмность, снаряженная масса и наличие жилого отсека в кабине. А методом, который есть только у грузовиков, могла быть функция сцепления и отцепления прицепа.

Инкапсуляция

Инкапсуляция – это ещё один принцип, который нужен для безопасности и управления сложностью кода. Инкапсуляция блокирует доступ к деталям сложной концепции. Абстракция подразумевает возможность рассмотреть объект с общей точки зрения, а инкапсуляция не позволяет рассматривать этот объект с какой-либо другой.

Вы разработали для муниципальных служб класс «Квартира». У неё есть свойства вроде адреса, метража и высоты потолков. И методы, такие как получение информации о каждом из этих свойств и, главное, метод, реализующий постановку на учёт в Росреестре. Это готовая концепция, и вам не нужно чтобы кто-то мог добавлять методы «открыть дверь» и «получить место хранения денег». Это А) Небезопасно и Б) Избыточно, а также, в рамках выбранной реализации, не нужно. Работникам Росреестра не требуется заходить к вам домой, чтобы узнать высоту потолков – они пользуются только теми документами, которые вы сами им предоставили.

Класс

— У тебя есть ключ? — Лучше! У меня есть рисунок ключа!

Классы, в некотором смысле, подобны чертежам: это не объекты сами по себе, а их схемы. Класс «банковских счетов» имеет строго определенные и одинаковые для всех атрибуты, но объекты в нём – сами счета – уникальны.

Как в Python создать класс

В Python классы и объекты по смыслу не отличаются от других языков. Нюансы в реализации. Для создания класса в Питоне необходимо написать инструкцию class, а затем выбрать имя. В простейшем случае, класс выглядит так:

class SimpleClass: pass

Для именования классов в Python обычно используют стиль «camel case», где первая буква – заглавная.

LikeThis

Конструктор

Метод, который вызывается при создании объектов, в ООП зовётся конструктором. Он нужен для объектов, которые изначально должны иметь какие-то значение. Например, пустые экземпляры класса «Студент» бессмысленны, и желательно иметь хотя бы минимальный обозначенный набор вроде имени, фамилии и группы.

В качестве Питоновского конструктора выступает метод __init__():

class Student: def __init__(self, name, surname, group): self.name = name self.surname = surname self.group = group alex = Student("Alex", "Ivanov", "admin")

Атрибуты класса

Вы уже поняли, что у каждого класса есть собственный набор характеристик, который помогает описывать его сущность. Эти свойства еще называются полями или атрибутами.

Поля могут быть статическими и динамическими:

  • Статические поля (поля класса) можно использовать без создания объекта. А значит, конструктор вам не нужен.
  • Динамические поля (поля объекта) задаются с помощью конструктора, и тут уже, как вы видели, экземпляр нужно создать, а полям присвоить значения.
class MightiestWeapon: # статический атрибут name = "Default name" def __init__(self, weapon_type): # динамический атрибут self.weapon_type = weapon_type

☝️ Обратите внимание – статический и динамический атрибут может иметь одно и то же имя:

class MightiestWeapon: # статический атрибут name = "Default name" def __init__(self, name): # динамический атрибут self.name = name weapon = MightiestWeapon("sword") print(MightiestWeapon.name) print(weapon.name)

Методы класса

Метод – это функция класса.

Например, у всех научно-фантастических космических кораблей есть бортовое оружие. И оно может стрелять.

class SpaceShip: def atack(self): print('Пиу!') star_destroyer = SpaceShip() star_destroyer.atack() > Пиу!

Что такое self?

Аналог этого ключевого слова в других языках – слово this. self – это всего лишь ссылка на текущий экземпляр класса.

🐈 Отличный пример с котофеями:

  1. Все котики умеют мурлыкать;
  2. Эта способность реализована в классе Кот, как метод Мурчать;
  3. Вы хотите, чтобы ваш кот по имени Пушок помурчал;
  4. Если сделать так: Кот.Мурчать, то мурлыкать начнут все коты во Вселенной;
  5. Но так как вам нужен один конкретный кот, то нужно вызвать метод иначе: self.Мурчать;
  6. Сделано. Пушок мурлыкает.

Уровни доступа атрибутов и методов

В Питоне не существует квалификаторов доступа к полям класса. Отсутствие аналогов связки public/private/protected можно рассматривать как упущение со стороны принципа инкапсуляции.

Декораторы

Декоратор – это функция-обёртка. В неё можно завернуть другой метод, и, тем самым, изменить его функциональность, не меняя код.

Объекты или экземпляры класса

Чем объекты отличаются от классов

Как уже было сказано, объект – это конкретный экземпляр класса. Все мы относимся к классу людей, но каждый из нас – уникальный объект этого класса.

Как создать объект класса в Python

Если у нас есть реализация класса, то его экземпляр создать очень просто:

class AirConditioner: def __init__(self, model, capacity): self.model = model self.capacity = capacity def turn_on(self): print('Now in the room will be cool') # создадим объект класса Кондиционер ballu = AirConditioner('BPAC-07', 785) ballu.turn_on() > Now in the room will be cool

Атрибуты объекта

Атрибуты класса могут быть динамическими и статическими. На уровне объекта они инициализируются так:

class MightiestWeapon: name = "Default name" def __init__(self, weapon_type): self.weapon_type = weapon_type # атрибут name можно переопределить и не создавая объекта MightiestWeapon.name = 'Steel Sword' print(MightiestWeapon.name) > Steal Sword # создаём объект и сразу же инициализируем динамический атрибут с помощью конструктора hero_sword = MightiestWeapon('sword') # и теперь, уже для конкретного объекта, можно задать имя hero_sword.name = 'Excalibur' # новое статическое имя по умолчанию для всего класса не изменится print(MightiestWeapon.name) > Steal Sword print(hero_sword.name) > Excalibur

Наследование

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

# класс "Животное". Это достаточно абстрактный класс всего с одним методом "Издать звук". class Animal: def make_a_sound(self): print("Издаёт животный звук")

Мы все прекрасно знаем, что котики, к примеру, любят всё ронять, а собакены – рыть землю. Создадим два соответствующих класса-наследника:

# факт наследования в Python указывается при объявлении класса-наследника. # в скобках, после имени класса, указывается класс-родитель class Cat(Animal): def drop_everything(self): print('Вставай скорее, я всё уронил!') class Dog(Animal): def dig_the_ground(self): print('Однажды я докопаюсь до ядра планеты!')

Теперь объекты этих двух классов могут не только издавать животные звуки, но и выполнять собственные уникальные действия:

Tom = Cat() Tom.make_a_sound() > Издаёт животный звук Tom.drop_everything() > Вставай скорее, я всё уронил!

Переопределение

Сейчас у нас и кошка, и собака просто «издают животные звуки», а хотелось бы, конечно, слышать звуки, свойственные именно этим животным. Для этого существует механика переопределения. Достаточно объявить в классе-наследнике метод с тем же названием, что и в базовом классе:

class Dog(Animal): def dig_the_ground(self): print('Однажды я докопаюсь до ядра планеты!') # отныне для объектов класса "Собака" будет выполняться именно эта реализация метода def make_a_sound(self): print('Гав-гав!') Balto = Dog() Balto.make_a_sound() > Гав-гав!

Документирование классов

Весь код нужно комментировать и документировать. Классы – не исключение. Стоит помнить, что код вы пишите не для себя, и вполне вероятно, что написанное вами придётся поддерживать другим людям. Комментарии повышают читаемость и увеличивают легкость восприятие кода в разы, тем самым экономя время и деньги.

Поэтому вот наша статья о Python-документации.


ООП ещё долгое время будет оставаться передовой парадигмой программирования. Но учить её полезно и по другой причине. Прямая связь объектно-ориентированного программирования с реальным миром помогает глубже понимать устройство и принципы работы, как самого языка, так и написания кода в целом.

Python — Объектно Ориентированное Программирование (ООП)


В данной статье даются основы ООП в питоне

В python всё — объекты.

*Аудитория в шоке, особо нервные дамочки падают в обморок*
  • Числа — объекты
  • Строки — объекты
  • Списки — объекты
  • Классы — объекты


Если говорить просто, то «объекты» — это некая структура.

И было слово…

Чтобы создавать объекты, используются «конструкторы» или классы.
  • Класс — это схема, описывающая нашу структуру, возможные внутри неё данные и присущие ей методы.
  • Метод — это функция. Т.е. метод объекта — это функция, описанная внутри объекта, и присущая этому объекту. Метод — это функция, которая действует на объекты данного вида. Для удобства у разных видов объектов могут быть методы с одинаковыми именами, работающие по разному, но схожим образом.
  • Экземпляр — это конкретный объект, созданный из класса.

Рассмотрим пример

Список:
L = [1, 2, 3, 4]

  • Список (List) — это класс объекта
  • Переменная L содержит экземпляр объекта (конкретно список [1, 2, 3, 4])
  • append(), sort() — методы объекта, т.е. функции, которые можно применить к его экземплярам.

Пока звучит не очень сложно.
Но по прежнему не понятно зачем это нужно. =)

1 что приходит на ум — абстракция. Мы не думаем о том как устроен объект, а думаем о том что мы можем с ним сделать.
Например, возьмём функцию dir() и передадим экземпляр объекта список в неё как аргумент:

dir(L)


На выходе получим большой список. Большой список методов для списка.
У нас есть 2 пути:
  • опробовать некоторые из них (возможно, пытаясь передать какие-то атрибуты)
  • найти их описание в google по имени

Но в любом случае мы узнаем о том, «что можно делать со списком», не изучая то как он устроен.

При этом если мы создадим свою структуру данных, свой класс со своими объектами, то у них могут быть одноимённые методы (например, если они ведут себя аналогично).

Например, у строк есть несколько методов, одноимённых методом списков:

Метод index() является одним из них.
Рассмотрим как он работает для наших примеров:

Легко заметить, что некоторые методы в списке имели по 2 подчёркивания с обеих сторон, например:

__len__
Это «стандартный» метод. Фактически, когда мы используем функцию len(), например:

то на самом деле вызывается метод __len__ соответствующего объекта.

Почему?
Всё потому же — если мы будем внутри функции языка len описывать как вычислять длину любого объекта, то это будет очень много кода. Да и для новых классов объектов (например, numpy.array) эта функция не будет работать.
А так у каждого класса внутри будет краткое описание того как это работает.

Так же это позволяет переопределить поведение некоторых операторов.
Например, что будет, если мы напишем:

Фактически будет вызван метод add — s.__add__(L)

В нашем примере мы получим ошибку:

Но некоторые классы объектов вполне могут принимать на вход «чужака» (не всякого конечно):

А ещё когда мы пишем

print L

, то на самом деле вызывается метод __str__, чтобы преобразовать список L в строку для печати.

Итак, 

  • класс — это инструкция, по которой собирается конкретный объект
  • экземпляр — это конкретный объект
  • а метод — это функция, присущая конкретному классу объектов.

И это всё?

Нет. =)

Когда мы говорим, что есть 2 экземпляра объектов список:


то очевидно, что они содержат разные данные.
Т.е. внутри объектов помимо методов есть данные, и хранятся они в атрибутах.

Примером атрибута может быть shape для numpy:

Мы просто создали вектор, передав в него данные. И при этом вычислился его размер.
Логично, для больших матриц в векторов проще хранить внутри 1 переменную с натуральным числом, чем каждый раз проходить по всем данным и тратить время для вычислений.

Давайте попрактикуемся

Создадим пустой класс, который ничего не делает и ничего не хранит:

Теперь создадим 1й метод у нашего класса.
Для этого создадим функцию, внутри метода. Она должна принимать по крайней мере 1 аргумент — self — это экземпляр того объекта, методов которого функция будет.

Если мы хотим передавать в метод какие-то параметры, то просто зададим их после self, как и обычные параметры функции:

Это всё здорово, но пока особой разницы между методом и функцией нет.
Можно конечно рассматривать класс как некий контейнер для централизованного хранения функций.
Но обычно всё же класс подразумевает ещё и хранение данных в атрибутах.

Синтаксис для атрибутов аналогичен синтаксису методов (только после атрибута не надо ставить круглые скобки):


Давайте создадим атрибут у экземпляра объекта:

Вполне очевидно, что если я создам 2й экземпляр этого же класса, и присвою уже в нём атрибуту с таким же именем какое-то значение, то у разных экземпляров в одноимённом атрибуте будут разные значения:

Так же я могу определить переменную внутри класса и она станет атрибутов всех экземпляров этого класса.
Естественно, это не отменит возможность принудительного переопределения соответствующего атрибута у какого-то экземпляра:

Для экземпляров B и C значение атрибута равно значению по-умолчанию (списку), а для экземпляров E и A переопределено (на число и строку).

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

  • надо определить класс
  • создать объект
  • обратиться к каждому из атрибутов, записав туда кастомные данные
  • обратиться к каждому объекту, передав туда кастомные аргументы

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

Для этого служит метод __init__().

Перепишем наш класс и убедимся, что его работа не изменилась:

Важно:

  • метод __init__(), как и другие методы класса должен принимать как минимум 1 аргумент self
  • к аргументам объекта можно обращаться из метода как аргументам self (например, self.arg)
  • Если аргумент создаётся конструктором __init__, то его не нужно описывать в классе как отдельную переменную

Если для конструктора __init__ мы зададим какие-то аргументы кроме self, то их можно будет передать в конструктор при создании экземпляра объекта, и сразу записать в аргументы, если необходимо.

Давайте создадим чуть более осмысленный класс.
Пусть это будет класс «Точка». Точка у нас будет характеризоваться 2 координатами (x, y).
И сразу зададим метод для нашей точки, вычисляющий расстояние от неё до другой точки (для этого воспользуемся теоремой Пифагора):

Здесь метод dist принимает 2 аргумента: экземпляр текущего объекта и ещё одного, между которыми необходимо найти расстояние.
Поскольку метод применяется к текущему экземпляру, то при вызове метода в скобках я указываю только 2ю точку).

Если я хочу передавать в мой метод аргументы привычным образом (как аргументы в скобках по порядку), то мне необходимо указать полный путь до метода. Он начнётся с имени класса:

Обратите внимание, что 2й способ обращения позволяет использовать классы как хранилище функций даже для стандартных типов данных.

Вот пример такого «хранилища»:

Заметим, что если мы попытаемся напечатать экземпляр объекта (а не его атрибуты, как раньше), то ничего хорошего не получим:

Мы можем понять экземпляром какого класса объект является и на какую область памяти смотрит указатель, но в работе это довольно бесполезно.
Согласитесь, печатая экземпляр списка мы получаем список — это удобно.
Воспользуемся стандартным методом __str__, который вызывается при его печати с помощью print:

Интересный эффект, который можно заметить — после пересоздания класса ранее созданные объекты не меняют своего поведения.

Дело в том, что класс — это тоже объект.

С точки зрения python мы создали 2 объекта. И на базе 1-ого создали несколько экземпляров.
Поэтому чтобы новые методы добавились у объектов, объекты придётся пересоздать.

Давайте заведём ещё 1 экземпляр объекта с точно такими же координатами, что и первый.
В бытовом понимании 2 такие точки «равны». Но что будет, если мы их сравним?

С точки зрения python это 2 разных объекта, а потому они НЕ равны.

Чтобы это исправить можно написать стандартный метод __eq__:

Замечу, что если мы заведём по новому атрибуту у наших точек (например, цвету), то операция сравнения их учитывать не будет:


Геттеры и Сеттеры

Идея геттеров и сеттеров заключается в том, что передавать в экземпляр класса значения атрибутов явно — довольно опасная идея.
Т.к. питон язык с динамический типизацией, то мы можем передать любой тип данных в переменную с одним и тем же именем внутри разных экземпляров (мы так и делали раньше и считали это даже преимуществом)

Но иногда при добавлении атрибута имеет смысл провести валидацию. Или иметь метод который не перезатирает значение, а добавляет (примером такого метода является append для списков).

Добавим в наш класс 2 новых метода:

  • сеттер — setColor — проверяет, что передаётся строка и записывает её в атрибут color экземпляра. Если передан другой тип данных, возвращает ошибку.
  • геттер — getColor — возвращает значение атрибута color текущего экземпляра

По идее на этом этапе надо переписать все методы нашего класса, чтобы в них использовались геттеры и сеттеры вместо прямых обращений к атрибутам.
=)

Наследование

Идея в том, чтобы хранить в классе только необходимые для него объекты и атрибуты.
И структурировать, объединить в иерархию объекты.
Обычно это иллюстрируется на примерах животных: кошечки, собачки, кролики и т.п.
Есть класс животные, он имеет
  • атрибуты: число ног, имя, возраст.
  • методы: геттеры и сеттеры

Теперь создадим класс кошки. Кошки — тоже животные. Поэтому все атрибуты и методы класса животные им тоже присущи. Но кроме этого, у них могут быть свои:
  • атрибуты: имя и т.п.
  • методы: «говорение» (мяу) и т.п.

Чтобы не дублировать код, класс кошки наследуется от класса животные.

Наследник получает все методы и атрибуты родителя, а так же может некоторые из них переопределить (например, если мы сделаем класс «птицы» наследником класса «животные», то, вероятно, ограничение на число ног изменится с 4 до 2), а так же задать собственные атрибуты и методы.

Давайте в нашем примере представим, что наш класс точек — это объекты на географической карте. Точка может использоваться для оформления (например, прокладки маршрута из точек).
А новый класс будет представлять из себя географический маркер: банкомат, достопримечательность, организацию или что-то иное.
Новый класс будет наследником обычной точки, но мы чуть расширим конструктор, чтобы иметь больше атрибутов:

Как видим, наш класс получил возможность использовать сеттер родительского класса, да и стандартная функция __str__ тоже наследуется.

Давайте зададим нашему классу новый метод __str__, чтобы при выводе на печать понимать что это не просто точка, именно маркер. И внутри будем использовать метод __str__ родительского класса:

Т.е. вы всегда можете обратиться к родительским методам у экземпляров дочерних классов, если это необходимо.

Давайте создадим ещё пару классов.

1 будет идентичен предыдущим — это будет класс иконок с геттером и сеттером:

Такие классы ещё иногда называются «примесью», дальше станет понятно почему.

А вот 2 будет интереснее. Это будет класс организации. И мы не станем записывать для него ни атрибутов, ни классов. Но унаследуем его от 2 родителей:

Удивительно, но это работает.
При этом:

  • наследник получает все методы и атрибуты обоих родителей.
  • если у нескольких родителей есть одноимённые методы, то автоматически наследник получит методы того, кто раньше в списке (в нашем случае раньше был гео маркер, поэтому конструктор был унаследован от него, а не от организации).

Наследование от нескольких родителей иногда считается сомнительной практикой, будьте с ним аккуратны.

Переменные класса

Мы уже говорили про атрибуты в качестве хранилища данных (мы даже создавали их внутри класса в самом начале).
Есть ещё одно интересное применение для них — переменная класса. Т.е переменные, которые хранятся в классе, а не в экземпляре.

Модифицируем конструктор организации (добавим заодно поддержку url). В классе создадим переменную tag с первоначальным значением равным 0. В конструкторе же запишем в переменную ID создаваемого экземпляра значение из tag, а tag после этого увеличим на 1:

Таким образом мы получили:

  • в атрибуте ID каждого объекта хранится его уникальный порядковый номер
  • в переменной tag класса хранится число созданных объектов (тут надо быть осторожнее, так как возможно мы захотим уменьшать это число при удалении объекта).

Ну и не забываем, что каждый экземпляр имеет доступ к атрибутам класса помимо собственных, т.е. tag:

Переиспользование методов

Мы уже говорили про наследование методов, мы даже использовали методы родительского класса.
Но можно наследовать часть родительского метода (и собирать из нескольких родительских 1 свой). Сократим наш код организации, унаследовав конструкторы гео маркера и иконки:

Мы совместили 2 наследуемых метода и свой код.
И всё это работает.
=)

Полезные ссылки:



Объектно-ориентированное программирование (ООП) в Python 3 — Real Python

Объектно-ориентированное программирование (ООП) — это метод структурирования программы путем объединения связанных свойств и поведения в отдельные объекты . В этом руководстве вы изучите основы объектно-ориентированного программирования на Python.

Концептуально объекты подобны компонентам системы. Думайте о программе как о своего рода заводской сборочной линии. На каждом этапе сборочной линии компонент системы обрабатывает некоторый материал, в конечном итоге превращая сырье в готовый продукт.

Объект содержит данные, такие как необработанные или предварительно обработанные материалы на каждом этапе сборочной линии, и поведение, такое как действие, которое выполняет каждый компонент сборочной линии.

Из этого руководства вы узнаете, как:

  • Создайте класс , который похож на чертеж для создания объекта
  • Используйте классы для создания новых объектов
  • Модельные системы с наследованием классов

Примечание: Это руководство адаптировано из главы «Объектно-ориентированное программирование (ООП)» в книге Основы Python: Практическое введение в Python 3 .

В книге используется встроенный редактор IDLE Python для создания и редактирования файлов Python и взаимодействия с оболочкой Python, поэтому вы будете время от времени видеть ссылки на IDLE в этом руководстве. Однако у вас не должно возникнуть проблем с запуском примера кода из редактора и среды по вашему выбору.

Что такое объектно-ориентированное программирование в Python?

Объектно-ориентированное программирование — это парадигма программирования, которая предоставляет средства структурирования программ таким образом, чтобы свойства и поведение объединялись в отдельные объекты .

Например, объект может представлять человека с свойствами , такими как имя, возраст и адрес, и поведениями , такими как ходьба, разговор, дыхание и бег. Или это может быть электронное письмо со свойствами, такими как список получателей, тема и текст, а также с поведением, например добавлением вложений и отправкой.

Другими словами, объектно-ориентированное программирование — это подход к моделированию конкретных реальных вещей, таких как автомобили, а также отношений между вещами, такими как компании и сотрудники, студенты и учителя и так далее.ООП моделирует объекты реального мира как программные объекты, с которыми связаны некоторые данные и которые могут выполнять определенные функции.

Другой распространенной парадигмой программирования является процедурное программирование , которое структурирует программу как рецепт, поскольку она предоставляет набор шагов в форме функций и кодовых блоков, которые последовательно выполняются для выполнения задачи.

Ключевой вывод заключается в том, что объекты находятся в центре объектно-ориентированного программирования в Python, не только представляя данные, как в процедурном программировании, но также и в общей структуре программы.

Определите класс в Python

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

Например, предположим, вы хотите отслеживать сотрудников в организации. Вам необходимо сохранить некоторую основную информацию о каждом сотруднике, такую ​​как их имя, возраст, должность и год, в котором они начали работать.

Один из способов сделать это — представить каждого сотрудника в виде списка:

  kirk = ["Джеймс Кирк", 34, "Капитан", 2265]
spock = ["Спок", 35, "Научный сотрудник", 2254]
mccoy = ["Леонард Маккой", "Главный врач", 2266]
  

У этого подхода есть ряд проблем.

Во-первых, это может затруднить управление большими файлами кода. Если вы укажете kirk [0] на несколько строк от того места, где объявлен список kirk , запомните ли вы, что элемент с индексом 0 — это имя сотрудника?

Во-вторых, это может привести к ошибкам, если не у всех сотрудников одинаковое количество элементов в списке.В приведенном выше списке mccoy возраст отсутствует, поэтому mccoy [1] вернет «Главный врач» вместо возраста доктора Маккой.

Отличный способ сделать этот тип кода более управляемым и более удобным в обслуживании — это использовать классы .

Классы и экземпляры

Классы используются для создания пользовательских структур данных. Классы определяют функции, называемые методами , которые определяют поведение и действия, которые объект, созданный из класса, может выполнять со своими данными.

В этом руководстве вы создадите класс Dog , в котором будет храниться некоторая информация о характеристиках и поведении отдельной собаки.

Класс — это образец того, как что-то должно быть определено. На самом деле он не содержит никаких данных. Класс Dog указывает, что имя и возраст необходимы для определения собаки, но он не содержит имени или возраста какой-либо конкретной собаки.

В то время как класс является схемой, экземпляр — это объект, созданный из класса и содержащий реальные данные.Экземпляр класса Dog больше не является планом. Это настоящая собака с таким именем, как Майлз, которому четыре года.

Другими словами, класс похож на форму или анкету. Экземпляр подобен форме, заполненной информацией. Подобно тому, как многие люди могут заполнить одну и ту же форму своей собственной уникальной информацией, многие экземпляры могут быть созданы из одного класса.

Как определить класс

Все определения классов начинаются с ключевого слова class , за которым следует имя класса и двоеточие.Любой код с отступом под определением класса считается частью тела класса.

Вот пример класса Dog :

Тело класса Dog состоит из одного оператора: ключевого слова pass . pass часто используется как заполнитель, указывающий, куда в конечном итоге пойдет код. Это позволяет запускать этот код, не вызывая ошибки Python.

Примечание. Имена классов Python записываются в нотации CapitalizedWords по соглашению.Например, класс для определенной породы собак, такой как джек-рассел-терьер, будет записан как JackRussellTerrier .

Класс Dog сейчас не очень интересен, поэтому давайте немного его украсим, определив некоторые свойства, которые должны иметь все объекты Dog . Мы можем выбирать из ряда свойств, включая имя, возраст, цвет шерсти и породу. Для простоты мы будем использовать имя и возраст.

Свойства, которые должны иметь все объекты Dog , определены в методе .__init __ () . Каждый раз, когда создается новый объект Dog , .__ init __ () устанавливает начальное состояние объекта, присваивая значения свойствам объекта. То есть .__ init __ () инициализирует каждый новый экземпляр класса.

Вы можете указать .__ init __ () любое количество параметров, но первым параметром всегда будет переменная с именем self . Когда создается новый экземпляр класса, он автоматически передается в параметр self в .__init __ () , чтобы для объекта можно было определить новые атрибутов .

Давайте обновим класс Dog с помощью метода .__ init __ () , который создает атрибуты .name и .age :

  класс Собака:
    def __init __ (я, имя, возраст):
        self.name = имя
        self.age = возраст
  

Обратите внимание, что подпись метода .__ init __ () имеет отступ в четыре пробела. В теле метода есть восемь пробелов.Этот отступ жизненно важен. Он сообщает Python, что метод .__ init __ () принадлежит классу Dog .

В теле .__ init __ () есть два оператора, использующие переменную self :

  1. self.name = name создает атрибут с именем name и присваивает ему значение параметра name .
  2. self.age = age создает атрибут с именем age и присваивает ему значение параметра age .

Атрибуты, созданные в .__ init __ () , называются атрибутами экземпляра . Значение атрибута экземпляра зависит от конкретного экземпляра класса. Все объекты Dog имеют имя и возраст, но значения атрибутов name и age будут различаться в зависимости от экземпляра Dog .

С другой стороны, атрибутов класса — это атрибуты, которые имеют одинаковое значение для всех экземпляров класса.Вы можете определить атрибут класса, присвоив значение имени переменной вне .__ init __ () .

Например, следующий класс Dog имеет атрибут класса под названием разновидностей со значением "Canis knownis" :

  класс Собака:
    # Атрибут класса
    разновидности = "Canis knownis"

    def __init __ (я, имя, возраст):
        self.name = имя
        self.age = возраст
  

Атрибуты класса определяются непосредственно под первой строкой имени класса и имеют отступ в четыре пробела.Им всегда должно быть присвоено начальное значение. Когда создается экземпляр класса, автоматически создаются атрибуты класса, которым присваиваются их начальные значения.

Используйте атрибуты класса для определения свойств, которые должны иметь одинаковое значение для каждого экземпляра класса. Используйте атрибуты экземпляра для свойств, которые различаются от одного экземпляра к другому.

Теперь, когда у нас есть класс Dog , давайте создадим несколько собак!

Создание экземпляра объекта в Python

Откройте интерактивное окно IDLE и введите следующее:

>>>
  >>> класс Собака:
...     проходить
  

Это создает новый класс Dog без атрибутов или методов.

Создание нового объекта из класса называется созданием экземпляра объекта. Вы можете создать новый объект Dog , введя имя класса, а затем открывающие и закрывающие круглые скобки:

>>>
  >>> Собака ()
<__ main__.Dog объект в 0x106702d30>
  

Теперь у вас есть новый объект Dog по адресу 0x106702d30 .Эта забавно выглядящая строка букв и цифр представляет собой адрес памяти , который указывает, где в памяти вашего компьютера хранится объект Dog . Обратите внимание, что адрес, который вы видите на экране, будет другим.

Теперь создайте второй объект Dog :

>>>
  >>> Собака ()
<__ main__.Dog объект по адресу 0x0004ccc90>
  

Новый экземпляр Dog расположен по другому адресу памяти. Это потому, что это совершенно новый экземпляр, который полностью отличается от первого экземпляра объекта Dog , который вы создали.

Чтобы увидеть это по-другому, введите следующее:

>>>
  >>> a = Собака ()
>>> b = Собака ()
>>> а == б
Ложь
  

В этом коде вы создаете два новых объекта Dog и назначаете их переменным a и b . Когда вы сравниваете a и b с помощью оператора == , результатом будет False . Несмотря на то, что a и b являются экземплярами класса Dog , они представляют два различных объекта в памяти.

Атрибуты класса и экземпляра

Теперь создайте новый класс Dog с атрибутом класса .species и двумя атрибутами экземпляра с именем .name и .age :

. >>>
  >>> класс Собака:
... разновидности = "Canisiliaris"
... def __init __ (я, имя, возраст):
... self.name = имя
... self.age = возраст
  

Чтобы создать экземпляры объектов этого класса Dog , необходимо указать значения для name и age .Если вы этого не сделаете, Python выдаст TypeError :

. >>>
  >>> Собака ()
Отслеживание (последний вызов последний):
  Файл "", строка 1, в 
    Собака()
TypeError: __init __ () отсутствует 2 обязательных позиционных аргумента: 'name' и 'age'
  

Чтобы передать аргументы параметрам name и age , поместите значения в круглые скобки после имени класса:

>>>
  >>> buddy = Dog ("Приятель", 9)
>>> miles = Dog ("Мили", 4)
  

Это создает два новых экземпляра Dog — один для девятилетней собаки по имени Бадди и один для четырехлетней собаки по имени Майлз.

Метод .__ init __ () класса Dog имеет три параметра, так почему в этом примере ему передаются только два аргумента?

Когда вы создаете экземпляр объекта Dog , Python создает новый экземпляр и передает его первому параметру .__ init __ () . Это по существу удаляет параметр self , поэтому вам нужно беспокоиться только о параметрах name и age .

После создания экземпляров Dog вы можете получить доступ к их атрибутам экземпляра, используя точечную нотацию :

>>>
  >>> дружище.название
'Приятель'
>>> buddy.age
9

>>> miles.name
'Майлз'
>>> miles.age
4
  

Таким же образом можно получить доступ к атрибутам класса:

>>>
  >>> buddy.species
'Canis knownis'
  

Одним из самых больших преимуществ использования классов для организации данных является то, что экземпляры гарантированно будут иметь ожидаемые атрибуты. Все экземпляры Dog имеют атрибуты .species , .name и .age , поэтому вы можете использовать эти атрибуты с уверенностью, зная, что они всегда будут возвращать значение.

Хотя наличие атрибутов гарантировано, их значения могут быть изменены динамически:

>>>
  >>> buddy.age = 10
>>> buddy.age
10

>>> miles.species = "Felis silvestris"
>>> miles.species
'Felis silvestris'
  

В этом примере вы изменяете атрибут .age объекта buddy на 10 . Затем вы меняете атрибут .species объекта miles на «Felis silvestris» , который является разновидностью кошки.Это делает Майлза довольно странной собакой, но это настоящий Python!

Ключевой вывод здесь заключается в том, что пользовательские объекты по умолчанию изменяемы. Объект является изменяемым, если его можно изменять динамически. Например, списки и словари изменяемы, но строки и кортежи неизменны.

Методы экземпляра

Методы экземпляра — это функции, которые определены внутри класса и могут быть вызваны только из экземпляра этого класса. Как и .__ init __ () , первым параметром метода экземпляра всегда является self .

Откройте новое окно редактора в IDLE и введите следующий класс Dog :

  класс Собака:
    разновидности = "Canis knownis"

    def __init __ (я, имя, возраст):
        self.name = имя
        self.age = возраст

    # Метод экземпляра
    def описание (self):
        return f "{self.name} {self.age} лет"

    # Другой метод экземпляра
    def говорить (сам, звук):
        return f "{self.name} говорит {звук}"
  

Этот класс Dog имеет два метода экземпляра:

  1. .description () возвращает строку, отображающую имя и возраст собаки.
  2. .speak () имеет один параметр с именем sound и возвращает строку, содержащую имя собаки и звук, который издает собака.

Сохраните измененный класс Dog в файл с именем dog.py и нажмите F5 , чтобы запустить программу. Затем откройте интерактивное окно и введите следующее, чтобы увидеть методы вашего экземпляра в действии:

>>>
  >>> miles = Dog ("Мили", 4)

>>> миль.описание()
"Майлзу 4 года"

>>> miles.speak ("Гав-гав")
'Майлз говорит Гав-Гав'

>>> miles.speak ("Bow Wow")
"Майлз говорит:" Вау! "
  

В приведенном выше классе Dog .description () возвращает строку, содержащую информацию об экземпляре Dog miles . При написании собственных классов рекомендуется иметь метод, возвращающий строку, содержащую полезную информацию об экземпляре класса. Однако .description () — не самый питонский способ сделать это.

Когда вы создаете объект list , вы можете использовать print () для отображения строки, которая выглядит как список:

>>>
  >>> names = ["Флетчер", "Дэвид", "Дэн"]
>>> печать (имена)
["Флетчер", "Дэвид", "Дэн"]
  

Давайте посмотрим, что произойдет, когда вы напечатаете () объект миль :

>>>
  >>> печать (мили)
<__ main__.Объект "Собака" по адресу 0x00aeff70>
  

Когда вы печатаете (миль) , вы получаете загадочное сообщение о том, что миль — это объект Dog по адресу памяти 0x00aeff70 . Это сообщение бесполезно. Вы можете изменить то, что печатается, определив специальный метод экземпляра с именем .__ str __ () .

В окне редактора измените имя метода .description () класса Dog на .__str __ () :

  класс Собака:
    # Остальные части класса Dog оставьте как есть

    # Замените .description () на __str __ ()
    def __str __ (сам):
        return f "{self.name} {self.age} лет"
  

Сохраните файл и нажмите F5 . Теперь, когда вы печатаете (миль) , вы получаете гораздо более удобный результат:

>>>
  >>> miles = Dog ("Мили", 4)
>>> печать (мили)
"Майлзу 4 года"
  

Методы, подобные .__init __ () и .__ str __ () называются методами dunder , потому что они начинаются и заканчиваются двойным подчеркиванием. Есть много ужасных методов, которые вы можете использовать для настройки классов в Python. Хотя это слишком сложная тема для начинающей книги по Python, понимание dunder-методов является важной частью освоения объектно-ориентированного программирования на Python.

В следующем разделе вы увидите, как расширить свои знания и создать классы из других классов.

Проверьте свое понимание

Разверните блок ниже, чтобы проверить свое понимание:

Создайте класс Car с двумя атрибутами экземпляра:

  1. .color , в котором название цвета автомобиля хранится в виде строки
  2. .
  3. . Пробег , в котором хранится количество миль на автомобиле в виде целого числа

Затем создайте экземпляры двух объектов Car — синего автомобиля с пробегом 20 000 миль и красного автомобиля с пробегом 30 000 миль — и распечатайте их цвета и пробег.Ваш результат должен выглядеть так:

  У синей машины 20 000 миль.
У красной машины 30 000 миль.
  

Вы можете развернуть блок ниже, чтобы увидеть решение:

Сначала создайте класс Car с атрибутами экземпляра .color и .m:

  класс Автомобиль:
    def __init __ (себя, цвет, пробег):
        self.color = цвет
        собственный пробег = пробег
  

Цвет и пробег параметры .__init __ () присваиваются self.color и self.m, что создает два атрибута экземпляра.

Теперь вы можете создать два экземпляра Car :

  blue_car = Автомобиль (цвет = "синий", пробег = 20_000)
red_car = Автомобиль (цвет = "красный", пробег = 30_000)
  

Экземпляр blue_car создается путем передачи значения «синий» в параметр цвета и 20_000 в параметр пробег .Аналогичным образом создается red_car со значениями «красный» и 30_000 .

Чтобы распечатать цвет и пробег каждого объекта Car , вы можете перебрать кортеж , содержащий оба объекта:

  на машину в (blue_car, red_car):
    print (f "У машины {car.color} {car.m900 :,} миль")
  

Строка f в приведенном выше цикле для вставляет атрибуты .color и .m в строку и использует спецификатор формата :, для печати пробега, сгруппированного по тысячам и разделенных запятой.

Окончательный результат выглядит так:

  У синей машины 20 000 миль.
У красной машины 30 000 миль.
  

Когда будете готовы, можете переходить к следующему разделу.

Наследование от других классов в Python

Наследование — это процесс, при котором один класс перенимает атрибуты и методы другого. Вновь сформированные классы называются дочерними классами , а классы, производные от которых являются дочерними классами, называются родительскими классами .

Дочерние классы могут переопределять или расширять атрибуты и методы родительских классов. Другими словами, дочерние классы наследуют все атрибуты и методы родительского объекта, но могут также определять атрибуты и методы, которые уникальны для них самих.

Хотя аналогия не идеальна, вы можете думать о наследовании объектов как о генетическом наследовании.

Возможно, вы унаследовали цвет волос от матери. Это атрибут, с которым вы родились. Допустим, вы решили покрасить волосы в фиолетовый цвет.Предполагая, что у вашей матери нет фиолетовых волос, вы всего лишь переопределили атрибут цвета волос, который вы унаследовали от своей мамы.

Вы также в некотором смысле наследуете свой язык от своих родителей. Если ваши родители говорят по-английски, то вы тоже будете говорить по-английски. А теперь представьте, что вы решили выучить второй язык, например, немецкий. В этом случае вы расширили своих атрибутов, потому что вы добавили атрибут, которого нет у ваших родителей.

Пример парка собак

Представьте на мгновение, что вы находитесь в собачьем парке.В парке много собак разных пород, и все они ведут себя по-разному.

Предположим, теперь вы хотите смоделировать парк собак с помощью классов Python. Класс Dog , который вы написали в предыдущем разделе, может различать собак по имени и возрасту, но не по породе.

Вы можете изменить класс Dog в окне редактора, добавив атрибут .breed :

  класс Собака:
    разновидности = "Canis knownis"

    def __init __ (я, имя, возраст, порода):
        себя.name = имя
        self.age = возраст
        self.breed = порода
  

Методы экземпляра, определенные ранее, здесь опускаются, потому что они не важны для этого обсуждения.

Нажмите F5 , чтобы сохранить файл. Теперь вы можете смоделировать парк собак, создав несколько экземпляров разных собак в интерактивном окне:

>>>
  >>> miles = Dog ("Майлз", 4, "Джек-Рассел-терьер")
>>> buddy = Собака («Бадди», 9, «Такса»)
>>> jack = Собака («Джек», 3, «Бульдог»)
>>> jim = Dog («Джим», 5, «Бульдог»)
  

У каждой породы собак немного разное поведение.Например, у бульдогов низкий лай, который звучит как гав , но у такс более высокий лай, который больше похож на гав .

Используя только класс Dog , вы должны предоставить строку для аргумента sound из .speak () каждый раз, когда вы вызываете его в экземпляре Dog :

>>>
  >>> buddy.speak ("Яп")
'Бадди говорит Яп'

>>> jim.speak ("Гав")
'Джим говорит Гав'

>>> домкрат.говорить ("Гав")
'Джек говорит Гав'
  

Передача строки при каждом вызове .speak () является повторяющейся и неудобной. Более того, строка, представляющая звук, который издает каждый экземпляр Dog , должна определяться его атрибутом .breed , но здесь вам нужно вручную передавать правильную строку в .speak () каждый раз, когда он вызывается.

Вы можете упростить работу с классом Dog , создав дочерний класс для каждой породы собак.Это позволяет расширить функциональность, которую наследует каждый дочерний класс, включая указание аргумента по умолчанию для .speak () .

Родительские классы и дочерние классы

Давайте создадим дочерний класс для каждой из трех упомянутых выше пород: джек-рассел-терьер, такса и бульдог.

Для справки, вот полное определение класса Dog :

  класс Собака:
    разновидности = "Canis knownis"

    def __init __ (я, имя, возраст):
        себя.name = имя
        self.age = возраст

    def __str __ (сам):
        return f "{self.name} {self.age} лет"

    def говорить (сам, звук):
        return f "{self.name} говорит {звук}"
  

Помните, чтобы создать дочерний класс, вы создаете новый класс с его собственным именем, а затем помещаете имя родительского класса в круглые скобки. Добавьте следующее в файл dog.py , чтобы создать три новых дочерних класса класса Dog :

  класс JackRussellTerrier (Собака):
    проходить

класс Такса (Собака):
    проходить

класс Бульдог (Собака):
    проходить
  

Нажмите F5 , чтобы сохранить и запустить файл.Определив дочерние классы, вы можете создать экземпляры собак определенных пород в интерактивном окне:

>>>
  >>> miles = JackRussellTerrier ("Мили", 4)
>>> buddy = Такса ("Бадди", 9)
>>> jack = Бульдог ("Джек", 3)
>>> jim = Бульдог ("Джим", 5)
  

Экземпляры дочерних классов наследуют все атрибуты и методы родительского класса:

>>>
  >>> miles.species
'Canis knownis'

>>> дружище.название
'Приятель'

>>> печать (домкрат)
Джеку 3 года

>>> jim.speak ("Гав")
'Джим говорит Гав'
  

Чтобы определить, к какому классу принадлежит данный объект, вы можете использовать встроенный тип () :

>>>
  >>> тип (мили)
<класс '__main __. JackRussellTerrier'>
  

Что делать, если вы хотите определить, является ли миль также экземпляром класса Dog ? Вы можете сделать это с помощью встроенного isinstance () :

>>>
  >>> isinstance (мили, собака)
Истинный
  

Обратите внимание, что isinstance () принимает два аргумента: объект и класс.В приведенном выше примере isinstance () проверяет, является ли миль экземпляром класса Dog и возвращает True .

Объекты miles , buddy , jack и jim являются экземплярами Dog , но miles не являются экземплярами Bulldog , а jack не являются экземплярами Dachshund :

>>>
  >>> isinstance (мили, бульдог)
Ложь

>>> isinstance (Джек, Такса)
Ложь
  

В более общем смысле, все объекты, созданные из дочернего класса, являются экземплярами родительского класса, хотя они могут не быть экземплярами других дочерних классов.

Теперь, когда вы создали дочерние классы для разных пород собак, давайте дадим каждой породе свое собственное звучание.

Расширение функциональности родительского класса

Поскольку собаки разных пород лают по-разному, вы хотите указать значение по умолчанию для аргумента sound их соответствующих методов .speak () . Для этого вам нужно переопределить .speak () в определении класса для каждой породы.

Чтобы переопределить метод, определенный в родительском классе, вы определяете метод с тем же именем в дочернем классе.Вот как это выглядит для класса JackRussellTerrier :

  класс JackRussellTerrier (Собака):
    def Speak (self, sound = "Arf"):
        return f "{self.name} говорит {звук}"
  

Теперь .speak () определен в классе JackRussellTerrier с аргументом по умолчанию для звука , установленным на "Arf" .

Обновите dog.py с новым классом JackRussellTerrier и нажмите F5 , чтобы сохранить и запустить файл.Теперь вы можете вызвать .speak () в экземпляре JackRussellTerrier без передачи аргумента в sound :

>>>
  >>> miles = JackRussellTerrier ("Мили", 4)
>>> miles.speak ()
'Майлз говорит Арф'
  

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

>>>
  >>> miles.speak ("Гррр")
'Майлз говорит Грр'
  

При наследовании классов следует помнить о том, что изменения родительского класса автоматически распространяются на дочерние классы.Это происходит до тех пор, пока изменяемый атрибут или метод не переопределен в дочернем классе.

Например, в окне редактора измените строку, возвращаемую функцией .speak () в классе Dog :

  класс Собака:
    # Остальные атрибуты и методы оставьте как есть

    # Измените строку, возвращаемую .speak ()
    def говорить (сам, звук):
        return f "{self.name} лай: {звук}"
  

Сохраните файл и нажмите F5 . Теперь, когда вы создаете новый экземпляр Bulldog с именем jim , jim.Speak () возвращает новую строку:

>>>
  >>> jim = Бульдог ("Джим", 5)
>>> jim.speak ("Гав")
'Джим лает: Гав'
  

Однако вызов .speak () в экземпляре JackRussellTerrier не покажет новый стиль вывода:

>>>
  >>> miles = JackRussellTerrier ("Мили", 4)
>>> miles.speak ()
'Майлз говорит Арф'
  

Иногда имеет смысл полностью переопределить метод родительского класса.Но в этом случае мы не хотим, чтобы класс JackRussellTerrier потерял любые изменения, которые могут быть внесены в форматирование выходной строки Dog.speak () .

Для этого вам все равно нужно определить метод .speak () для дочернего класса JackRussellTerrier . Но вместо явного определения выходной строки вам нужно вызвать .speak () класса Dog внутри дочернего класса .speak () , используя те же аргументы, которые вы передали JackRussellTerrier.Говори () .

Вы можете получить доступ к родительскому классу из метода дочернего класса, используя super () :

  класс JackRussellTerrier (Собака):
    def Speak (self, sound = "Arf"):
        вернуть супер (). говорить (звук)
  

Когда вы вызываете super (). Speak (звук) внутри JackRussellTerrier , Python ищет в родительском классе Dog метод .speak () и вызывает его с переменной sound .

Обновите dog.py , добавив новый класс JackRussellTerrier . Сохраните файл и нажмите F5 , чтобы вы могли протестировать его в интерактивном окне:

>>>
  >>> miles = JackRussellTerrier ("Мили", 4)
>>> miles.speak ()
Майлз лает: Арф
  

Теперь, когда вы вызываете miles.speak () , вы увидите результат, отражающий новое форматирование в классе Dog .

Примечание: В приведенных выше примерах иерархия классов очень проста.Класс JackRussellTerrier имеет единственный родительский класс — Dog . В реальных примерах иерархия классов может быть довольно сложной.

super () делает гораздо больше, чем просто ищет метод или атрибут в родительском классе. Он просматривает всю иерархию классов в поисках соответствующего метода или атрибута. Если вы не будете осторожны, super () может дать удивительные результаты.

Проверьте свое понимание

Разверните блок ниже, чтобы проверить свое понимание:

Создайте класс GoldenRetriever , который наследуется от класса Dog .Присвойте аргументу sound для GoldenRetriever.speak () значение по умолчанию "Bark" . Используйте следующий код для родительского класса Dog :

.
  класс Собака:
    разновидности = "Canis knownis"

    def __init __ (я, имя, возраст):
        self.name = имя
        self.age = возраст

    def __str __ (сам):
        return f "{self.name} {self.age} лет"

    def говорить (сам, звук):
        return f "{self.name} говорит {звук}"
  

Вы можете развернуть блок ниже, чтобы увидеть решение:

Создайте класс с именем GoldenRetriever , который наследуется от класса Dog и переопределяет .Speak () метод:

  класс GoldenRetriever (Собака):
    def Speak (self, sound = "Лай"):
        вернуть супер (). говорить (звук)
  

Параметр звук в GoldenRetriever.speak () получает значение по умолчанию «Лай» . Затем super () используется для вызова метода .speak () родительского класса с тем же аргументом, переданным в sound , что и метод .speak () класса GoldenRetriever .

Заключение

Из этого руководства вы узнали об объектно-ориентированном программировании (ООП) на Python. Большинство современных языков программирования, таких как Java, C # и C ++, следуют принципам ООП, поэтому полученные здесь знания будут применимы независимо от того, куда приведет ваша карьера программиста.

В этом руководстве вы узнали, как:

  • Определите класс , который является своего рода схемой для объекта
  • Создать экземпляр объекта из класса
  • Используйте атрибутов и методы для определения свойств и поведения объекта
  • Используйте наследование для создания дочерних классов из родительского класса
  • Ссылка на метод родительского класса с помощью super ()
  • Проверить, наследуется ли объект от другого класса, используя isinstance ()

Если вам понравилось то, что вы узнали из этого примера из книги Основы Python: Практическое введение в Python 3 , то обязательно прочтите оставшуюся часть книги.

OOP Python Tutorial: объектно-ориентированное программирование

Объектно-ориентированное программирование

Общее введение

Хотя Python является объектно-ориентированным языком без суеты и придирок, мы до сих пор намеренно избегали рассмотрения объектно-ориентированного программирования (ООП) в предыдущих главах нашего руководства по Python. Мы пропустили ООП, потому что убеждены, что легче и увлекательнее начать изучать Python, не зная всех деталей объектно-ориентированного программирования.

Несмотря на то, что мы избегали ООП, оно всегда присутствовало в упражнениях и примерах нашего курса. Мы использовали объекты и методы из классов, не объясняя должным образом их предысторию ООП. В этой главе мы рассмотрим то, чего до сих пор не хватало. Мы предоставим введение в принципы объектно-ориентированного программирования в целом и в особенности ООП-подхода Python. ООП — один из самых мощных инструментов Python, но, тем не менее, вам не обязательно его использовать, т.е.е. вы также можете писать мощные и эффективные программы без него.

Хотя многие компьютерщики и программисты считают ООП современной парадигмой программирования, ее корни уходят в 1960-е годы. Первым языком программирования, использующим объекты, был Simula 67. Как следует из названия, Simula 67 была представлена ​​в 1967 году. Важным прорывом в объектно-ориентированном программировании стал язык программирования Smalltalk в 1970-х.

Вы узнаете четыре основных принципа объектной ориентации и то, как Python работает с ними, в следующем разделе этого руководства по объектно-ориентированному программированию:

  • Инкапсуляция
  • Абстракция данных
  • Полиморфизм
  • Наследование

Прежде чем мы начнем раздел о том, как ООП используется в Python, мы хотим дать вам общее представление об объектно-ориентированном программировании.Для этого хотим обратить ваше внимание на публичную библиотеку. Давайте подумаем о таком огромном, как «Британская библиотека» в Лондоне или «Нью-Йоркская публичная библиотека» в Нью-Йорке. Если это поможет, вы можете представить себе библиотеки в Париже, Берлине, Оттаве или Торонто *. Каждый из них содержит организованную коллекцию книг, периодических изданий, газет, аудиокниг, фильмов и так далее.

Как правило, существует два противоположных способа хранения материала в библиотеке. Вы можете использовать метод «закрытого доступа», когда товар не отображается на открытых полках.В этой системе обученный персонал доставляет книги и другие публикации пользователям по запросу. Другой способ работы библиотеки — полки с открытым доступом, также известные как «открытые полки». «Открытый» означает открытый для всех пользователей библиотеки, а не только специально обученный персонал. В этом случае книги выставляются открыто. Императивные языки, такие как C, можно рассматривать как полочные библиотеки с открытым доступом. Пользователь может все. Пользователь должен найти книги и положить их обратно на нужную полку. Несмотря на то, что это хорошо для пользователя, в долгосрочной перспективе это может привести к серьезным проблемам.Например, некоторые книги будут потеряны, поэтому их будет сложно найти снова. Как вы уже догадались, «закрытый доступ» можно сравнить с объектно-ориентированным программированием. Аналогию можно увидеть так: книги и другие публикации, которые предлагает библиотека, подобны данным в объектно-ориентированной программе. Доступ к книгам ограничен, как и доступ к данным в ООП. Получить или вернуть книгу можно только через сотрудников. Штатные функции аналогичны методам в ООП, которые контролируют доступ к данным.Таким образом, данные — часто называемые атрибутами — в такой программе можно рассматривать как скрытые и защищенные оболочкой, и к ним могут получить доступ только специальные функции, обычно называемые методами в контексте ООП. Помещение данных в «оболочку» называется инкапсуляция . Таким образом, библиотеку можно рассматривать как класс, а книгу — как экземпляр или объект этого класса. Вообще говоря, объект определяется классом. Класс — это формальное описание того, как спроектирован объект, то есть какие атрибуты и методы он имеет.Эти объекты также называются экземплярами. Выражения в большинстве случаев используются как синонимы. Не следует путать класс с объектом.

ООП на Python

Хотя мы не говорили о классах и объектной ориентации в предыдущих главах, мы работали с классами все время. Фактически, в Python все является классом. Гвидо ван Россум разработал язык по принципу «все первоклассно». Он писал: «Одной из моих целей в Python было сделать так, чтобы все объекты были« первоклассными ».«Этим я имел в виду, что хотел, чтобы все объекты, которым можно было присвоить имя на языке (например, целые числа, строки, функции, классы, модули, методы и т. Д.), Имели одинаковый статус. То есть, они могли быть назначены переменные, помещенные в списки, сохраненные в словарях, переданные в качестве аргументов и т. д. » (Блог, История Python, 27 февраля 2009 г.) Другими словами, «все» обрабатывается одинаково, все является классом: функции и методы являются значениями, такими же, как списки, целые числа или числа с плавающей запятой. Каждый из них является экземплярами своих соответствующих классов.

Python 3 Объектно-ориентированное программирование — третье издание

Давайте свяжем все наши новые объектно-ориентированные знания вместе, пройдя несколько итераций объектно-ориентированного проектирования на довольно реальном примере. Система, которую мы будем моделировать, представляет собой библиотечный каталог. Библиотеки отслеживали свои запасы на протяжении веков, первоначально используя карточные каталоги, а в последнее время — электронные реестры. В современных библиотеках есть веб-каталоги, которые мы можем запрашивать у себя дома.

Начнем с анализа.Местный библиотекарь попросил нас написать новую программу-каталог карточек, потому что их старая программа на базе Windows XP уродлива и устарела. Это не дает нам подробностей, но прежде чем мы начнем запрашивать дополнительную информацию, давайте рассмотрим, что мы уже знаем о каталогах библиотек.

Каталоги содержат списки книг. Люди ищут их, чтобы найти книги по определенным предметам, с определенными названиями или определенным автором. Книги можно однозначно идентифицировать по международному стандартному номеру книги ( ISBN ).Каждая книга имеет номер в десятичной системе Дьюи ( DDS ), который помогает найти ее на определенной полке.

Этот простой анализ показывает нам некоторые очевидные объекты в системе. Мы быстро определяем Книга как наиболее важный объект с уже упомянутыми несколькими атрибутами, такими как автор, название, тема, ISBN и номер DDS, а также каталог как своего рода менеджер для книг.

Мы также замечаем несколько других объектов, которые могут или не могут нуждаться в моделировании в системе.Для целей каталогизации все, что нам нужно для поиска книги по автору, — это атрибут author_name в книге. Однако авторы также являются объектами, и мы можем захотеть сохранить некоторые другие данные об авторе. Размышляя над этим, мы можем вспомнить, что у некоторых книг несколько авторов. Внезапно идея иметь один атрибут author_name для объектов кажется немного глупой. Список авторов, связанных с каждой книгой, явно лучше.

Связь между автором и книгой явно связана с ассоциацией, поскольку вы никогда не скажете , что книга является автором (это не наследование), и утверждение у книги есть автор , хотя грамматически правильное, не означает, что авторы являются частью книг (это не агрегация).Действительно, любой автор может быть связан с несколькими книгами.

Мы также должны обратить внимание на существительное (существительные всегда являются хорошими кандидатами для объектов) полка . Полка — это объект, который нужно смоделировать в системе каталогизации? Как мы идентифицируем индивидуальную полку? Что произойдет, если книга будет сохранена в конце одной полки, а затем перемещена в начало следующей полки, потому что новая книга была вставлена ​​на предыдущую полку?

DDS был разработан, чтобы помочь найти физические книги в библиотеке.Таким образом, хранения атрибута DDS вместе с книгой должно быть достаточно, чтобы найти его, независимо от того, на какой полке он хранится. Таким образом, мы можем, по крайней мере, на время удалить полку из нашего списка конкурирующих объектов.

Другой сомнительный объект в системе — это пользователь. Нужно ли нам что-нибудь знать о конкретном пользователе, например, его имя, адрес или список просроченных книг? Пока что библиотекарь сказал нам только, что им нужен каталог; они ничего не сказали об отслеживании подписок или просроченных уведомлений.В глубине души мы также отмечаем, что авторы и пользователи — это разные люди; здесь в будущем могут появиться полезные отношения наследования.

В целях каталогизации мы решили, что нам пока не нужно идентифицировать пользователя. Мы можем предположить, что пользователь будет искать в каталоге, но нам не нужно активно моделировать их в системе, кроме предоставления интерфейса, который позволяет им выполнять поиск.

Мы определили несколько атрибутов в книге, но какими свойствами обладает каталог? Есть ли в какой-либо библиотеке более одного каталога? Нужно ли их однозначно идентифицировать? Очевидно, что каталог каким-то образом должен содержать коллекцию книг, но этот список, вероятно, не является частью общедоступного интерфейса.

А как насчет поведения? В каталоге явно нужен метод поиска, возможно, отдельные по авторам, названиям и тематикам. Есть ли какое-нибудь поведение в книгах? Нужен ли метод предварительного просмотра? Или предварительный просмотр можно определить по атрибуту первой страницы, а не по методу?

Вопросы в предыдущем обсуждении являются частью фазы объектно-ориентированного анализа. Но в сочетании с вопросами мы уже определили несколько ключевых объектов, которые являются частью дизайна. Действительно, то, что вы только что видели, — это несколько микролитров между анализом и дизайном.

Скорее всего, все эти итерации произойдут при первой встрече с библиотекарем. Однако перед этой встречей мы уже можем набросать самый базовый дизайн для объектов, которые мы конкретно идентифицировали, а именно:

Вооружившись этой базовой схемой и карандашом для интерактивного улучшения, мы встречаемся с библиотекарем. Они говорят нам, что это хорошее начало, но библиотеки обслуживают не только книги; у них также есть DVD, журналы и компакт-диски, ни у одного из которых нет номера ISBN или DDS.Однако все эти типы элементов можно однозначно идентифицировать по номеру UPC. Напоминаем библиотекарю, что они должны найти предметы на полке, и эти предметы, вероятно, не организованы СКП. Библиотекарь объясняет, что каждый тип организован по-своему. Компакт-диски — это в основном аудиокниги, а их в наличии всего два десятка, поэтому они организованы по фамилии автора. DVD-диски разделены по жанрам и далее упорядочены по названиям. Журналы сгруппированы по заголовкам, а затем уточняются по томам и номерам выпусков.Книги, как мы уже догадались, организованы по номеру DDS.

Не имея предыдущего опыта объектно-ориентированного проектирования, мы могли бы рассмотреть возможность добавления отдельных списков DVD-дисков, компакт-дисков, журналов и книг в наш каталог и выполнять поиск по каждому из них по очереди. Проблема в том, что, за исключением некоторых расширенных атрибутов и определения физического местоположения элемента, все эти элементы ведут себя примерно одинаково. Это работа по наследству! Мы быстро обновляем нашу диаграмму UML следующим образом:

Библиотекарь понимает суть нарисованной нами схемы, но его немного смущает функция locate .Мы объясняем, используя конкретный вариант использования, когда пользователь ищет слово bunnies . Пользователь сначала отправляет поисковый запрос в каталог. Каталог запрашивает свой внутренний список предметов и находит книгу и DVD с кроликами в названии. На этом этапе каталогу все равно, содержит ли он DVD, книгу, компакт-диск или журнал; что касается каталога, все позиции идентичны. Однако пользователь хочет знать, как найти физические предметы, поэтому каталог будет упущен, если он просто вернет список названий.Таким образом, он вызывает метод locate для двух обнаруженных элементов. Метод locate книги возвращает номер DDS, который можно использовать для поиска полки, на которой находится книга. DVD определяется путем возврата жанра и названия DVD. Затем пользователь может посетить раздел DVD, найти раздел, содержащий этот жанр, и найти конкретный DVD, отсортированный по заголовкам.

Как мы объясняем, мы набросаем схему последовательности операций UML , объясняющую, как взаимодействуют различные объекты:

В то время как диаграммы классов описывают отношения между классами, диаграммы последовательностей описывают определенные последовательности сообщений, передаваемых между объектами.Пунктирная линия, свисающая с каждого объекта, — это линия жизни , описывающая время жизни объекта. Более широкие прямоугольники на каждой линии жизни представляют активную обработку в этом объекте (где нет прямоугольника, объект в основном сидит без дела, ожидая, что что-то произойдет). Горизонтальные стрелки между линиями жизни указывают на конкретные сообщения. Сплошные стрелки представляют вызываемые методы, а пунктирные стрелки со сплошными головками представляют значения, возвращаемые методом.

Половинки стрелок указывают на асинхронные сообщения, отправленные объекту или от него.Асинхронное сообщение обычно означает, что первый объект вызывает метод второго объекта, который немедленно возвращается. После некоторой обработки второй объект вызывает метод первого объекта, чтобы присвоить ему значение. Это отличается от обычных вызовов методов, которые выполняют обработку в методе и немедленно возвращают значение.

Диаграммы последовательностей, как и все диаграммы UML, лучше всего использовать только тогда, когда они необходимы. Нет смысла рисовать диаграмму UML для рисования диаграммы.Однако, когда вам нужно сообщить о серии взаимодействий между двумя объектами, диаграмма последовательности является очень полезным инструментом.

К сожалению, наша диаграмма классов до сих пор выглядит запутанной. Мы замечаем, что актеры на DVD и артисты на компакт-дисках — это разные люди, но к ним относятся иначе, чем к авторам книг. Библиотекарь также напоминает нам, что большинство их компакт-дисков — это аудиокниги, у которых есть авторы, а не художники.

Как мы можем работать с разными людьми, которые вносят свой вклад в создание названия? Одна очевидная реализация — создать класс Person с именем человека и другими соответствующими деталями, а затем создать его подклассы для художников, авторов и актеров.Однако действительно ли здесь необходимо наследование? В целях поиска и каталогизации нам все равно, что актерское мастерство и писательство — это два совершенно разных вида деятельности. Если бы мы проводили экономическое моделирование, было бы разумно выделить отдельные классы актеров и авторов и разные методы calculate_income и perform_job , но для целей каталогизации достаточно знать, какой вклад внес человек в элемент. Поразмыслив над этим, мы обнаруживаем, что все элементы имеют один или несколько объектов Contributor , поэтому мы перемещаем отношение автора из книги в ее родительский класс:

Множественность отношения Contributor / LibraryItem составляет многие ко многим , на что указывает символ * на обоих концах одной связи.Любой элемент библиотеки может иметь более одного участника (например, несколько актеров и режиссер на DVD). Многие авторы пишут много книг, поэтому их можно прикрепить к нескольким объектам библиотеки.

Это небольшое изменение, хотя и выглядит немного чище и проще, но лишено важной информации. Мы все еще можем сказать, кто внес свой вклад в конкретный элемент библиотеки, но мы не знаем, как они внесли свой вклад. Они были режиссером или актером? Они написали аудиокнигу или они были голосом, который рассказывал книгу?

Было бы неплохо, если бы мы могли просто добавить атрибут Contributor_type в класс Contributor , но это развалится, когда мы имеем дело с людьми с разносторонними талантами, которые одновременно являются авторами книг и режиссерами фильмов.

Один из вариантов — добавить атрибуты к каждому из наших подклассов LibraryItem для хранения необходимой нам информации, например, Author в Book или Artist на CD , а затем установить связь со всеми этими свойствами укажите на класс Contributor . Проблема в том, что мы теряем много полиморфной элегантности. Если мы хотим перечислить участников элемента, мы должны искать определенные атрибуты этого элемента, такие как Authors или Actors .Мы можем решить эту проблему, добавив метод GetContributors в класс LibraryItem , который подклассы могут переопределить. Тогда каталогу никогда не нужно знать, какие атрибуты запрашивают объекты; мы абстрагировали публичный интерфейс:

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

Теперь, когда мы изучили наследование как вариант и обнаружили, что оно желательно, мы можем вернуться к нашей предыдущей диаграмме на основе композиции, где Contributor был присоединен непосредственно к LibraryItem . Поразмыслив, мы можем увидеть, что на самом деле нам нужно только добавить еще одно отношение к совершенно новому классу, чтобы определить тип участника. Это важный шаг в объектно-ориентированном дизайне. Теперь мы добавляем в проект класс, который предназначен для поддержки других объектов, а не для моделирования какой-либо части начальных требований.Мы реорганизуем дизайн, чтобы упростить объекты в системе, а не объекты в реальной жизни. Рефакторинг — важный процесс поддержки программы или дизайна. Целью рефакторинга является улучшение дизайна путем перемещения кода, удаления дублированного кода или сложных взаимосвязей в пользу более простых и элегантных дизайнов.

Этот новый класс состоит из Contributor и дополнительного атрибута, определяющего тип вклада человека в данный LibraryItem .Таких вкладов в конкретный LibraryItem может быть много, и один участник может одинаково вносить вклад в разные элементы. Следующая диаграмма очень хорошо передает эту конструкцию:

Сначала это отношение композиции выглядит менее естественным, чем отношения на основе наследования. Однако у него есть то преимущество, что мы можем добавлять новые типы вкладов без добавления нового класса в дизайн. Наследование наиболее полезно, когда подклассы имеют какую-то специализацию .Специализация — это создание или изменение атрибутов или поведения подкласса, чтобы он чем-то отличался от родительского класса. Кажется глупым создавать кучу пустых классов исключительно для идентификации различных типов объектов (такое отношение менее распространено среди Java и других программистов, все является объектом , но это распространено среди более прагматичных дизайнеров Python). Если мы посмотрим на версию наследования диаграммы, мы увидим группу подклассов, которые на самом деле ничего не делают:

Иногда важно понимать, когда не следует использовать объектно-ориентированные принципы.Этот пример того, когда не следует использовать наследование, является хорошим напоминанием о том, что объекты — это просто инструменты, а не правила.

Объектно-ориентированное программирование на Python

Объектно-ориентированное программирование

Python — это язык программирования с несколькими парадигмами. Он поддерживает разные подходы к программированию.

Один из популярных подходов к решению проблемы программирования — создание объектов. Это известно как объектно-ориентированное программирование (ООП).

Объект имеет две характеристики:

Возьмем пример:

Попугай — это объект, так как он обладает следующими свойствами:

  • имя, возраст, цвет как атрибуты
  • пение, танец как поведение

Концепция ООП в Python направлена ​​на создание повторно используемого кода.Эта концепция также известна как СУХОЙ (Не повторяйся).

В Python концепция ООП следует некоторым основным принципам:


Класс

Класс — это план объекта.

Мы можем думать о классе как об эскизе попугая с ярлыками. Он содержит все подробности о имени, цвете, размере и т. Д. Основываясь на этих описаниях, мы можем изучить попугая. Здесь попугай — это объект.

Примером для класса попугая может быть:

класс Попугай:
    пройти 

Здесь мы используем ключевое слово class для определения пустого класса Parrot .Из класса мы создаем экземпляры. Экземпляр — это конкретный объект, созданный из определенного класса.


Объект

Объект (экземпляр) — это экземпляр класса. Когда класс определен, определяется только описание объекта. Таким образом, ни память, ни хранилище не выделяются.

Пример объекта класса попугай может быть:

obj = Попугай () 

Здесь obj — это объект класса Parrot .

Предположим, у нас есть данные о попугаях.Теперь мы собираемся показать, как построить класс и объекты попугаев.

Пример 1: Создание класса и объекта в Python

  класс Попугай:

    # атрибут класса
    разновидности = "птица"

    # атрибут экземпляра
    def __init __ (я, имя, возраст):
        self.name = имя
        self.age = возраст

# создать экземпляр класса Parrot
blu = Попугай ("Голубой", 10)
woo = Попугай ("Ву", 15)

# доступ к атрибутам класса
print ("Blu is a {}". format (blu .__ class __. разновидности))
print ("Ву тоже {}".формат (ву .__ класс __. виды))

# доступ к атрибутам экземпляра
print ("{} исполнилось {} лет" .format (blu.name, blu.age))
print ("{} {} лет" .format (woo.name, woo.age))  

Выход

  Blu - птица
Ву тоже птица
Блю 10 лет
Ву 15 лет  

В приведенной выше программе мы создали класс с именем Parrot . Затем мы определяем атрибуты. Атрибуты — это характеристика объекта.

Эти атрибуты определены внутри метода класса __init__ .Это метод инициализатора, который запускается первым при создании объекта.

Затем мы создаем экземпляры класса Parrot . Здесь blu и woo — это ссылки (значения) на наши новые объекты.

Мы можем получить доступ к атрибуту класса, используя __class __. Разновидности . Атрибуты класса одинаковы для всех экземпляров класса. Точно так же мы получаем доступ к атрибутам экземпляра, используя blu.name и blu.age . Однако атрибуты экземпляра различны для каждого экземпляра класса.

Чтобы узнать больше о классах и объектах, перейдите в раздел Классы и объекты Python


Методы

Методы — это функции, определенные внутри тела класса. Они используются для определения поведения объекта.

Пример 2: Создание методов в Python

  класс Попугай:
    
    # атрибутов экземпляра
    def __init __ (я, имя, возраст):
        self.name = имя
        self.age = возраст
    
    # метод экземпляра
    def Sing (я, песня):
        верните "{} sings {}".формат (собственное имя, песня)

    def dance (сам):
        return "{} теперь танцует" .format (self.name)

# создать экземпляр объекта
blu = Попугай ("Голубой", 10)

# вызываем наши методы экземпляра
print (blu.sing ("Счастливый"))
печать (blu.dance ())  

Выход

  Blu поет 'Happy'
Blu сейчас танцует  

В приведенной выше программе мы определяем два метода: sing () и dance () . Они называются методами экземпляра, потому что они вызываются для объекта экземпляра i.e blu .


Наследование

Наследование — это способ создания нового класса для использования деталей существующего класса без его изменения. Вновь сформированный класс является производным классом (или дочерним классом). Точно так же существующий класс является базовым классом (или родительским классом).

Пример 3: Использование наследования в Python

  # родительский класс
класс Bird:
    
    def __init __ (сам):
        print («Птица готова»)

    def whoisThis (я):
        print ("Птица")

    def плавать (сам):
        print («Плыви быстрее»)

# дочерний класс
класс Пингвин (Птица):

    def __init __ (сам):
        # вызов функции super ()
        супер().__в этом__()
        print («Пингвин готов»)

    def whoisThis (я):
        print («Пингвин»)

    def run (self):
        print («Беги быстрее»)

пегги = Пингвин ()
peggy.whoisThis ()
peggy.swim ()
peggy.run ()  

Выход

  Птица готова
Пингвин готов
Пингвин
Плыви быстрее
Беги быстрее  

В приведенной выше программе мы создали два класса, то есть Bird (родительский класс) и Penguin (дочерний класс). Дочерний класс наследует функции родительского класса.Мы можем видеть это из метода swim () .

Опять же, дочерний класс изменил поведение родительского класса. Мы можем видеть это из метода whoisThis () . Кроме того, мы расширяем функции родительского класса, создавая новый метод run () .

Кроме того, мы используем функцию super () внутри метода __init __ () . Это позволяет нам запускать метод __init __ () родительского класса внутри дочернего класса.


Инкапсуляция

Используя ООП в Python, мы можем ограничить доступ к методам и переменным. Это предотвращает прямую модификацию данных, которая называется инкапсуляцией. В Python мы обозначаем частные атрибуты с помощью подчеркивания в качестве префикса, то есть одиночного _ или двойного __ .

Пример 4: Инкапсуляция данных в Python

  класс Компьютер:

    def __init __ (сам):
        self .__ maxprice = 900

    def sell (self):
        print ("Цена продажи: {}".формат (self .__ maxprice))

    def setMaxPrice (self, price):
        self .__ maxprice = цена

c = Компьютер ()
c.sell ()

# изменить цену
c .__ maxprice = 1000
c.sell ()

# использование функции установки
c.setMaxPrice (1000)
c.sell ()  

Выход

  Цена продажи: 900
Цена продажи: 900
Цена продажи: 1000  

В приведенной выше программе мы определили класс Computer .

Мы использовали метод __init __ () для хранения максимальной продажной цены Computer .Мы пытались изменить цену. Однако мы не можем его изменить, потому что Python обрабатывает __maxprice как частные атрибуты.

Как показано, чтобы изменить значение, мы должны использовать функцию установки, то есть setMaxPrice () , которая принимает цену в качестве параметра.


Полиморфизм

Полиморфизм — это способность (в ООП) использовать общий интерфейс для нескольких форм (типов данных).

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

Пример 5: Использование полиморфизма в Python

  класс Попугай:

    def fly (self):
        print («Попугай умеет летать»)
    
    def плавать (сам):
        print («Попугай не умеет плавать»)

класс Пингвин:

    def fly (self):
        print («Пингвин не умеет летать»)
    
    def плавать (сам):
        print («Пингвин умеет плавать»)

# Общий интерфейс
def flying_test (птица):
    bird.fly ()

#instantiate objects
blu = Попугай ()
пегги = Пингвин ()

# передача объекта
летающий_тест (blu)
fly_test (пегги)  

Выход

  Попугай умеет летать
Пингвин не умеет летать  

В приведенной выше программе мы определили два класса Parrot и Penguin .У каждого из них есть общий метод fly () . Однако их функции разные.

Чтобы использовать полиморфизм, мы создали общий интерфейс, то есть функцию flying_test () , которая принимает любой объект и вызывает метод объекта fly () . Таким образом, когда мы передали объекты blu и peggy в функции flying_test () , она работала эффективно.


Ключевые моменты, о которых следует помнить:

  • Объектно-ориентированное программирование делает программу простой и эффективной для понимания.
  • Поскольку класс является общедоступным, код можно использовать повторно.
  • Данные безопасны и надежны благодаря абстракции данных.
  • Полиморфизм позволяет использовать один и тот же интерфейс для разных объектов, поэтому программисты могут писать эффективный код.

Объектно-ориентированное программирование на Python | Набор 1 (класс, объект и члены)

Ниже представлена ​​простая программа Python, которая создает класс с помощью одного метода.

Python

класс Тест:

def весело ( self ):

печать ( "Привет" )

obj = Test ()

obj.fun ()

Вывод:

 Hello 

Как мы видим выше, мы создаем новый класс, используя оператор class и имя класса. За ним следует блок операторов с отступом, которые составляют тело класса. В этом случае мы определили единственный метод в классе.
Затем мы создаем объект / экземпляр этого класса, используя имя класса, за которым следует пара круглых скобок.

Объект:

Объект — это сущность, с которой связаны состояние и поведение.Это может быть любой объект реального мира, такой как мышь, клавиатура, стул, стол, ручка и т. Д. Целые числа, строки, числа с плавающей запятой, даже массивы и словари — все это объекты. Более конкретно, любое целое число или любая отдельная строка является объектом. Число 12 — это объект, строка «Hello, world» — это объект, список — это объект, который может содержать другие объекты, и так далее. Вы все время использовали предметы и, возможно, даже не осознавали этого.


Класс:

Класс — это проект, который определяет переменные и методы (характеристики), общие для всех объектов определенного типа.

Пример: Если Автомобиль является классом, то Audi A6 является объектом класса Автомобиль. Все автомобили имеют схожие черты, такие как 4 колеса, 1 руль, окна, тормозные диски и т. Д. Audi A6 (объект «Автомобиль») обладает всеми этими характеристиками.

Методы класса self

  1. должны иметь дополнительный первый параметр в определении метода. Мы не указываем значение для этого параметра при вызове метода, Python предоставляет его
  2. Если у нас есть метод, который не принимает аргументов, то у нас все равно должен быть один аргумент — self.См. Fun () в приведенном выше простом примере.
  3. Это похоже на этот указатель в C ++ и эту ссылку в Java.

Когда мы вызываем метод этого объекта как myobject.method (arg1, arg2), он автоматически преобразуется Python в MyClass.method (myobject, arg1, arg2) — это все, о чем идет речь в специальном self.

Метод __init__
Метод __init__ аналогичен конструкторам в C ++ и Java. Он запускается, как только создается экземпляр объекта класса.Этот метод полезен для любой инициализации, которую вы хотите выполнить с вашим объектом.

Python

класс Человек:

def __init __ ( self , имя): 8

900 .name = имя

def say_hi ( self ):

print ( 'Привет, мое имя это , self .имя)

p = Человек ( 'Shwetanshu' )

p.say_hi ()

Вывод:

 Привет, меня зовут Шветанс. 

Здесь мы определяем метод __init__ как принимающий имя параметра (вместе с обычным self). .

Переменные класса и экземпляра (или атрибуты)
В Python переменные экземпляра — это переменные, значение которых присваивается внутри конструктора или метода с помощью self.
Переменные класса — это переменные, значение которых присваивается в классе.

Python

класс CSS студент:

поток = 'cse' 9 9002 967

0

def __init __ ( self , roll):

self .рулон = рулон

a = CSStudent ( 101 )

b = CSStudent (

102

0)

8

print (a.stream)

print (b.stream)

print (a.roll)

print (CSStudent .stream)

Мы также можем определять переменные экземпляра внутри обычных методов.

Python

класс CSStudent:

поток

02

0

9000 9000 9000 9000

def __init __ ( self , roll):

self .roll = roll

def setAddress ( self , адрес):

self .address address = .

def getAddress ( self ):

return self .адрес

a = CSStudent ( 101 )

a.setAddress ( "Noida, UP" )

print (a.getAddress ())

Вывод:

 Noida, UP 


Как создать пустой класс?
Мы можем создать пустой класс, используя инструкцию pass в Python.

Объектно-ориентированное программирование на Python | Набор 2 (Скрытие данных и печать объектов)

-xa0
Автор статьи Shwetanshu Rohatgi .Пожалуйста, напишите комментарии, если вы обнаружите что-то неправильное, или вы хотите поделиться дополнительной информацией по теме, обсужденной выше

Внимание компьютерщик! Укрепите свои основы с помощью курса Python Programming Foundation и изучите основы.

Для начала подготовьтесь к собеседованию. Расширьте свои концепции структур данных с помощью курса Python DS . И чтобы начать свое путешествие по машинному обучению, присоединяйтесь к Машинное обучение - базовый курс


ООП на Python | Набор 3 (наследование, примеры объекта, issubclass и super)

Мы обсудили следующие темы по объектно-ориентированному программированию в Python

В этой статье рассматривается наследование.

Одно из главных преимуществ объектно-ориентированного программирования - многократное использование. Наследование - один из механизмов достижения того же. При наследовании класс (обычно называемый суперклассом) наследуется другим классом (обычно называемым подклассом). Подкласс добавляет к суперклассу некоторые атрибуты.

Ниже приведен образец программы Python, демонстрирующий, как в Python реализовано наследование.

класс Лицо ( объект ):

__ по умолчанию , имя):

сам .имя = имя

def getName ( self ):

return self .name 9002

def isEmployee ( self ):

return False

Employee

0 класс Человек):

def isEmployee ( self ):

возврат True

9000 emp = 900 68 Человек ( "Geek1" )

печать (жирный шрифт)getName (), emp.isEmployee ())

emp = Сотрудник ( "Geek2" )

print (emp.getName (), emp. isEmployee ())

Вывод:
('Geek1', ложь)
('Geek2', правда)
 

Как проверить, является ли класс подклассом другого?
Python предоставляет функцию issubclass (), которая напрямую сообщает нам, является ли класс подклассом другого класса.

класс База ( объект ):

проход

класс Производный 9677 967 967 9: пройти

печать ( isubclass (производный, базовый))

печать ( issubclass (базовый, производный))

d = Производное ()

b = База ()

печать ( isinstance (b, производное)) 9000

печать ( isinstance (d, Base)) 9 0007

Выход:
Истинный
Ложь
Ложь
Истинный
 

Что такое класс объекта?
Как класс Java Object в Python (начиная с версии 3.x) объект является корнем всех классов.

В Python 3.x «class Test (объект)» и «class Test» одинаковы.
В Python 2.x «class Test (object)» создает класс с объектом в качестве родительского (называемый классом нового стиля), а «class Test» создает класс старого стиля (без родительского объекта). Обратитесь к этому для более подробной информации.

Поддерживает ли Python множественное наследование?
В отличие от Java и, как и C ++, Python поддерживает множественное наследование. Мы указываем все родительские классы в виде списка, разделенного запятыми, в скобках.



класс Base1 ( объект ):

def __init __ ( self ):

selfst = «Geek1»

печать «Base1»

класс Base2 ( объект ):

9000__ по умолчанию __ ( собственное ):

собственное .str2 = "Geek2"

печать "Base2"

класс Производный (Base1, Base2):

9000__2 defit __ self ):

Base1 .__ init __ ( self )

Base2.__init __ ( self )

print «Производный»

def printStrs ( self ):

print

( self .str1, self .str2)

ob = Получено ()

ob.printStrs ()

Вывод:
Base1
Base2
Полученный
("Компьютерщик1", "Компьютерщик2")
 

Как получить доступ к родительским элементам в подклассе?

  1. Использование имени родительского класса

    класс Базовый ( объект ):

    defit , x):

    собственное .x = x

    класс Производное (базовое):

    def __init __ ( , x y):

    Base.x = x

    self .y = y

    def printXY ( self ):

    печать (Base.x, self .y)

    d = Получено ( 10 , 20 )

    d.printXY ( )

  2. Использование super ()
    Мы также можем получить доступ к членам родительского класса с помощью super.

    класс База ( объект ):

    def __init __ (

    0

    , x) 9: 9000 собственное .x = x

    класс Производное (базовое):

    def __init __ ( , x y):

    super (получено, self ).__init __ (x)

    self .y = y

    def printXY ( self ):

    0

    печать ( собственное .x, собственное .y)

    d d d d ( 10 , 20 )

    г.printXY ()

    Обратите внимание, что два вышеуказанных метода не совсем то же самое. В следующей статье о наследовании мы рассмотрим следующие темы.
    1) Как супер работает? Чем отличается доступ к члену через имя родительского и родительского классов?
    2) Как проблема с алмазом решается в Python?

    Упражнение:
    Предскажите результат следующих программ Python

    1. 17

      класс X ( объект ):

      по умолчанию __init __ ( self , a):

      self .число = a

      def удвоение ( self ):

      self . число * = 2

      класс Y (X):

      def __init __ ( self , a):

      X.__init __ ( self , a)

      def tripleup ( self ):

      self .num * = 3

      obj = Y ( 4 )

      печать (obj.num)

      obj.doubleup ()

      print (obj.num)

      obj.tripleup ()

      print (obj.num)

    2. класс Человек ( объект ):

      def __init __ ( self , name):

      self .имя = имя

      def getName ( self ):

      return self .name

      8

      def isEmployee ( self ):

      return False

      class Сотрудник (человек):

      def __init __ ( self , имя, eid):

      super (сотрудник, self ).__init __ (имя)

      self .empID = eid

      def isEmployee ( self ): возврат

      True

      def getID ( self ):

      return self .empID

      emp = Сотрудник ( "Geek1" , "E101" )

      печать (emp.getName (), emp.isEmployee (), emp.getID ())

      Вывод:
      ('Geek1', правда, 'E101')
       

    Авторы этой статьи: Shwetanshu Rohatgi и Mayank Rawat .Если вам нравится GeeksforGeeks, и вы хотели бы внести свой вклад, вы также можете написать статью и отправить ее по электронной почте на [email protected] Посмотрите, как ваша статья появляется на главной странице GeeksforGeeks, и помогите другим гикам.

    Пожалуйста, напишите комментарии, если вы обнаружите что-то неправильное, или вы хотите поделиться дополнительной информацией по теме, обсужденной выше

    Внимание компьютерщик! Укрепите свои основы с помощью курса Python Programming Foundation и изучите основы.

    Для начала подготовьтесь к собеседованию. Расширьте свои концепции структур данных с помощью курса Python DS .И чтобы начать свое путешествие по машинному обучению, присоединяйтесь к Машинное обучение - курс базового уровня


Понять O.O.P. в Python с одной статьей | Джулиан Эррера

Чтобы начать понимать интуицию, лежащую в основе этой техники программирования, давайте взглянем на начальный пример. Представьте, что вам нужно описать машину кому-то, кто никогда ее раньше не видел. Как бы вы это сделали?

Фото vaea Garrido на Unsplash

Вы можете начать говорить, что это колесный автомобиль, используемый для перевозки.Кроме того, можно сказать, что существует несколько брендов компаний-производителей автомобилей, которые производят разные типы автомобилей, отвечающие различным потребностям. На более простом уровне можно сказать, что у него четыре шины, что они в большинстве случаев вмещают до пяти человек и что они в основном используются для перевозки людей, а не товаров.

Объясняя компьютеру, что это за объект, неплохо подойти к нему аналогичным образом. Компьютеры не имеют врожденного представления о том, что такое автомобиль, для чего они были созданы и кто ими пользуется.И если вы хотите, чтобы ваш компьютер правильно понимал объект, в данном случае автомобиль, вы должны четко объяснить, каковы его атрибуты .

Чтобы облегчить компьютерам понимание этих новых концепций, Python использует шаблон программирования под названием объектно-ориентированное программирование , который моделирует концепции с помощью классов и объектов. Это гибкая и мощная парадигма, в которой классы представляют и определяют концепции, а объекты являются экземплярами классов .В примере с автомобилем мы можем создать класс с именем car , который определяет его характеристики для компьютера. Мы могли бы создать тысячи экземпляров этого класса car , которые являются отдельными объектами этого класса.

Идея объектно-ориентированного программирования может показаться абстрактной и сложной, но если вы запрограммировали какое-либо программное обеспечение, вы, возможно, уже использовали объекты, даже не осознавая этого. Почти все в Python представляет собой объект , все числа, строки, списки и словари включены в этот тип элемента.Основная концепция объектно-ориентированного программирования сводится к атрибутам и методам , связанным с типом:

  • Атрибуты - это характеристики, связанные с типом.
  • Методы - это функции, связанные с типом.

В примере с автомобилем цвет и марка - это два атрибута, связанных с каждым экземпляром или автомобилем, созданным с помощью программы. С другой стороны, методы - это действия, выполняемые автомобилем или автомобилем, например, вождение.Более ориентированным на компьютер примером может быть файл в каталоге, поскольку каждый файл имеет имя, размер и дату создания.

Как вы можете видеть на изображении выше, когда мы используем функцию типа , как мы только что сделали здесь, Python сообщает нам, к какому классу принадлежит значение или переменная. А поскольку целых чисел и строк являются классами, с ними связано множество атрибутов и методов. Вы можете получить доступ к атрибутам и методам класса с помощью функции dir в Python, как показано ниже:

В моей предыдущей статье о строках я исследовал множество методов и атрибутов класса строк.Взгляните на него, чтобы получить дополнительные сведения о том, как обращаться с объектами строк, которые в этом случае будут другим экземпляром класса строк. Это означает, что все они имеют одинаковые методы, хотя содержимое строки отличается.

Почему существует множество методов, которые начинаются и заканчиваются двойным подчеркиванием?

Они называются специальными методами , и их обычно не вызывают такими странными именами. Вместо этого они вызываются некоторыми внутренними функциями Python.например, метод __len__ вызывается функцией len .

Если вы хотите узнать, что делает конкретный метод, вам следует использовать функцию help :

Когда мы используем функцию help для любой переменной или значения, мы получаем доступ ко всей документации для соответствующего класса. В этом случае мы смотрим на документацию для классов str и int .

Хотя Python поставляется с множеством предопределенных классов для нас, сила объектно-ориентированного программирования проявляется тогда, когда мы определяем наши собственные классы с их собственными атрибутами и методами.

Как упоминалось ранее, цель объектно-ориентированного программирования состоит в том, чтобы помочь определить реальную концепцию таким образом, чтобы ее понимал компьютер. Давайте приступим к созданию нового класса на примере автомобиля:

Давайте проясним конкретные элементы кода:

  • В Python мы используем зарезервированное ключевое слово class , чтобы сообщить компьютеру, что мы запускаем новый класс. . За ним следует имя класса и двоеточие. Руководства по стилю Python рекомендуют, чтобы имена классов начинались с заглавной буквы .В моем случае класс называется Car .
  • После строки с определением класса идет тело класса с отступом вправо по шаблону циклов или функций.
  • Мы рассмотрим специальные методы init и repr в четвертом разделе этой статьи.

Как мы можем расширить наше определение класса Car? Вероятно, он будет иметь те же атрибуты, которые представляют информацию, которую мы хотим связать с автомобилем, например марку и цвет.

Теперь приступим к созданию экземпляра класса Car и присвоим его переменной с именем «my_car». Чтобы создать новый экземпляр любого класса, мы вызываем имя класса, как если бы это была функция:

Как видите, я передаю в качестве аргумента марку и цвет, поскольку я настроил свой класс на требуются оба элемента при создании нового объекта класса. В результате мы можем вызвать атрибуты созданного экземпляра и получить ранее присвоенное значение:

Синтаксис, используемый для доступа к атрибутам, называется точечной нотацией из-за точки, используемой в выражении.Точечная запись позволяет получить доступ к любым возможностям объекта, например к бренду или цвету.

Атрибуты и методы некоторых объектов могут быть другими объектами и могут иметь собственные атрибуты и методы. Например, мы могли бы использовать методы upper или capize для изменения обоих строковых атрибутов моего экземпляра:

До сих пор мы создали один экземпляр класса Car и установили его атрибуты. Теперь мы могли бы создать новый экземпляр класса с другими атрибутами:

Методы, по сути, вызываются для того, чтобы объекты выполняли какие-то действия.Например, верхний и заглавные для строк. Ключ к познанию интуиции методов в O.O.P. это понимать, что методы - это функции, которые работают с атрибутами конкретного экземпляра класса . Когда мы вызываем метод lower для строки, мы переводим содержимое этой конкретной строки в нижний регистр.

Давайте рассмотрим подробнее, создав новый класс с именем Dog и определив наши собственные методы. Во-первых, нам нужно определить класс и создать его экземпляр, как мы делали это раньше с классом Car.Хотя моя собака может быть отличной, она не может выполнять никаких действий, пока я не определю для нее методы. Взгляните на пример:

Как показано на изображении, мы должны начать определение метода с помощью ключевого слова def так же, как и для функции, и сделать отступ справа от тела метода, как если бы мы для функции.

Функция получает параметр под названием «self». Этот параметр представляет экземпляр, в котором выполняется метод.

Даже если моя собака лает, она всегда будет делать это одинаково.Мы можем изменить то, как он будет лаять, с помощью простой модификации кода, чтобы получить гибкость в атрибутах и ​​методах, которые мы настраиваем для наших классов:

Оба класса, созданные до этого параграфа, содержат значения по умолчанию как атрибуты и методы. Это не идеальный сценарий, поскольку он создает избыточный код для каждого атрибута и, что более важно, , так как очень легко забыть установить важное значение .

Итак, при написании кода рекомендуется установить атрибуты и методы, которые будут меняться в зависимости от экземпляра при создании класса, чтобы гарантировать, что каждый экземпляр содержит одни и те же важные атрибуты.Для этого нам нужно использовать специальный метод под названием Constructor .

Конструктор класса - это метод, который вызывается при вызове имени класса. Он всегда называется init .

Он содержит ключевое слово self, которое относится к создаваемому экземпляру, и аргументы, которые будут переданы программистом как атрибуты после создания экземпляра, как мы это делаем в примере ниже:

Второй метод class - это метод repr , который сообщает Python печатать предопределенный оператор каждый раз, когда вызывается экземпляр класса, как на изображении выше.

Хотите знать, какая функция у того или иного метода? См. Ранее введенную функцию help . Поскольку встроенные классы включают руководство, помогающее пользователям понять интуицию, лежащую в основе каждого метода или атрибута, мы также можем сделать это для наших собственных классов, методов и функций. Мы можем сделать это, добавив Docstring .

Строка документации - это краткий текст, объясняющий, что что-то делает.

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

Другой важный аспект объектно-ориентированного программирования - Inheritance . Точно так же, как у людей есть родители, бабушки и дедушки и т. Д., У объектов есть предки. Принцип наследования позволяет программисту строить отношения между концепциями и группировать их вместе. В частности, это позволяет нам уменьшить дублирование кода за счет обобщения нашего кода.

Например, как мы можем определить представление « других транспортных средств» отдельно от машины, которую мы уже создали, или других домашних животных, кроме моей собаки.Эта характеристика группирования позволяет нам создавать другие классы, которые разделяют некоторые атрибуты существующих классов, но не все из них, чтобы добавлять другие похожие экземпляры без перезаписи существующего кода.

В Python мы используем круглые скобки в объявлении класса, чтобы показать отношение наследования . В приведенном выше примере транспорта я использовал синтаксис Python, чтобы сообщить компьютеру, что и автомобиль, и поезд наследуются от класса MyTransports. Из-за этого у них автоматически есть один и тот же конструктор, который устанавливает атрибуты цвета и бренда.

Оставить комментарий

Добавить комментарий

Ваш адрес email не будет опубликован.

© 2019 Штирлиц Сеть печатных салонов в Перми

Цифровая печать, цветное и черно-белое копирование документов, сканирование документов, ризография в Перми.