Что такое классы в Python определение, наследование и примеры создания


Содержание материала:

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

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

Введение в ООП в Python

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

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

Объект имеет две характеристики:

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

Объект – это попугай:

  • имя, возраст, цвет являются атрибутами;
  • пение, танцы — это поведение;

Концепция ООП в Python направлена ​​на создание кода для многократного использования. Эта концепция также известна как DRY (Don’t Repeat Yourself).

В Python концепция ООП реализует несколько принципов:

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

Класс

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

Примером для класса попугая может быть:

Мы используем ключевое слово class для определения пустого класса Parrot . Из класса мы создаем экземпляр – объект определенного класса.

Объект

Объект — это экземпляр класса. В определении класса задается только описание объекта. Пример создания объекта класса Parrot:

В данном случае obj является объектом класса Parrot.

Предположим, что у нас есть информация о попугае. Теперь нужно показать, как построить класс и объекты Parrot.

Пример 1. Создание класса и объекта в Python

Результат работы программы:

Сначала мы создаем класс с именем Parrot. Затем мы определяем атрибуты. Они являются характеристикой объекта.

Затем мы создаем экземпляры класса Parrot. В данном случае blu и woo являются ссылками на новые объекты.

После этого мы получаем доступ к атрибуту класса с помощью __class __.species. Атрибуты класса одинаковы для всех его экземпляров. Точно так же мы получаем доступ к атрибутам экземпляра, используя blu.name и blu.age. Но атрибуты экземпляра уникальны для каждого экземпляра класса.

Методы

Методы — это функции, определенные внутри класса. Они используются для определения поведения объекта.

Пример 2: Создание методов в Python

Результат работы программы:

В приведенном выше примере мы определяем два метода sing() и dance(). Их называют методами экземпляра, так как они вызываются для экземпляра объекта, то есть для blu.

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

Наследование — это способ создания нового класса на основе старого. Новый класс является производным классом (дочерним). Существующий класс является базовым классом (родительским).

Пример 3: Использование наследования в Python

Результат работы программы:

Сначала мы создали два класса: Bird (родительский класс) и Penguin (дочерний класс). Он наследует функции родительского класса. Это прослеживается в методе swim().

Дочерний класс изменил поведение родительского класса – метод whoisThis(). Также мы расширяем родительский класс, создав новый метод run().

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

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

Используя ООП в Python, мы можем ограничить доступ к методам и переменным. Это предотвращает изменение данных вне класса. Такой подход называется инкапсуляцией. В Python мы устанавливаем приватный модификатор доступа, используя в качестве префикса подчеркивание одинарное «_» или двойное «_ _» подчеркивание.

Пример 4: Инкапсуляция данных в Python

Когда мы запустим эту программу, результат будет следующим:

Сначала мы определили класс Computer . Затем использовали метод __init__() для хранения значения максимальной стоимости продажи компьютера.

Мы попытались изменить цену, но не смогли, потому что Python рассматривает __maxprice, как приватные атрибуты. Чтобы изменить значение, мы использовали функцию сеттера. То есть, setMaxPrice(), которая принимает цену в качестве параметра.

Полиморфизм

Полиморфизм — это способность использовать в ООП общий интерфейс для нескольких форм (типов данных).

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

Пример 5: Использование полиморфизма в Python

Мы определили два класса: Parrot и Penguin . У каждого из них есть общий метод fly(), но они разные.

Чтобы реализовать полиморфизм, мы создали общий интерфейс. То есть, функцию flying_test(), которая может принимать любой объект. Затем мы передали объекты blu и peggy в функцию flying_test().

Преимущества использования ООП:

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

Данная публикация представляет собой перевод статьи « Python Object Oriented Programming » , подготовленной дружной командой проекта Интернет-технологии.ру

Инкапсуляция, наследование, полиморфизм

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

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

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

Инкапсуляция в Python работает лишь на уровне соглашения между программистами о том, какие атрибуты являются общедоступными, а какие — внутренними.

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

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

Однако полностью это не защищает, так как атрибут всё равно остаётся доступным под именем _ИмяКласса__ИмяАтрибута:

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

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

