Перейти к содержимому

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

  • автор:

В Питоне переменные передаются по ссылке или по значению? Есть подводные камни?

Неизменяемые объекты передаются по значению. Это значит, что при изменении значения переменной будет создан новый объект. К этому типу относятся:

  • числовые данные (int, float, complex)
  • символьные строки (str)
  • кортежи (tuple) При инициализации переменной незменяемого типа создается объект (например, целое число), этот объект имеет некоторый идентификатор:
>>> a = 10 >>> id(a) 10914656 

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

Изменяемые (mutable)

Изменяемые объекты передаются по ссылке. Это значит, что при изменении значения переменной объект будет изменен. К этому типу относятся:

  • списки (list)
  • множества (set)
  • словари (dict)

Подводные камни

Создадим список a , установим для переменной b ссылку на a , прибавим к b элемент списка и выведем их значения и идентификаторы на экран:

>>> a = [1, 2] >>> b = a >>> b.append(3) >>> print(a, b) [1, 2, 3] [1, 2, 3] >>> print(id(a), id(b)) 139748057891656 139748057891656 

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

>>> def add_value(a): . a.append(3) >>> b = [1, 2] >>> add_value(b) >>> print(b) [1, 2, 3] 

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

Что с этим можно сделать

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

новый_лист = старый_лист[:] 

Тоже самое можем сделать вот так:

новый_лист = list(старый_лист) 

Переменная новый_лист ссылается на новый объект:

>>> id(старый_лист), id(новый_лист) (139748050279112, 139748057891656) 

Это дает нам возможность изменять оба объекта независимо друг от друга.

Что почитать

  • Переменные-ссылки в Python
  • Understanding Python variables and Memory Management

Попробуйте бесплатные уроки по Python

Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.

Методы классов. Параметр self

Мы продолжаем изучать ООП языка Python. Как я говорил на первом занятии, класс может содержать свойства (данные) и методы (функции). Благодаря методам внутри класса можно реализовывать самые разные алгоритмы, то есть методы – это действия. Именно поэтому, в названиях методов используют глаголы, например:

set_value, get_param, start, stop, и т.п.

В то время как именами свойств (данных) выступают существительные:

color, size, x, y, и т.п.

Рекомендуется придерживаться этого простого правила.

Давайте, для примера объявим метод set_coords в классе Point, который будет просто выводить в консоль сообщение «вызов метода set_coords»:

class Point: color = 'red' circle = 2 def set_coords(self): print("вызов метода set_coords")

Здесь сразу бросается в глаза вот этот параметр self, который автоматически прописывает интегрированная среда. Зачем он здесь, если мы пока ничего не собираемся передавать этому методу? Давайте его уберем! Пока никаких проблем не возникло. Мало того, мы можем его вызвать из класса Point:

Point.set_coords()

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

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

pt = Point()

И, как мы с вами говорили, через объект pt можно обращаться ко всем атрибутам класса Point, в том числе и к методу set_coords:

pt.set_coords

Этот атрибут ссылается на объект-функцию, которую мы определили в классе Point. Попробуем ее вызвать:

pt.set_coords()

Видим ошибку, что в метод set_coords при вызове передается один аргумент, а он у нас определен без параметров. Дело в том, что когда мы вызываем методы класса через его объекты, то интерпретатор Python автоматически добавляет первым аргументом ссылку на объект, из которого этот метод вызывается.

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

class Point: color = 'red' circle = 2 def set_coords(self): print("вызов метода set_coords " + str(self))

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

Point.set_coords()

но можем через его объекты:

pt.set_coords()

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

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

Point.set_coords(pt)

Именно это на автомате делает Python, когда вызов осуществляется через объекты классов.

Так зачем понадобилось такое поведение? Дело в том, что метод класса – это тоже его атрибут и когда создаются экземпляры класса, то метод становится общим для всех объектов и не копируется в них. Фактически, только благодаря параметру self мы «знаем» какой объект вызвал данный метод и можем организовать с ним обратную связь.

Например, пусть метод set_coords задает координаты точек для текущего объекта. Тогда, мы пропишем в нем два дополнительных параметра и через self в самом экземпляре класса создадим (либо переопределим) два свойства:

