[python]Экземпляр класса как параметр по умолчанию
А че нет, замыкания же. Только param инициализируется один раз, это надо помнить.
creepnee
( 28.09.10 00:11:22 MSD )
мочь то можно, но это рассадник багов.
Zubchick ★
( 28.09.10 00:14:12 MSD )
Ответ на: комментарий от Zubchick 28.09.10 00:14:12 MSD
просто мне лень один объект все время явно передавать в разные функции, вот подумал, может его как параметр по умолчанию указать, но как это сделать не знаю
swelf ★
( 28.09.10 00:24:04 MSD ) автор топика
Глобальная переменная, нэ? Ее хотя бы поменять можно.
baverman ★★★
( 28.09.10 04:36:43 MSD )
а не судьба вот так сделать:
[code] def f(param = null): if !a a = A() a.bla = ‘bla’ [/code]
питона не знаю, но, думаю, суть ясна
anonymous
( 28.09.10 08:01:51 MSD )
Ответ на: комментарий от swelf 28.09.10 00:24:04 MSD
true_admin ★★★★★
( 28.09.10 11:20:07 MSD )
Ответ на: комментарий от anonymous 28.09.10 08:01:51 MSD
анонимус прав на все 100. Так и делают когда хочется дефолтовым параметром передать, например пустой список. Но с объектом сложней, они могут иметь отличные от А() параметры. Вообще изменять объекты внутри функций не являющихся методами черевато, но никто не говорит, что этого нельзя делать.
Zubchick ★
( 28.09.10 11:29:42 MSD )
Ответ на: комментарий от true_admin 28.09.10 11:20:07 MSD
«Глобальный синглтон» в python — это модуль. Только и всего.
shylent ★
( 28.09.10 12:12:18 MSD )
Ответ на: комментарий от shylent 28.09.10 12:12:18 MSD
не обязательно. И там есть свои нюансы.
true_admin ★★★★★
( 28.09.10 13:02:34 MSD )
Ответ на: комментарий от true_admin 28.09.10 13:02:34 MSD
Эх, ну вы же понимаете, что про все что угодно можно сказать, что там «есть свои нюансы» без всяких объяснений.
Просто, в самом деле, наипростейший вариант реализации синглтона без всяких вывертов (тем более, что все эти выверты без проблем в python обходятся, благодаря его динамизму), это объект внутри модуля.
shylent ★
( 28.09.10 14:03:27 MSD )
Ответ на: комментарий от shylent 28.09.10 14:03:27 MSD
В моём понимании класс тоже может быть синглтоном и я бы загнал все глобальные переменные в класс(для красоты). Про нюанс я имел в виду что если сделать «from module import *», где * это простые типы данных типа int, str итп то работать не будет т.к. они при присвоении значения получится своя локальная копия переменной которая и будет изменена.
true_admin ★★★★★
( 28.09.10 14:32:50 MSD )
Ответ на: комментарий от true_admin 28.09.10 14:32:50 MSD
про синглтоны почитаю потом какнить, пока решил просто сделать функцию методом класса.
Основы Python — Функции и Объекты
До сих пор мы видели, как мы можем использовать переменные в Python для хранения различных типов данных, и как мы можем использовать структуры «управления потоком», такие как условия и циклы, чтобы изменить порядок или способ выполнения строк кода. С помощью только этих инструментов мы уже можем начать выражать некоторые довольно сложные логические схемы. Однако, имея только наши текущие инструменты, любой достаточно сложный скрипт начинал бы становиться очень длинным, так как каждый раз, когда мы хотели выполнить определенный процесс, нам приходилось бы переписывать весь его код.
Вот тут-то и появляются функции и классы . Функции позволяют нам инкапсулировать строки кода для создания пользовательских процессов, которые могут быть повторно использованы в любом месте скрипта. Объекты делают эту инкапсуляцию еще одним шагом вперед и заключают в себе не только один процесс, но и несколько связанных процессов, а также локальные переменные, которые могут отслеживать состояние этого объекта.
Функции
Мы уже видели и использовали некоторые функции, такие как type() , str() , .append() , .keys() и range() . Но что такое функции на самом деле?
Как и в математике, функция-это базовая структура, которая может принимать входные данные, выполнять некоторую обработку этих входных данных и возвращать результат. Давайте создадим базовую функцию, которая добавит два к заданному числу и вернет нам результат:
result = inputNumber + 2
Сам по себе этот код будет только определять, что делает функция, но на самом деле не будет выполнять никакого кода. Чтобы выполнить код внутри функции, вы должны вызвать его где-то внутри скрипта и передать ему соответствующие входные данные:
Определение (Definition) функции начинается с ключевого слова def . После этого следует имя функции,которое следует тем же соглашениям об именовании, что и переменные. Внутри круглой скобки после имени функции можно поместить любое количество входных переменных, которые будут переданы функции при ее вызове и доступны в теле функции. При вызове функции можно либо непосредственно передавать значения, либо передавать переменные, внутри которых хранятся значения. Например, этот код вызовет функцию таким же образом:
Здесь значение переменной var , которое в данном случае равно 2 , передается функции addFunction , а затем доступно внутри этой функции через переменную inputNumber . Обратите внимание, что имена двух переменных var и inputNumber не обязательно должны совпадать. Когда значение передается функции, оно образует прямую связь между двумя наборами скобок, которые несут данные.
В этом случае var — это глобальная переменная, которая хранит значение 2 в основном скрипте, в то время как inputNumber — это локальная переменная, которая хранит это значение только на время выполнения этой функции. Таким образом, функции «сворачивают» конкретные задачи и все данные, необходимые для выполнения этой задачи, чтобы ограничить количество глобальных переменных, необходимых в основной функции.
Первая строка, объявляющая функцию и ее входные данные, заканчивается двоеточием , которое должно быть уже знакомо, с остальной частью тела функции, вставленной из первой строки. При необходимости, если вы хотите вернуть значение из функции обратно в основной скрипт, вы можете завершить функцию с помощью ключевого слова return , за которым следует значение или переменная, которую вы хотите вернуть. Как только функция попадет в оператор return , она пропустит остальную часть тела и вернет связанное значение. Это может быть использовано для создания более сложного поведения внутри функции:
return ‘Number must be positive!’
result = inputNumber + 2
Вы можете видеть, что в этом случае, если входные данные меньше нуля, условное условие будет выполнено, что приводит к запуску первого оператора return, пропуская остальную часть кода в функции.
Вы можете передать в функцию любое количество входных данных, но количество входных данных всегда должно совпадать между тем, что определено в функции, и тем, что передается в нее при вызове функции. Например, мы можем расширить нашу простую функцию сложения, чтобы принять два числа, которые будут добавлены:
def addTwoNumbers(inputNumber1, inputNumber2):
result = inputNumber1 + inputNumber2
print addTwoNumbers(2, 3)
Вы также можете вернуть несколько значений, построив их в список, а затем извлекая их из возвращаемого списка. Давайте расширим нашу функцию, чтобы возвращать как сложение, так и умножение двух чисел
def twoNumbers(inputNumber1, inputNumber2):
addition = inputNumber1 + inputNumber2
multiplication = inputNumber1 * inputNumber2
return [addition, multiplication]
result = twoNumbers(2, 3)
print ‘addition: ‘ + str(result[0])
print ‘multiplication: ‘ + str(result[1])
Такие функции чрезвычайно полезны для создания эффективного и читаемого кода. Заключая определенные функциональные возможности в пользовательские модули, они позволяют вам (и, возможно, другим) очень эффективно использовать код, а также заставляют вас быть откровенными о различных наборах операций, происходящих в вашем коде. Вы можете видеть, что основное определение функций довольно просто, однако вы можете быстро начать определять более продвинутые логики, где функции вызывают друг друга и передают входы и возвраты очень сложными способами (вы даже можете передать функцию в качестве входных данных в другую функцию, что мы увидим позже в этих учебниках).
Объекты (Классы)
Шаг за пределы программирования с помощью функций — это объектно-ориентированное программирование или ООП. В ООП программы определяются не как список процедур, которые должны выполняться по очереди, а как набор взаимодействующих объектов. В традиционном процедурном подходе программа выполняется и завершается после выполнения всех процедур. С помощью ООП программа работает непрерывно, а объекты взаимодействуют и запускают различные модели поведения, основанные на событиях, происходящих в реальном времени.
Хотя мы не будем слишком углубляться в ООП в рамках этого курса, мы можем использовать некоторые из его принципов для разработки более сложных проектных пространств. Поэтому важно, по крайней мере, ознакомиться с тем, что такое объекты и как мы можем их использовать в самом базовом смысле. Объект в Python называется классом, но эти два слова часто используются взаимозаменяемо. Вы можете представить себе класс как структуру, которая инкапсулирует набор связанных функций (функции, принадлежащие определенным объектам, часто называются «методами» этого объекта) с набором локальных переменных, которые отслеживают состояние этого класса. Вместе эти переменные и методы определяют «поведение» объекта и определяют, как он взаимодействует с другими объектами в программной «среде».
Давайте подумаем об этом в повседневной жизни. Для животного примером метода может быть «бег». Многие вещи могут работать, поэтому определение работы как функции было бы общим и не обязательно относилось бы к тому, кто выполняет работу. С другой стороны, примером класса может быть «собака», которая будет иметь экземпляр метода «бег», а также другие методы, связанные с тем, чтобы быть собакой, такие как «еда» и «лай». Он также будет иметь набор переменных для хранения информации о данной собаке, такой как ее возраст, порода или вес. Другим классом может быть «человек», который будет хранить различные переменные и будет иметь свою собственную версию методов, таких как «бег» и «еда» (но, надеюсь, не «лай»).
Давайте определим очень простой класс, чтобы увидеть, как он работает. Мы будем использовать пример счетчика, который будет хранить значение и увеличивать это значение на основе запросов пользователей:
def addToCounter(self, inputValue):
Обратите внимание, что мы снова используем сокращение += для увеличения значения переменной count объекта на входное значение. Чтобы использовать этот класс, нам сначала нужно создать его экземпляр, который мы будем хранить в переменной так же, как и любой другой фрагмент данных:
Как только мы создадим экземпляр класса (называемый «instantiation»), мы сможем запустить методы этого экземпляра и запросить его переменные. Обратите внимание, что общее определение класса — это только конструкция. Все переменные внутри класса применяются только к определенному экземпляру, и методы могут выполняться только в том случае, если они связаны с этим экземпляром. Например:
Сразу же вы заметите некоторые различия между тем, как мы определяем функции и классы. Во-первых, никакие переменные не передаются в первой строке определения, так как ключевое слово class определяет только общую структуру класса. После первой строки вы найдете список переменных, которые являются локальными переменными этого класса, и будете отслеживать данные для отдельных экземпляров. После этого у вас будет коллекция локальных методов (помните, что «методы» — это просто функции, принадлежащие определенному классу), которые определяют функциональность класса. Эти методы определяются так же, как и раньше, за исключением того, что вы видите, что первым вводом всегда является ключевое слово self . Это представляет экземпляр объекта и всегда передается как первый входной сигнал в каждый метод в классе. Это позволяет вам запрашивать локальные переменные экземпляра, как вы можете видеть, что мы делаем с переменной count .
Чтобы вызвать метод внутри класса, вы используете имя переменной, хранящей экземпляр, и используете точечную нотацию » . » для вызова метода. Точка — это в основном ваш путь в экземпляр, а также все его данные и функциональность. Мы уже видели эту точку раньше, например, когда вызывали функцию .append() в списке. Это потому, что список на самом деле является классом сам по себе! Когда вы определяете список, вы фактически создаете экземпляр класса list , который наследует все функциональные возможности этого класса (сумасшествие, правда?). На самом деле в Python существует только небольшая коллекция примитивных типов данных (ints, floats, booleans и некоторые другие), а все остальное определяется как классы в рамках ООП. Даже строки — это специальные классы, которые хранят коллекцию символов.
Кстати, можно также использовать синтаксис » . » для запроса локальных переменных экземпляра класса. Например, если мы хотим найти значение переменной count myCounter , мы можем просто задать его, набрав:
Однако это не рекомендуется, поскольку оно раскрывает конечному пользователю истинное имя локальных переменных. В производственной среде это создало бы серьезные риски для безопасности, но считается плохой практикой даже в частном использовании. Вместо этого рекомендуется создать специальные методы accessor для извлечения значений переменных из экземпляра, как это было сделано с помощью метода getCount() в нашем примере. Еще одно преимущество этой практики (которая называется инкапсуляцией) заключается в том, что код легче поддерживать. Вы можете вносить любые изменения в определение класса, включая изменение имен локальных переменных и того, что они делают. Пока вы поддерживаете функции доступа и они возвращают ожидаемый результат, вам не нужно ничего обновлять в основном коде.
Что касается именования классов, то вы можете следовать тому же правилу, что и именование переменных или функций, однако стандартная практика заключается в том, чтобы прописывать каждое слово, включая первое.
Наконец, в приведенном выше примере каждый экземпляр, который мы делаем из CounterClass , будет запускать счетчик в 0 . Однако что делать, если мы хотим указать, каким должно быть это число, когда мы создаем экземпляр класса? Для этого мы можем реализовать метод __init__() (это два подчеркивания на каждой стороне ‘init’):
def __init__(self, inputValue):
def addToCounter(self, inputValue):
Как передать функцию как параметр в Python?
Поскольку функция в Python является объектом, ее можно передавать в другую функцию в качестве аргумента. При этом функция в аргументе указывается без круглых скобок, то есть передается только ссылка на функцию.
def calculate(a, b, operation): result = operation(a, b) return result def sum(a, b): return a + b def multiply(a, b) return a * b calculate(2, 3, sum) # 5 calculate(3, 4, multiply) # 12
Функции, которые могут принимать другие функции в качестве аргументов, также называются функциями высшего порядка. Яркий пример функции высшего порядка — функция map() , которая принимает аргументом функцию и итерируемую последовательность, и применяет функцию-параметр к каждому элементу последовательности.
Как создавать классы в Python со знанием дела: разбираем на примерах
Чтобы создавать классы в Python, нужно использовать ключевое слово class . Ну и еще пара-тройка деталей: прописать инициализацию, создать свойства и методы. Python же простой язык. И, может быть, разглагольствовать на эту тему ни к чему? Для тех, кого не устраивает короткое и «простое» объяснение, мы написали эту статью. Если и после ее прочтения возникнут проблемы, то тогда лучше записаться на курсы к нашим партнерам Mate Academy и Powercode. После них у вас не останется вопросов.
Інтенсивний курс від laba: Product management.
Від ідеї до успішного продукту.
Да, синтаксис изучить достаточно просто. Но сложность кроется в том мире, откуда пришло слово class . Это мир объектно-ориентированного программирования (ООП). Он не является очередной фантазией теоретиков, а наоборот, пытается отобразить реальный мир в виде объектов и связей между ними.
Так что Python — это (n+1)-й язык, в котором была реализована концепция ООП.
Что же такое ООП?
Мы уже упомянули слово class . Это формальная модель или шаблон, задающий структуру объекта. А что такое объект? Между классом и объектом такие же отношения, как между чертежом и изделием. Например, есть чертеж табуретки, по которому ее изготавливают. Но табуретки не существуют в живой природе. Приведем пример оттуда: допустим, мы решили смоделировать утку в компьютерной игре. Тогда получается, что объект уже есть, а класса нет. В этом случае нужно понаблюдать за уткой и создать модель, включив в нее нужные нам характеристики и ее поведение.
В какой-то момент мы поймем, что утка чем-то похожа на других птиц: значит, ее можно отнести к категории (обобщенному, родительскому классу, шаблону) «птицы». С другой стороны, для нашей игры не важно, как устроен организм утки: главное, что она как-то выглядит и как-то крякает. То есть мы абстрагируемся от ненужных для нашей модели нюансов. А кто-то другой добавит эти нюансы в свою модель и реализует, например, утиный анатомический атлас. Но некто третий использует обе модели и на их базе создаст свою. Это в двух словах о том, каким образом формируются модели и каким образом объекты подразделяются на классы.
Ефективний курс від laba: HR Business Partner.
Ставайте ключовим гравцем у розвитку бізнесу.
Утка и Птица — это классы, а утка1, утка2 и птица1, птица2 — объекты (экземпляры классов).
Концепция объектно-ориентированного программирования опирается на три известных утки кита. Давайте познакомимся с ними поближе.
Инкапсуляция
Этим словом обозначают сокрытие нюансов поведения объекта или его характеристик от «посторонних» программистов. Например, над компьютерной игрой работают два программиста. Один из них реализовал класс Утка, а другой просто хочет его использовать и у них случается такой диалог:
— Какой код написать, чтобы твоя утка полетела?
— Ты просто создай объект класса Утка: например, утка1 . А потом вызови у этого объекта функцию утка1.летать() (функции, созданные внутри класса, правильнее называть «методами» — о них поговорим в соответствующем разделе — прим.) .
И второму программисту, который использует этот класс Утка не нужно думать, как реализована функция летать() . Работает, и хорошо!
Наследование
В ООП принято «экономить» код, время и силы, не делая лишних движений. Например, у нас уже реализован класс Птица . Если нам нужно реализовать класс Утка, мы можем максимально использовать код класса Птица и добавить еще (желательно не сильно много) нового кода. Этот процесс и называется наследованием.
В примере выше Утка — наследник класса Птица. В то же время Птица — родительский класс по отношению к Утке. Вот так принято выражаться.
Полиморфизм
Сколько способов есть у утки, чтобы поздороваться с вами голосом? Наверное, она просто скажет что-то вроде «кря». Допустим, у нее есть один способ. Хорошо, сколько способов есть у лебедя? Они вроде как просто шипят. Допустим, тоже один. А если у птиц в целом? Ну не знаю… много. Однако все в целом по-прежнему будет называться словом «поздороваться»:
Освітній курс від laba: PR-комунікації.
Побудуйте успішний образ вашого бренду.
утка1.поздороваться() лебедь1.поздороваться()
Действие называется одинаково, но выполнено оно будет по-разному — в зависимости от объекта, который это действие будет выполнять. У объекта утка1 класса Утка и у объекта лебедь1 класса Лебедь это произойдет по-разному. В буквальном переводе греческое слово polýmorphos означает «многообразный». Поняли, да?
Далее перейдем к практике и разберемся, как создавать классы и делать с ними вот это все на Python. Остальную терминологию ООП и специальные термины из Python изучим по ходу дела.
Первый класс
class Bird: print("Я — класс Птица!")
Ключевое слово class мы уже обсудили. Оно ставится перед названием класса, которое пишется с большой буквы. Внутри класса для начала просто выведем сообщение.
Создаем объект (он же экземпляр класса) вот так — в одну строку:
b = Bird() b2 = Bird()
Имя объекта (здесь создан объект по имени b ) пишем с маленькой буквы. В этом случае внутри скобок нет параметров, но позже мы посмотрим, как и зачем их туда вписывать. Для второго объекта — аналогично. И вот такой результат работы этой программы мы получим:
Я — класс Птица! Я — класс Птица!
Птица, ты кто?
Пока мы толком ничего не можем сказать о созданном объекте b. Все потому, что мы создали для него пустой класс, который просто занимается самолюбованием. Давайте это исправим. Добавим один атрибут name классу Bird.
Атрибуты — это набор данных, характеризующих объект или его состояние.
Изменим структуру класса, добавив специальную инициализирующую функцию. В ней обычно атрибуты заполняются конкретными значениями, переданными в качестве параметров. В этом случае — параметр name . Тут, правда, есть еще один странный параметр — self . Это специальная переменная, содержащая ссылку на текущий экземпляр класса. Она помогает реализовать механизм ООП в Python. Поговорим о ней в следующем разделе.
class Bird: def __init__(self, name): self.name = name
Теперь можно дать каждой птице (объекту класса Bird) имя:
b = Bird("Сережа") print("Я птица. Меня зовут " + b.name); b2 = Bird("Жанна") print("Я птица. Меня зовут " + b2.name);
Запустив программу, получим:
Освітній курс від laba: Клієнтський сервіс.
Залучайте та зберігайте клієнтів.
Я птица. Меня зовут Сережа Я птица. Меня зовут Жанна
Чтобы получить значение атрибута, мы обращаемся к нему через имя объекта с точкой (b.name).
Ключевое слово self
Ранее мы уже пытались говорить про ключевое слово self . Давайте разберемся с ним чуть лучше. Это специальная зарезервированная переменная, содержащая ссылку на текущий объект (экземпляр класса). Когда мы пишем код внутри класса, возникает естественное желание использовать его атрибуты и методы. За пределами класса мы делали это так:
b.name b2.name
По аналогии внутри него мы будем делать это вот как:
Получается, что self заменяет имя любого объекта, когда мы пишем код внутри его класса.
В этом случае self заменяет имя объекта b или b2 внутри класса Bird .
Если внутри Bird использовать объект другого класса, слово self для него не применяется. Например, создадим класс Cird :
class Cird: def __init__(self): self.message = "Я объект c, класса Cird"
Создадим и используем объект класса Cird внутри класса Bird:
class Bird: def __init__(self, name): self.name = name c = Cird() print(c.message) b = Bird("Я объект b, класса Bird") print(b.name)
Получая значение атрибута ( c.message ) , мы используем имя конкретного объекта (то есть с ) , а не слово self .
Результат выполнения кода:
Я объект c, класса Cird Я объект b, класса Bird
Конструктор и инициализатор
Теперь стало немного понятнее, зачем нужен self в этом странном методе класса:
# для нашего класса Bird def __init__(self, name): self.name = name
Это один из его параметров. Он записывает значение второго параметра ( name ) в соответствующий атрибут текущего объекта. Там может быть и больше параметров. А может быть один только — self . Без него нельзя.
# для класса Cird def __init__(self): self.message = "Я объект c, класса Cird"
Хорошо… но почему это так? И зачем вообще нужен этот метод?
Это инициализатор. Обычно именно здесь в атрибуты объекта записываются значения. Это могут быть значения по умолчанию (как в классе Cird: self.message = «Я объект c, класса Cird» ) или значения, полученные с использованием параметров функции.
А как же создается сам объект?
В других языках программирования, например, существуют так называемые конструкторы. В Python тоже есть нечто похожее. Это специальный метод, который называется __new__ . Только в Python его код мы обычно не видим и не пишем сами. Такой конструктор существует и работает «за кулисами». В качестве единственного параметра он принимает класс, анализирует его структуру (код) и на базе этой структуры создает пустой объект. Инициализировать его — не царское дело. Пусть этим занимается инициализатор, а не конструктор. Просьба не путать их друг с другом.
Важно, что оба эти метода вызываются автоматически, когда мы создаем объект — сначала __new__ , потом __init__ . Например для b = Bird(“Сережа”) последовательность вызовов будет выглядеть так:
1. __new__(Bird) 2. __init__(b, "Сережа")
Статические и динамические атрибуты
Вот вы говорите конструктор, инициализатор… А что, если я объявлю и инициализирую атрибут вне метода __init__ ?
class Bird: ruClassName = "Птица" def __init__(self, name): self.name = name
И такое тоже практикуют. И такие атрибуты даже имеют свое название и применение:
b = Bird("Я объект b, класса " + Bird.ruClassName) print(b.name)
Атрибут ruClassName называется статическим. А атрибут name — динамическим. Заметьте, что внутри класса к статическим атрибутам мы не обращаемся через self . Вне класса мы обращаемся к статическим атрибутам не через с точкой , а через с точкой. То же самое, кстати, требуется делать со статическим атрибутом и внутри методов класса! Иначе работать не будет.
Статические атрибуты применяются для того, чтобы иметь одну общую переменную для всех объектов класса.
Дело в том, что при создании новых объектов создаются копии всех динамических атрибутов со сброшенными к «заводским настройкам» значениями. Статические атрибуты относятся не к объекту, а к классу и имеют только одну копию.
В примере выше статический атрибут ruClassName просто хранит название класса. В примере поинтереснее статический атрибут может служить для подсчета количества созданных объектов класса:
class Bird: ruClassName = "Птица" objInstancesCount = 0 def __init__(self, name): self.name = name Bird.objInstancesCount = Bird.objInstancesCount + 1 b = Bird("объект №1 класса " + Bird.ruClassName) print(b.name) b2 = Bird("объект №2 класса " + Bird.ruClassName) print(b2.name) print("Количество объектов класса " + Bird.ruClassName + ": " + str(Bird.objInstancesCount))
объект №1 класса Птица объект №2 класса Птица Количество объектов класса Птица: 2
Методы класса
Что, если нам дали задачу расширить пример из предыдущего раздела?
1. Нужно добавить Птице больше атрибутов:
- идентификационный номер ( id );
- возраст ( age ).
2. Реализовать возможность выводить атрибут имя ( name ), а также эти два атрибута для каждого объекта класса Птица.
С первым пунктом мы справимся легко, как говорится, по образу и подобию:
class Bird: ruClassName = "Птица" objInstancesCount = 0 def __init__(self, name, id, age): self.name = name self.id = id self.age = age Bird.objInstancesCount = Bird.objInstancesCount + 1
Вроде бы и вывод тоже можно сделать аналогично:
b = Bird("объект №1 класса " + Bird.ruClassName, 11, 3) print(b.name) print("Идентификационный номер: " + str(b.id)) print("Возраст: " + str(b.age)) b2 = Bird("объект №2 класса " + Bird.ruClassName, 10, 4) print(b2.name) print("Идентификационный номер: " + str(b2.id)) print("Возраст: " + str(b2.age)) print("Количество объектов класса " + Bird.ruClassName + ": " + str(Bird.objInstancesCount))
Результат работы кода:
объект №1 класса Птица Идентификационный номер: 11 Возраст: 3 объект №2 класса Птица Идентификационный номер: 10 Возраст: 4 Количество объектов класса Птица: 2
Но наш тимлид считает, что такой код некрасивый и не очень читабельный. Да и ООП рекомендует помещать код обработки данных объекта внутрь его класса. Попробуем:
class Bird: ruClassName = "Птица" objInstancesCount = 0 def __init__(self, name, id, age): self.name = name self.id = id self.age = age Bird.objInstancesCount = Bird.objInstancesCount + 1 def info(self): print(self.name) print("Идентификационный номер: " + str(self.id)) print("Возраст: " + str(self.age))
Функция info() внутри класса Bird называется методом. Теперь у каждого созданного объекта этого класса можно вызвать метод:
b = Bird("объект №1 класса " + Bird.ruClassName, 11, 3) b.info() b2 = Bird("объект №2 класса " + Bird.ruClassName, 10, 4) b2.info() print("Количество объектов класса " + Bird.ruClassName + ": " + str(Bird.objInstancesCount))
Код действительно выглядит более лаконично. Получается, что теперь каждый объект сам может сообщить информацию о себе. И теперь, используя объект, сторонний программист может не задумываться о том, как реализован вывод информации о нем. Он работает, и хорошо!
Результат работы такой же, как в предыдущем примере:
объект №1 класса Птица Идентификационный номер: 11 Возраст: 3 объект №2 класса Птица Идентификационный номер: 10 Возраст: 4 Количество объектов класса Птица: 2
Уровни доступа атрибута и метода
Реализация концепции ООП должна предусмотреть возможность запретить прямой доступ к атрибуту (или к методу) со стороны внешнего кода. То есть сделать его скрытым, приватным (термин из англоязычного ООП — private). Прямая модификация некоторых особо важных атрибутов может привести к дефектам в программе. Часто это нужно для того, чтобы оставлять доступ открытым («публичным», public) только к тем атрибутам и методам, которые будут взаимодействовать с внешним кодом. Остальные атрибуты и функции, которые, например, просто (или сложно) обслуживают свой класс, не должны быть доступны извне.
В Python это реализовано с помощью добавления к имени атрибута или метода одинарного или двойного подчеркивания. Первое используется в расчете на более сознательных и внимательных граждан. Второе — более жесткий вариант для всех остальных. Что это означает? Перейдем к конкретике и возьмем из нашего примера атрибут name:
- Если мы заменим name на _name, он станет скрытым атрибутом. Но к такому атрибуту еще можно получить доступ как обычно, через ._name. Это делать не рекомендуется, но в исключительных случаях те самые сознательные и внимательные граждане умеют без печальных последствий обращаться к скрытым атрибутам таким образом. Ну а исключения подтверждают правила.
- Если мы заменим name на __name, он станет по-настоящему закрытым атрибутом. И к нему уже нельзя будет получить доступ обычным способом. Интерпретатор языка Python выдаст ошибку AttributeError и не выполнит этот код.
По заветам ООП, и в первом, и во втором случае нужно узнать, написаны ли специальные методы для получения значения ( getter ) и/или модификации ( setter ) интересующего вас скрытого атрибута. Если да, то используйте их.
Эти методы обычно выглядят примерно так:
# getter def get_name(self): return self._name # setter def set_name(self, n): self._name = n
Перепишем весь класс:
class Bird: ruClassName = "Птица" objInstancesCount = 0 def __init__(self, name, id, age): self._name = name self.id = id self.age = age Bird.objInstancesCount = Bird.objInstancesCount + 1 # getter def get_name(self): return self._name # setter def set_name(self, n): self._name = n def info(self): print(self._name) print("Идентификационный номер: " + str(self.id)) print("Возраст: " + str(self.age))
Создадим объект и покажем правильный доступ к атрибуту _name (для __name все будет аналогично), который теперь обозначен как скрытый:
b = Bird("Сережа", 23, 2) print("Это " + b.get_name()) b.set_name("Иван") print("А, нет. Это " + b.get_name())
Результат работы программы:
Это Сережа А, нет. Это Иван
Оказывается, мы можем переименовать созданный объект. Здорово. Но что, если нам запрещено называть птиц Иванами?
Тогда мы можем сделать соответствующую проверку прямо в коде метода set_name . Она будет выполняться при каждом вызове метода set_name для любого объекта. Поэтому логично поместить ее именно внутрь класса. Иначе пришлось бы многократно добавлять ее во внешний код при каждой подобной манипуляции с атрибутом. Это нерационально, да и ООП советует делать подобную валидацию данных объекта внутри самого объекта. Так что, вот:
def set_name(self, n): if(n != "Иван"): self._name = n
Тогда программа будет работать по-другому:
Это Сережа А, нет. Это Сережа
Свойства
Есть способ упростить внешний код, который обращается вместо атрибута к его методам getter и setter . Начнем опять обращаться к атрибуту по его имени, только без подчеркивания. И пусть методы getter и setter вызываются автоматически, когда это необходимо. Правда, ради такого комфорта в код класса придется внести изменения. Для этого в Python существует специальная конструкция — декоратор @property :
# getter @property def name(self): return self._name # setter @name.setter def name(self, n): if(n != "Иван"): self._name = n
В остальном это обычные методы.
Код вызова извне тоже изменится:
b = Bird("Сережа", 23, 2) print("Это " + b.name) b.name = "Иван" print("А, нет. Это " + b.name)
Результат работы программы останется прежним:
Это Сережа А, нет. Это Сережа
Начать и закончить: наследование и полиморфизм
Про наследование и полиморфизм уже шла речь в начале статьи. Но тогда нельзя было приводить полноценные примеры на Python. Теперь, когда мы подтянули матчасть, написали и много раз переписали свой первый класс… как говорится, примеры в студию! Поэтому, если вам требуется время, чтобы вернуться в начало статьи и повторить эту тему — не волнуйтесь, я подожду.
Повторили? Тогда полетели дальше. Если что-то все равно не понятно и остались вопросы – милости просим с вопросами к практикующему ментору.
Наследование
Оттолкнемся от нашего класса Bird. Вспомним, как он выглядит:
class Bird: objInstancesCount = 0 def __init__(self, name, id, age): self._name = name self._id = id self._age = age Bird.objInstancesCount = Bird.objInstancesCount + 1 # getter @property def name(self): return self._name # setter @name.setter def name(self, n): if(n != "Иван"): self._name = n # getter @property def age(self): return self._age # setter @age.setter def name(self, age): if(age >= 0): self._age = age else: self._age = 0 # getter @property def id(self): return self._id def info(self): print("Имя: " + self._name) print("Идентификационный номер: " + str(self._id)) print("Возраст: " + str(self._age))
Создадим новый класс Duck (утка), который максимально использует код класса Bird и допишем в него немного своего кода. То есть Duck будет наследником Bird, который является родителем, базовым классом. Это в принципе ожидаемо: чтобы некая сферическая птица в вакууме стала уткой, достаточно добавить пару опознавательных знаков (потому что для примера мы придумали простую модель). Итак, пусть у класса Duck будут такие динамические атрибуты:
- скорость полета (fly_speed);
- высота полета (fly_height).
А также статический атрибут «вид» (species). Он всегда имеет значение, равное « Утка» .
Атрибуты «скорость полета» и «высота полета» бессмысленно помещать в базовый класс, потому что не все птицы умеют летать:
class Duck (Bird): species = "Утка" def __init__ (self, name, id, age, fly_speed, fly_height): super().__init__(name, id, age) self.__fly_speed = fly_speed self.__fly_height = fly_height @property def fly_speed(self): return self.__fly_speed @fly_speed.setter def fly_speed(self, fly_speed): self.__fly_speed = fly_speed @property def fly_height(self): return self.__fly_height @fly_height.setter def fly_height(self, fly_height): self.__fly_height = fly_height def info(self): super().info() print("Вид: " + Duck.species) print("Скорость полета: " + str(self.__fly_speed)) print("Высота полета: " + str(self.__fly_height))
В строке class Duck (Bird) мы в скобках указываем имя класса, от которого хотим делать наследование.
В методе def __init__ (self, name, id, age, fly_speed, fly_height) мы, помимо заполнения значений атрибутов текущего объекта класса Duck, вызываем инициализатор объекта базового класса (Bird):
super().__init__(name, id, age)
Только заметьте, что слово Bird мы заменяем на специальное слово super() . Оно обозначает базовый класс. Если не углубляться в дебри, то просто примите, что super() используется для вызова инициализатора и методов родительского класса внутри класса-наследника.
Теперь проверим, как работает это ваше наследование. Как обычно, создаем объект, заполняем его данными и просим рассказать о себе:
d = Duck("duck1", 22, 2, 110, 5) d.info()
Отметим, что внутри метода info() у утки (см. класс Duck) мы, по аналогии с инициализатором и чисто для полноты информации — вызываем свой метод info() у базового класса (то есть у Bird):
Ну вот, у нас получилась утка, которая может рассказать о себе и при этом помнит, что она Птица — не забывает свои корни. Жирным выделены атрибуты базового класса:
Идентификационный номер: 22
Скорость полета: 110
Отлично, с уткой все получилось удачно. Но означает ли это, что можно создавать еще много аналогичных классов-наследников, используя один и тот же код базового класса? А еще интереснее узнать вот что:
- можно ли в других наследниках создавать свой публичный метод с тем же названием info() , как у базового класса (Bird)?
- если для метода info() уже написан код внутри базового класса, будет ли этот код замещен собственным новым кодом при вызове метода info() у каждого из наследников?
Возьмем, например, галапагосского баклана, который не умеет летать . Этим он кардинально отличается от утки. Однако из-за этого он не перестает быть птицей. Галапагосский баклан — редкий вид, за ним ведется пристальное наблюдение. Поэтому в качестве его единственного динамического атрибута выберем количество особей (population). А также статический атрибут «вид» (species):
class GalaBacklane (Bird): species = "Галапагосский баклан" def __init__ (self, name, id, age, population): super().__init__(name, id, age) self.__population = population @property def population(self): return self.__population @population.setter def population(self, population): self.__population = population def info(self): super().info() print("Вид: " + GalaBacklane.species) print("Количество особей: " + str(self.__population))
Заметьте, что в строке def __init__ (self, name, id, age, population) присутствуют те же параметры id и age , которые были в инициализаторе внутри класса Duck. И затем также следует вызов:
super().__init__(name, id, age)
g = GalaBacklane("galaBacklane1", 22, 2, 60) g.info()
Теперь проверим, как это сработает на этот раз. Жирным выделены атрибуты, относящиеся к базовому классу:
Идентификационный номер: 22
Вид: Галапагосский баклан
Теперь сведем воедино работу объектов всех трех классов — родителя и наследников :
g = GalaBacklane("galaBacklane1", 22, 2, 60) g.info() b = Bird("bird1", 9, 4) b.info() d = Duck("duck1", 77, 9, 110, 5) d.info()
Создавать такие объекты можно в любом порядке. Как видите, вообще не обязательно, чтобы объект базового класса создавался первым.
Эти объекты никак не связаны друг с другом. Связаны родственными отношениями только их классы .
Жирным выделены атрибуты, относящиеся к базовому классу:
Идентификационный номер: 22
Вид: Галапагосский баклан
Количество особей: 60
Идентификационный номер: 9
Идентификационный номер: 77
Скорость полета: 110
Мы показали, что метод info() действительно работает по-разному — в зависимости от класса того объекта, который обращается к этому методу.
Мы применили все знания, полученные в статье, и только в конце смогли написать код в соответствии с парадигмой полиморфизма.
Надеюсь, тот, кто дочитал это до конца, получил ответ на свой вопрос!