Класс Mydict ведёт себя точно так же, как и словарь, за исключением того, что метод get по умолчанию возвращает не None, а 0.

Полиморфизм

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

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

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

Данное руководство ознакомит вас с основными аспектами наследования в Python: с работой родительских и дочерних классов, переопределением методов и атрибутов, функцией super() и множественным наследованием.

Что такое наследование?

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

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

Предположим, что у нас есть класс Parent с переменными last_name, height, и eye_color. Подкласс Child может наследовать эти переменные из класса Parent, то есть повторно использовать этот код. Это позволяет уменьшить объём кода и снизить избыточность.

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

Родительский, или базовый класс создаёт шаблон кода, который в дальнейшем может наследоваться дочерними классами. Родительским классом может быть любой класс. Базовые классы – это не просто шаблоны, а полноценные функционирующие классы.

Предположим, у нас есть общий родительский класс Bank_account с дочерними классами Personal_account и Business_account. Многие параметры классов Personal_account и Business_account будут совпадать. Такие параметры можно наследовать из родительского класса Bank_account. В подклассе Business_account будут индивидуальные параметры (например, методы сбора деловых документов и форм и переменная employee_identification_number).

Аналогичным образом, класс Animal может содержать методы eating() и sleeping(), а подкласс Snake помимо вышеперечисленных наследуемых методов может также содержать методы hissing() и slithering().

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

Создайте файл fish.py и добавьте в него метод конструктора __init__(), который будет содержать переменные first_name и last_name для каждого подкласса (или объекта) Fish.

class Fish:
def __init__(self, first_name, last_name=»Fish»):
self.first_name = first_name
self.last_name = last_name

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

Добавьте в файл другие методы:

class Fish:
def __init__(self, first_name, last_name=»Fish»):
self.first_name = first_name
self.last_name = last_name
def swim(self):
print(«The fish is swimming.»)
def swim_backwards(self):
print(«The fish can swim backwards.»)

Теперь в файле есть методы swim() и swim_backwards(), которые будут наследоваться подклассами.

Добавьте другие атрибуты в метод __init__():