class Point: color = 'red' circle = 2 def set_coords(self, x, y): self.x = x self.y = y

В результате, при вызове метода:

pt.set_coords(1, 2) print(pt.__dict__)

в объекте pt будут созданы два свойства x, y со значениями 1 и 2. Вот для чего нужен этот параметр self. Если в программе создать еще один объект:

pt2 = Point()

и через него вызвать тот же самый метод:

pt2.set_coords(10, 20) print(pt2.__dict__)

То увидим, что свойства x, y со значениями 10 и 20 были созданы только в нем (в его пространстве имен) и никак не связаны с координатами другого объекта pt или классом Point. То есть, через self мы работаем с конкретным объектом, из которого был вызван данный метод.

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

class Point: color = 'red' circle = 2 def set_coords(self, x, y): self.x = x self.y = y def get_coords(self): return (self.x, self.y)

И ниже в программе можем вызвать его:

print(pt.get_coords())

Интересно, что так как имя метода – это атрибут класса, то мы можем обратиться к нему через знакомую нам уже функцию:

res = getattr(pt, 'get_coords') print(res)

Видим, что это ссылка на объект-функцию. А раз так, то ничто нам не мешает ее здесь вызывать:

print(res())

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

Заключение

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

Видео по теме

Концепция ООП простыми словами

#1. Классы и объекты. Атрибуты классов и объектов

#2. Методы классов. Параметр self

#3. Инициализатор __init__ и финализатор __del__

#4. Магический метод __new__. Пример паттерна Singleton

#5. Методы класса (classmethod) и статические методы (staticmethod)

#6. Режимы доступа public, private, protected. Сеттеры и геттеры

#7. Магические методы __setattr__, __getattribute__, __getattr__ и __delattr__

#8. Паттерн Моносостояние

#9. Свойства property. Декоратор @property

#10. Пример использования объектов property

#11. Дескрипторы (data descriptor и non-data descriptor)

#12. Магический метод __call__. Функторы и классы-декораторы

#13. Магические методы __str__, __repr__, __len__, __abs__

#14 Магические методы __add__, __sub__, __mul__, __truediv__

#15. Методы сравнений __eq__, __ne__, __lt__, __gt__ и другие

#16. Магические методы __eq__ и __hash__

#17. Магический метод __bool__ определения правдивости объектов

#18. Магические методы __getitem__, __setitem__ и __delitem__

#19. Магические методы __iter__ и __next__

#20. Наследование в объектно-ориентированном программировании

#21. Функция issubclass(). Наследование от встроенных типов и от object

#22. Наследование. Функция super() и делегирование

#23. Наследование. Атрибуты private и protected

#24. Полиморфизм и абстрактные методы

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

#26. Коллекция __slots__

#27. Как работает __slots__ с property и при наследовании

#28. Введение в обработку исключений. Блоки try / except

#29. Обработка исключений. Блоки finally и else

#30. Распространение исключений (propagation exceptions)

#31. Инструкция raise и пользовательские исключения

#32. Менеджеры контекстов. Оператор with

#33. Вложенные классы

#34. Метаклассы. Объект type

#35. Пользовательские метаклассы. Параметр metaclass

#36. Метаклассы в API ORM Django

#37. Введение в Python Data Classes (часть 1)

#38. Введение в Python Data Classes (часть 2)

#39. Python Data Classes при наследовании

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

Как создавать классы в Python со знанием дела: разбираем на примерах

Чтобы создавать классы в Python, нужно использовать ключевое слово class . Ну и еще пара-тройка деталей: прописать инициализацию, создать свойства и методы. Python же простой язык. И, может быть, разглагольствовать на эту тему ни к чему? Для тех, кого не устраивает короткое и «простое» объяснение, мы написали эту статью. Если и после ее прочтения возникнут проблемы, то тогда лучше записаться на курсы к нашим партнерам Mate Academy и Powercode. После них у вас не останется вопросов.

Детям из Мариуполя нужно 120 ноутбуков для обучения — подари старое «железо», пусть оно работает на будущее Украины

Да, синтаксис изучить достаточно просто. Но сложность кроется в том мире, откуда пришло слово class . Это мир объектно-ориентированного программирования (ООП). Он не является очередной фантазией теоретиков, а наоборот, пытается отобразить реальный мир в виде объектов и связей между ними.