class Fish:
def __init__(self, first_name, last_name=»Fish»,
skeleton=»bone», eyel > self.first_name = first_name
self.last_name = last_name
self.skeleton = skeleton
self.eyel > def swim(self):
print(«The fish is swimming.»)
def swim_backwards(self):
print(«The fish can swim backwards.»)

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

Дочерние классы

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

К примеру, дочерний класс Goldfish может наследовать из класса Fish функцию swim().

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

Топ-пост этого месяца:  Использование ограничения SQL unique для запрета ввода одинаковых записей в таблицу

Класс Trout является дочерним по отношению к классу Fish (родительский класс нужно указать в круглых скобках).

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

.
class Trout(Fish):
pass

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

.
class Trout(Fish):
pass
terry = Trout(«Terry»)
print(terry.first_name + » » + terry.last_name)
print(terry.skeleton)
print(terry.eyelids)
terry.swim()
terry.swim_backwards()

Теперь в файле есть объект Trout, который использует все методы класса Fish несмотря на то, что они не объявлены в самом объекте. Нужно только передать значение «Terry» переменной first_name; все остальные переменные уже инициализированы.

Запустите программу. Вы получите:

Terry Fish
bone
False
The fish is swimming.
The fish can swim backwards.

Создайте другой дочерний класс, теперь уже добавив новые методы. Класс будет называться Clownfish, его индивидуальный метод – live_with_anemone.

.
class Clownfish(Fish):
def live_with_anemone(self):
print(«The clownfish is coexisting with sea anemone.»)

Создайте объект Clownfish:

.
casey = Clownfish(«Casey»)
print(casey.first_name + » » + casey.last_name)
casey.swim()
casey.live_with_anemone()

Запустив программу, вы получите такой вывод:

Casey Fish
The fish is swimming.
The clownfish is coexisting with sea anemone.

Как видите, объект casey использует методы __init__() и swim() родительского класса Fish и индивидуальный метод live_with_anemone().

Если попробовать использовать метод live_with_anemone() в объекте Trout, получится ошибка:

terry.live_with_anemone()
AttributeError: ‘Trout’ object has no attribute ‘live_with_anemone’

Это потому, что метод live_with_anemone() принадлежит исключительно дочернему классу Clownfish.

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

Только что вы создали дочерний класс Trout, который с помощью ключевого слова pass полностью наследует методы родительского класса Fish, и дочерний класс Clownfish, который не только наследует методы родительского класса, но и использует свой собственный метод.

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

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

Для примера создайте дочерний класс Shark. При создании класса Fish использовался параметр skeleton=»bone», но в случае с классом Sharkэто неверно. Переопределите этот параметр.

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

Переопределите метод конструктора __init__() и метод swim_backwards(). Метод swim() изменять не нужно. Дочерний класс будет выглядеть так:

.
class Shark(Fish):
def __init__(self, first_name, last_name=»Shark»,
skeleton=»cartilage», eyel > self.first_name = first_name
self.last_name = last_name
self.skeleton = skeleton
self.eyel > def swim_backwards(self):
print(«The shark cannot swim backwards, but can sink backwards.»)

Параметры метода конструктора __init__() были переопределены; теперь переменная last_name имеет значение «Shark», переменная skeleton имеет значение «cartilage», а eyelids – значение True.

Метод swim_backwards() теперь выводит другое значение, а не то, которое определено в родительском классе Fish.

Теперь создайте экземпляр класса Shark, который будет использовать метод swim() родительского класса Fish.

.
wally = Shark(«Wally»)
print(wally.first_name + » » + wally.last_name)
wally.swim()
wally.swim_backwards()
print(wally.eyelids)
print(wally.skeleton)

Запустите этот код, и вы получите:

Wally Shark
The fish is swimming.
The shark cannot swim backwards, but can sink backwards.
True
cartilage

В дочернем классе Shark методы __init__() и swim_backwards() успешно переопределены.

Функция super()

С помощью функции super() вы можете получить доступ к унаследованным методам, которые были перезаписаны в объекте класса.

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

К примеру, в программе, которая выставляет оценки студентам, внутри родительского класса Grade может быть дочерний класс Weighted_grade. В классе Weighted_grade можно переопределить метод родительского класса calculate_grade() и унаследовать остальные методы без изменений. Для этого и нужна функция super().

Обычно функция super() используется в методе конструктора __init__(), потому что именно там, скорее всего, появятся уникальные методы дочернего класса.

Попробуйте изменить дочерний класс Trout. Добавьте переменную water в метод __init__() и присвойте ей значение «freshwater». Остальные методы родительского класса можно наследовать без изменений.

.
class Trout(Fish):
def __init__(self, water = «freshwater»):
self.water = water
super().__init__(self)
.

Метод __init__() класса Trout был переопределён. Он иначе реализует методы __init__() родительского класса Fish. В методе __init__() класса Trout был явно инициализирован метод __init__() класса Fish.

Поскольку метод переопределён, больше не нужно передавать first_name как параметр Trout. Если бы вы передали параметр, параметр freshwater был бы сброшен. Поэтому переменную first_name нужно инициализировать путём вызова в объекте.

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

.
terry = Trout()
# Инициализация first name
terry.first_name = «Terry»
# Использование родительского метода __init__() с помощью функции super()
print(terry.first_name + » » + terry.last_name)
print(terry.eyelids)
# Дочерний метод __init__()
print(terry.water)
# Родительский метод swim()
terry.swim()
Terry Fish
False
freshwater
The fish is swimming.

Как видите, объект terry класса Trout может использовать свой метод __init__() с переменной water и родительский метод __init__() класса Fish с переменными first_name, last_name и eyelids.

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

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

Попробуйте создать класс Coral_reef, дочерний по отношению к классам Coral и Sea_anemone. Создайте в каждом классе метод и передайте его с помощью ключевого слова pass в дочерний класс Coral_reef.

class Coral:
def community(self):
print(«Coral lives in a community.»)
class Anemone:
def protect_clownfish(self):
print(«The anemone is protecting the clownfish.»)
class CoralReef(Coral, Anemone):
pass

Класс Coral содержит метод community(), который выводит одну строку текста, а класс Anemone содержит метод protect_clownfish(), который отображает другую строку. После этого оба класса вызываются в кортеж. Таким образом класс Coral может наследовать оба родительских класса.

Читайте также: Кортежи в Python 3

Создайте объект класса Coral:

.
great_barrier = CoralReef()
great_barrier.community()
great_barrier.protect_clownfish()

Объект great_barrier класса CoralReef использует методы обоих родительских классов.

Coral lives in a community.
The anemone is protecting the clownfish.

Как видите, множественное наследование работает правильно.

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

Заключение

Теперь вы знакомы с основами наследования в Python 3 и умеете создавать родительские и дочерние классы, переопределять методы, использовать функцию super() и использовать множественное наследование.

Наследование в объектно-ориентированном программировании позволяет соблюдать принцип разработки DRY («don’t repeat yourself»), благодаря чему программа содержит меньше повторяющихся блоков кода. Наследование также заставляет разработчиков заранее продумывать конструкцию программы.

Как проверить наследование классов в Python?

У меня есть класс А и класс B , наследующий класс A . Кто подскажет функцию, выдающую True или False на наследование классом B класса A ?

2 ответа 2

Встроенные функции issubclass и isinstance отвечают на вопрос о наследовании:

issubclass (class, classinfo) — является ли класс унаследованым (дочерним) от другого(их) класса(ов)


Класс B унаследован от A , но он не является его сущностью. Классы сами по себе объекты и функция isinstance к ним вполне применима. Класс B является сущностью класса type , который в свою очередь, унаследован от object , поэтому:

Классы в Python

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

В примере мы видим строку, присвоенную переменной х. Это может выглядеть как большой объем, но дело в том, что у этой строки много методов. Если вы используете ключевое слово dir, вы получите список всех методов, которые можно присвоить строке. Мы видим 71 метод! Технически, мы не можем вызвать методы, которые начинаются с подчеркивание, так что это сужает список до 38 методов, но это все еще очень много! Что это значит? Это значит что, строка основана на классе, а переменная х – и есть экземпляр этого класса. В Пайтоне мы можем создавать собственные классы. Начнем!

Создание Класса

Создание класса в Пайтоне – это очень просто. Вот простой пример:

Этот класс не делает ничего конкретного, тем не менее, это очень хороший инструмент для изучения. Например, чтобы создать класс, мы используем ключевое слово class, за которым следует наименование класса. В Пайтоне, конвенция указывает на то, что наименование класса должно начинаться с заглавной буквы. Далее нам нужно открыть круглые скобки, за которыми следует слово object и закрытые скобки. «object» — то, на чем основан класс, или наследуется от него. Это называется базовым классом или родительским классом. Большая часть классов в Пайтоне основаны на объекте. У классов есть особый метод, под названием __init__.

Этот метод вызывается всякий раз, когда вы создаете (или создаете экземпляр) объект на основе этого класса. Метод __init__ вызывается единожды, и не может быть вызван снова внутри программы. Другое определение метода __init__ — это конструктор, кстати, этот термин редко встречается в Пайтоне. Вы можете подумать, почему я называю это методом, а не функцией? Функция меняет свое имя на «method», когда она находится внутри класса. Обратите внимание на то, что каждый метод должен иметь как минимум один аргумент, что в случае с обычной функцией уже не вяжется. В Python 3 нам не нужно прямо указывать, что мы наследуем у объекта. Вместо этого, мы можем написать это следующим образом:

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

В данном примере мы добавили три атрибута и два метода. Эти три атрибута являются:

Атрибуты описывают автомобиль. У него есть цвет, определенное количество дверей и колес. Также у него есть два метода. Метод описывает, что делает класс. В нашем случае, автомобиль может двигаться и останавливаться. Вы могли заметить, что все методы, включая первый, имеют интересный аргумент, под названием self. Давайте рассмотрим его внимательнее.

Топ-пост этого месяца:  Отображение обложки группы ВК на мобильных устройствах

Что такое self?

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

Условия оператора if в данном примере это стандартный способ указать Пайтону на то, что вы хотите запустить код, если он выполняется как автономный файл. Если вы импортировали свой модуль в другой скрипт, то код, расположенный ниже проверки if не заработает. В любом случае, если вы запустите этот код, вы создадите два экземпляра класса автомобиля (Vehicle): класс легкового и класс грузового. Каждый экземпляр будет иметь свои собственные атрибуты и методы. Именно по этому, когда мы выводи цвета каждого экземпляра, они и отличаются друг от друга. Причина в том, что этот класс использует аргумент self, чтобы указать самому себе, что есть что. Давайте немного изменим класс, чтобы сделать методы более уникальными:

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

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

Подклассы

Настоящая сила классов становится очевидной, когда вопрос касается подклассов. Вы, возможно, еще не поняли это, но мы уже создали подкласс, когда создавали класс, основанный на объекте. Другими словами, «подклассифицировали» объект. Так как объект – это не очень интересная тема, предыдущие примеры не уделили должного внимания такому сильному инструменту как подкласс. Давайте подклассифицируем наш класс Vehicle и узнаем, как все это работает.

В этом примере, мы подклассифицировали класс Vehicle. Вы могли заметить, что мы не использовали методы __init__ и drive. Причина в том, что когда мы хотим сделать из класса подкласс, мы уже имеем все атрибуты и методы, только если мы не переопределяем их. Таким образом, вы могли заметить, что мы переопределяем метод brake и указываем ему делать кое-что другое. Другие методы остаются такими же, какими они и были до этого. Так что, когда вы указываете автомобилю тормозить, он использует оригинальный метод, и мы узнали, что мы водим желтый автомобиль. Когда мы используем значения родительского класса по умолчанию – мы называем это наследование.

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

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

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

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

Классы не такие уж и простые, но они очень и очень полезные и эффективные. С их помощью вы можете использовать переменные в методах, которые делают повторное использование кода намного проще. Я могу порекомендовать взглянуть на исходник Пайтона, для ознакомления с потрясными примерами того, как классы определяются и используются. Теперь, зная, как создавать подклассы, вы можете переопределять параметры родительского класса так, и в тех количествах, как вам угодно. Помните: если вы полностью переопределите его, вы по факту просто создадите новый класс.

Метаклассы в Python

Как сказал один из пользователей StackOverflow, «using SO is like doing lookups with a hashtable instead of a linked list». Мы снова обращаемся к этому замечательному ресурсу, на котором попадаются чрезвычайно подробные и понятные ответы на самые различные вопросы.

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

Классы как объекты

Перед тем, как изучать метаклассы, надо хорошо разобраться с классами, а классы в Питоне — вещь весьма специфическая (основаны на идеях из языка Smalltalk).

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

Но в Питоне класс это нечто большее — классы также являются объектами.

Как только используется ключевое слово class , Питон исполняет команду и создаёт объект. Инструкция

создаст в памяти объект с именем ObjectCreator .

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

Тем не менее, это объект, а потому:

  • его можно присвоить переменной,
  • его можно скопировать,
  • можно добавить к нему атрибут,
  • его можно передать функции в качестве аргумента,

Динамическое создание классов

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

Например, можно создать класс в функции, используя ключевое слово class :

Однако это не очень-то динамично, поскольку по-прежнему нужно самому писать весь класс целиком.

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

Когда используется ключевое слово class , Питон создаёт этот объект автоматически. Но как и большинство вещей в Питоне, есть способ сделать это вручную.

Помните функцию type ? Старая-добрая функция, которая позволяет определить тип объекта:

На самом деле, у функции type есть совершенно иное применение: она также может создавать классы на ходу. type принимает на вход описание класса и созвращает класс.

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

type работает следующим образом:

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

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

type принимает словарь, определяющий атрибуты класса:

можно переписать как

и использовать как обычный класс

Конечно, можно от него наследовать:

В какой-то момент вам захочется добавить методов вашему классу. Для этого просто определите функцию с нужной сигнатурой и присвойте её в качестве атрибута:

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

Это именно то, что Питон делает, когда используется ключевое слово class , и делает он это с помощью метаклассов.

Что такое метакласс (наконец)

Метакласс это «штука», которая создаёт классы.

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

Мы уже видели, что type позволяет делать что-то в таком духе:

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

Естественный вопрос: с чего это он его имя пишется в нижнем регистре, а не Type ?

Я полагаю, это просто для соответствия str , классу для создания объектов-строк, и int , классу для создания объектов-целых чисел. type это просто класс для создания объектов-классов.

Это легко проверить с помощью атрибута __class__ :

В питоне всё (вообще всё!) является объектами. В том числе числа, строки, функции и классы — они все являются объектами и все были созданы из класса:

А какой же __class__ у каждого __class__ ?

Итак, метакласс это просто штука, создающая объекты-классы.

Если хотите, можно называть его «фабрикой классов»

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

Атрибут __metaclass__

При написании класса можно добавить атрибут __metaclass__ :

В таком случае Питон будет использовать указанный метакласс при создании класса Foo .

Осторожно, тут есть тонкость!

Хоть вы и пишете class Foo(object) , объект-класс пока ещё не создаётся в памяти.

Питон будет искать __metaclass__ в определении класса. Если он его найдёт, то использует для создания класса Foo . Если же нет, то будет использовать type .

То есть когда вы пишете

Питон делает следующее:

Есть ли у класса Foo атрибут __metaclass__ ?

Если да, создаёт в памяти объект-класс с именем Foo , используя то, что указано в __metaclass__ .

Если Питон не находит __metaclass__ , он ищет __metaclass__ в родительском классе Bar и попробует сделать то же самое.

Если же __metaclass__ не находится ни в одном из родителей, Питон будет искать __metaclass__ на уровне модуля.

И если он не может найти вообще ни одного __metaclass__ , он использует type для создания объекта-класса.

Теперь важный вопрос: что можно положить в __metaclass__ ?

Ответ: что-нибудь, что может создавать классы.

А что создаёт классы? type или любой его подкласс, а также всё, что использует их.

Топ-пост этого месяца:  Восстановить пароль администратора Wordpress

Пользовательские метаклассы

Основная цель метаклассов — автоматически изменять класс в момент создания.

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

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

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

К счастью, __metaclass__ может быть любым вызываемым объектом, не обязательно формальным классом (я знаю, что-то со словом «класс» в названии не обязано быть классом, что за ерунда? Однако это полезно).

Так что мы начнём с простого примера, используя функцию.

А теперь то же самое, только используя настояший класс:

Но это не совсем ООП. Мы напрямую вызываем type и не перегружаем вызов __new__ родителя. Давайте сделаем это:

Вы, возможно, заметили дополнительный аргумент upperattr_metaclass . Ничего особого в нём нет: метод всегда получает первым аргументом текущий экземпляр. Точно так же, как вы используете self в обычным методах.

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

Можно сделать даже лучше, использовав super , который вызовет наследование (поскольку, конечно, можно создать метакласс, унаследованный от метакласса, унаследованного от type ):

Вот и всё. О метаклассах больше ничего и не сказать.

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

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

  • перехватить создание класса
  • изменить класс
  • вернуть модифицированный

Зачем использовать метаклассы вместо функций?

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

Тому есть несколько причин:

  • Назначение яснее. Когда вы видите UpperAttrMetaclass(type) , вы сразу знаете, что дальше будет.
  • Можно использовать ООП. Метаклассы могту наследоваться от метаклассов, перегружая родитальские методы.
  • Лучше структурированный код. Вы не будете использовать метаклассы для таких простых вещей, как в примере выше. Обычно это что-то сложное. Возможность создать несколько методов и сгруппировать их в одном классе очень полезна, чтобы сделать код более удобным для чтения.
  • Можно использовать __new__ , __init__ и __call__ . Конечно, обычно можно всё сделать в __new__ , но некоторым комфортнее использовать __init__
  • Они называются метаклассами, чёрт возьми! Это должно что-то значить!

Зачем вообще использовать метаклассы?

Наконец, главный вопрос. С чего кому-то использовать какую-то непонятную (и способствующую ошибкам) фичу?

Ну, обычно и не надо использовать:

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

Гуру Питона Тим Питерс

Основное применение метаклассов это создание API. Типичный пример — Django ORM.

Она позволяет написать что-то в таком духе:

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

вы получите не IntegerField , а int , причём значение может быть получено прямо из базы данных.

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

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

Напоследок

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

На самом деле, классы это тоже экземпляры. Экземпляры метаклассов.

Всё что угодно является объектом в Питоне: экземпляром класса или экземпляром метакласса.

type является собственным метаклассом. Это нельзя воспроизвести на чистом Питоне и делается небольшим читерством на уровне реализации.

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

  • руками
  • декораторы классов

В 99% случаев, когда вам нужно изменить класс, лучше использовать эти два.

Но в 99% случаев вам вообще не нужно изменять классы 🙂

Условное определение наследования класса в python

У меня есть приложение на основе python на основе linux, которое использует pygtk и gtk. Он имеет как исполнение пользовательского интерфейса, так и режим командной строки. В режиме пользовательского интерфейса, чтобы создать главное окно приложения, определение класса

Что я хочу сделать, если приложение может импортировать gtk и pygtk, тогда только класс ToolWindow должен наследовать как common.Singleton, так и gtk.Window классы else, он должен только наследовать обычный. Класс Singleton.

Каков наилучший способ сделать это?

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

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

Вы должны знать, что вы должны изменить свой метод __init__() из ToolWindow , потому что он может не иметь доступный модуль gtk (возможно, установить флаг в метаклассе, чтобы позже проверить, доступен ли модуль, или вы может даже переопределить метод __init__() изнутри метакласса на основе того, доступен ли модуль или нет — есть несколько способов решения этой проблемы).

Инкапсуляция, наследование, полиморфизм

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

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

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

Инкапсуляция в Python работает лишь на уровне соглашения между программистами о том, какие атрибуты являются общедоступными, а какие — внутренними.

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

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

Однако полностью это не защищает, так как атрибут всё равно остаётся доступным под именем _ИмяКласса__ИмяАтрибута:

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

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

Класс Mydict ведёт себя точно так же, как и словарь, за исключением того, что метод get по умолчанию возвращает не None, а 0.

Полиморфизм

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

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

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

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

Итак, метод f_func1() определен в двух классах — Class1 и Class3. Так как класс Class2 стоит первым в списке базовых классов, вначале просматривается этот класс, а затем все его базовые классы. Поэтому метод f_func1() будет найден в классе Class1, а не в классе Class3.

Метод f_func2() также определен в двух классах — Class2 и Class3. Так как класс Class2 стоит первым в списке базовых классов, то метод будет найден именно в этом классе. Чтобы наследовать метод из класса Class3, следует указать это явным образом. Передаем определение класса Class4 из предыдущего примера и наследуем метод f_func2() из класса Class3.

Указание класса при наследовании метода

Метод f_func3() определен только в классе Class3, поэтому метод наследуется от этого класса. Метод f_func4(), определенный в классе Class3, переопределяется в производном классе. Если метод найден в производном классе, то вся иерархия наследования просматривается не будет.

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

Наследование и внутренние классы в Python?

в следующем классе кода B унаследовала из класса A , Я ожидал этого. Я бы также ожидал, что внутренний класс B.Foo ведет себя так же, но это не так.

как сделать B.Foo наследование класса A ? Мне нужно, чтобы внутренний подкласс Foo на B имеет атрибуты alice и bob .

3 ответов

почему B.Foo.alice выдал вам ошибку, потому что нет никакой связи между класса A и класса B .

на B атрибут Foo имеет значение объекта класса, которое полностью заменяет значение объекта класса, унаследованное от A .

это должно исправить:

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

в случае класс B мы:

  1. yay атрибут, который имеет значение True наследуется от А.
  2. nay атрибут, который имеет значение False .
  3. Foo атрибут, который имеет объект класса.

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

наследование-это вещь для каждого класса. В вашем классе кода B наследует от класса A , но только потому, что оба они имеют внутренний класс Foo ничего не говорит нам об их наследстве.

если вы хотите B.Foo иметь атрибуты A.Foo , вам необходимо сделать B.Foo наследовать от A.Foo :

Foo Это собственный класс. Он не наследуется от A . Из-за этого у него нет полей A . Тот факт, что вложен в подкласс A ничего не меняет.

Добавить комментарий