Так что Python — это (n+1)-й язык, в котором была реализована концепция ООП.

Что же такое ООП?

Мы уже упомянули слово class . Это формальная модель или шаблон, задающий структуру объекта. А что такое объект? Между классом и объектом такие же отношения, как между чертежом и изделием. Например, есть чертеж табуретки, по которому ее изготавливают. Но табуретки не существуют в живой природе. Приведем пример оттуда: допустим, мы решили смоделировать утку в компьютерной игре. Тогда получается, что объект уже есть, а класса нет. В этом случае нужно понаблюдать за уткой и создать модель, включив в нее нужные нам характеристики и ее поведение.

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

Курс Fullstack Web Development.

Fullstack developer вміє працювати як з фронтендом, так і з бекендом сайта, що дозволяє релізовувати будь-яку ідею комплексно і самостійно. Ця професія різноманітна й творча, ти точно не будеш нудьгувати.

Утка и Птица — это классы, а утка1, утка2 и птица1, птица2 — объекты (экземпляры классов).

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

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

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

— Какой код написать, чтобы твоя утка полетела?

— Ты просто создай объект класса Утка: например, утка1 . А потом вызови у этого объекта функцию утка1.летать() (функции, созданные внутри класса, правильнее называть «методами» — о них поговорим в соответствующем разделе — прим.) .

И второму программисту, который использует этот класс Утка не нужно думать, как реализована функция летать() . Работает, и хорошо!

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

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

В примере выше Утка — наследник класса Птица. В то же время Птица — родительский класс по отношению к Утке. Вот так принято выражаться.

Полиморфизм

Сколько способов есть у утки, чтобы поздороваться с вами голосом? Наверное, она просто скажет что-то вроде «кря». Допустим, у нее есть один способ. Хорошо, сколько способов есть у лебедя? Они вроде как просто шипят. Допустим, тоже один. А если у птиц в целом? Ну не знаю… много. Однако все в целом по-прежнему будет называться словом «поздороваться»:

Курс Стратегічний маркетинг.

Від хаосу до системного маркетингу разом із Тетяною Лукинюк, B2C-директором у Kyivstar, колишнім CMO у Coca-Cola, Mars Ukraine та генеральною директоркою у Red Bull Ukraine.

утка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);

Запустив программу, получим:

Курс Python.

Цю мову програмування використовують від базових концепцій до складних застосувань у сферах штучного інтелекту. Після проходження курсу гарантовано отримай роботу в ІТ.

Я птица. Меня зовут Сережа Я птица. Меня зовут Жанна

Чтобы получить значение атрибута, мы обращаемся к нему через имя объекта с точкой (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:

  1. Если мы заменим name на _name, он станет скрытым атрибутом. Но к такому атрибуту еще можно получить доступ как обычно, через ._name. Это делать не рекомендуется, но в исключительных случаях те самые сознательные и внимательные граждане умеют без печальных последствий обращаться к скрытым атрибутам таким образом. Ну а исключения подтверждают правила.
  2. Если мы заменим 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() действительно работает по-разному — в зависимости от класса того объекта, который обращается к этому методу.

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

Надеюсь, тот, кто дочитал это до конца, получил ответ на свой вопрос!

Курс Front-end Basic.

Оволодій навичками розробки веб-інтерфейсів та стань справжнім Front-end розробником! Заробляй від 800$ на початку карʼєри.

Примеры работы с классами в Python

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

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

Оператор class создает новое определение класса. Имя класса сразу следует за ключевым словом class , после которого ставиться двоеточие:

class ClassName: """Необязательная строка документации класса""" class_suite
  • У класса есть строка документации, к которой можно получить доступ через ClassName.__doc__ .
  • class_suite состоит из частей класса, атрибутов данных и функции.

Пример создания класса на Python:

 
class Employee: """Базовый класс для всех сотрудников""" emp_count = 0 def __init__(self, name, salary): self.name = name self.salary = salary Employee.emp_count += 1 def display_count(self): print('Всего сотрудников: %d' % Employee.empCount) def display_employee(self): print('Имя: <>. Зарплата: <>'.format(self.name, self.salary))
  • Переменная emp_count — переменная класса, значение которой разделяется между экземплярами этого класса. Получить доступ к этой переменной можно через Employee.emp_count из класса или за его пределами.
  • Первый метод __init__() — специальный метод, который называют конструктором класса или методом инициализации. Его вызывает Python при создании нового экземпляра этого класса.
  • Объявляйте другие методы класса, как обычные функции, за исключением того, что первый аргумент для каждого метода self . Python добавляет аргумент self в список для вас; и тогда вам не нужно включать его при вызове этих методов.

Создание экземпляров класса

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

 
# Это создаст первый объект класса Employee emp1 = Employee("Андрей", 2000) # Это создаст второй объект класса Employee emp2 = Employee("Мария", 5000)

Доступ к атрибутам

Получите доступ к атрибутам класса, используя оператор . после объекта класса. Доступ к классу можно получить используя имя переменой класса:

 
emp1.display_employee() emp2.display_employee() print("Всего сотрудников: %d" % Employee.emp_count)

Теперь, систематизируем все.

 
class Employee: """Базовый класс для всех сотрудников""" emp_count = 0 def __init__(self, name, salary): self.name = name self.salary = salary Employee.emp_count += 1 def display_count(self): print('Всего сотрудников: %d' % Employee.emp_count) def display_employee(self): print('Имя: <>. Зарплата: <>'.format(self.name, self.salary)) # Это создаст первый объект класса Employee emp1 = Employee("Андрей", 2000) # Это создаст второй объект класса Employee emp2 = Employee("Мария", 5000) emp1.display_employee() emp2.display_employee() print("Всего сотрудников: %d" % Employee.emp_count)

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

Имя: Андрей. Зарплата: 2000 Имя: Мария. Зарплата: 5000 Всего сотрудников: 2 

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

 
emp1.age = 7 # Добавит атрибут 'age' emp1.age = 8 # Изменит атрибут 'age' del emp1.age # Удалит атрибут 'age'

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

  • getattr(obj, name [, default]) — для доступа к атрибуту объекта.
  • hasattr(obj, name) — проверить, есть ли в obj атрибут name .
  • setattr(obj, name, value) — задать атрибут. Если атрибут не существует, он будет создан.
  • delattr(obj, name) — удалить атрибут.
 
hasattr(emp1, 'age') # возвращает true если атрибут 'age' существует getattr(emp1, 'age') # возвращает значение атрибута 'age' setattr(emp1, 'age', 8) #устанавливает атрибут 'age' на 8 delattr(empl, 'age') # удаляет атрибут 'age'

Встроенные атрибуты класса

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

  • __dict__ — словарь, содержащий пространство имен класса.
  • __doc__ — строка документации класса. None если, документация отсутствует.
  • __name__ — имя класса.
  • __module__ — имя модуля, в котором определяется класс. Этот атрибут __main__ в интерактивном режиме.
  • __bases__ — могут быть пустые tuple, содержащие базовые классы, в порядке их появления в списке базового класса.

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

 
class Employee: """Базовый класс для всех сотрудников""" emp_count = 0 def __init__(self, name, salary): self.name = name self.salary = salary Employee.empCount += 1 def display_count(self): print('Всего сотрудников: %d' % Employee.empCount) def display_employee(self): print('Имя: <>. Зарплата: <>'.format(self.name, self.salary)) print("Employee.__doc__:", Employee.__doc__) print("Employee.__name__:", Employee.__name__) print("Employee.__module__:", Employee.__module__) print("Employee.__bases__:", Employee.__bases__) print("Employee.__dict__:", Employee.__dict__)

Когда этот код выполняется, он возвращает такой результат:

Employee.__doc__: Базовый класс для всех сотрудников Employee.__name__: Employee Employee.__module__: __main__ Employee.__bases__: (,) Employee.__dict__: , 'display_count': , 'display_employee': , '__dict__': , '__weakref__': > 

Удаление объектов (сбор мусора)

Python автоматически удаляет ненужные объекты (встроенные типы или экземпляры классов), чтобы освободить пространство памяти. С помощью процесса ‘Garbage Collection’ Python периодически восстанавливает блоки памяти, которые больше не используются.

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

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

a = 40 # создали объект b = a # увеличивает количество ссылок c = [b] # увеличивает количество ссылок del a # уменьшает количество ссылок b = 100 # уменьшает количество ссылок c[0] = -1 # уменьшает количество ссылок

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

Пример работы __del__()
Деструктор __del__() выводит имя класса того экземпляра, который должен быть уничтожен:

 
class Point: def __init__(self, x=0, y=0): self.x = x self.y = y def __del__(self): class_name = self.__class__.__name__ print('<> уничтожен'.format(class_name)) pt1 = Point() pt2 = pt1 pt3 = pt1 print(id(pt1), id(pt2), id(pt3)) # выведите id объектов del pt1 del pt2 del pt3

Когда вышеуказанный код выполняется и выводит следующее:

17692784 17692784 17692784 Point уничтожен

В идеале вы должны создавать свои классы в отдельном модуле. Затем импортировать их в основной модуль программы с помощью import SomeClass .

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

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

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

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

Синтаксис наследования класса

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

class SubClassName(ParentClass1[, ParentClass2, . ]): """Необязательная строка документации класса""" class_suite

Пример наследования класса в Python

 
class Parent: # объявляем родительский класс parent_attr = 100 def __init__(self): print('Вызов родительского конструктора') def parent_method(self): print('Вызов родительского метода') def set_attr(self, attr): Parent.parent_attr = attr def get_attr(self): print('Атрибут родителя: <>'.format(Parent.parent_attr)) class Child(Parent): # объявляем класс наследник def __init__(self): print('Вызов конструктора класса наследника') def child_method(self): print('Вызов метода класса наследника') c = Child() # экземпляр класса Child c.child_method() # вызов метода child_method c.parent_method() # вызов родительского метода parent_method c.set_attr(200) # еще раз вызов родительского метода c.get_attr() # снова вызов родительского метода

Когда этот код выполняется, он выводит следующий результат:

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

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

class A: # объявите класс A . class B: # объявите класс B . class C(A, B): # C наследуется от A и B . 

Вы можете использовать функции issubclass() или isinstance() для проверки отношений двух классов и экземпляров.

  • Логическая функция issubclass(sub, sup) возвращает значение True , если данный подкласс sub действительно является подклассом sup .
  • Логическая функция isinstance(obj, Class) возвращает True , если obj является экземпляром класса Class или является экземпляром подкласса класса.

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

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

Пример переопределения методов:

 
class Parent: # объявите родительский класс def my_method(self): print('Вызов родительского метода') class Child(Parent): # объявите класс наследник def my_method(self): print('Вызов метода наследника') c = Child() # экземпляр класса Child c.my_method() # метод переопределен классом наследником

Когда этот код выполняется, он производит следующий результат:

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

Популярные базовые методы

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

№ Метод, описание и пример вызова
1 __init__(self [, args. ]) — конструктор (с любыми необязательными аргументами)
obj = className(args)
2 __del__(self) — деструктор, удаляет объект
del obj
3 __repr__(self) — программное представление объекта
repr(obj)
4 __str__(self) — строковое представление объекта
str(obj)

Пример использования __add__

Предположим, вы создали класс Vector для представления двумерных векторов. Что происходит, когда вы используете дополнительный оператор для их добавления? Скорее всего, Python будет против.

Однако вы можете определить метод __add__ в своем классе для добавления векторов и оператор + будет вести себя так как нужно.

 
class Vector: def __init__(self, a, b): self.a = a self.b = b def __str__(self): return 'Vector (<>, <>)'.format(self.a, self.b) def __add__(self, other): return Vector(self.a + other.a, self.b + other.b) v1 = Vector(2, 10) v2 = Vector(5, -2) print(v1 + v2)

При выполнении этого кода, мы получим:

Vector(7, 8)

Приватные методы и атрибуты

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

Пример приватного атрибута:

 
class JustCounter: __secret_count = 0 def count(self): self.__secret_count += 1 print(self.__secret_count) counter = JustCounter() counter.count() counter.count() print(counter.__secret_count)

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

1 2 Traceback (most recent call last): File "test.py", line 12, in print(counter.__secret_count) AttributeError: 'JustCounter' object has no attribute '__secret_count'

Вы можете получить доступ к таким атрибутам, так object._className__attrName . Если вы замените свою последнюю строку следующим образом, то она будет работать.

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *