Классы в Python

Содержание
Введение
Атрибуты
__init__()
Статические атрибуты
Объекты
Объекты и экземпляры класса
__dict__
Далее по теме

Введение

Классы нужно называть с заглавной буквы и использовать CamelCase.

Создается новый класс с помощью class

Объект данного класса создаётся с помощью =

объект = ИмяКласса()

Самый простой пример использования

class Site: pass my_site = Site()

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

Этот процесс называется инстанцированием (instantiation).

В англоязычной среде экземпляр класса называется instance (instance of the class).

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

print(dir(my_site))

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

__class__

C помощью встроенного атрибута __class__ можно увидеть принадлежность my_site классу Site.

Также для этой цели можно использовать функцию type()

class Site: pass my_site = Site() print(my_site.__class__) # либо print(type(my_site))

<class '__main__.Site'>

Когда мы создаём новый экземпляр класса

my_site = Site()

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

Из этой информации можно выделить минимум две важные детали:

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

Во-вторых, у метод __init__() есть параметр по-умолчанию, который нужен для получения доступа к памяти, которую под объект выделил метод __new__(). Именно этот параметр принято называть self.

Классы без кастомных атрибутов и методов могут использоваться для обработки ошибок

class MyAPIException(Exception): """ My API exception. """

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

Атрибуты

Атрибуты это свойства объекта. Бытовой пример: у велосипеда есть страна производитель , масса, цвет, количество скоростей, цена и так далее.

У сайта в интернете есть, url, год делегирования домена, автор, основная тема.

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

Синтаксис при создании и при доступе следующий

объект.имя_атрибута

Это равносильно следующему синтаксису с явным обращением к __dict__()

объект.__dict__["имя_атрибута"]

Создадим класс Site и объект hh с адресом сайта.

class Site: pass hh = Site() hh.url = "https://www.heihei.ru"

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

print(dir(hh))

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'url']

Видно, что к дефолтным атрибутам добавился атрибут url

В следующем примере создадим два экземпляра одного и того же класса Employee

class Employee: pass emp_1 = Employee() emp_2 = Employee() print(emp_1) print(emp_2)

python emp_ex.py

<__main__.Employee object at 0x7fca67c26fd0> <__main__.Employee object at 0x7fca67c26fa0>

Объекты emp_1 и emp_2 имеют разные адреса в памяти.

Зададим несколько атрибутов

class Employee: pass emp_1 = Employee() emp_2 = Employee() emp_1.first = 'Yuri' emp_1.last = 'Gagarin' emp_1.email = 'Yuri.Gagarin@vostok.su' emp_1.pay = 1000 emp_2.first = 'Test' emp_2.last = 'User' emp_2.email = 'Test.User@vostok.su' emp_2.pay = 1234 print(emp_1.email) print(emp_2.email)

python emp_ex.py

Yuri.Gagarin@vostok.su Test.User@vostok.su

__init__

Создавать атрибуты способом из предыдущего параграфа не всегда удобно.

Обычно используют метод __init__()

Важно понимать следующее

Каждый раз когда создаётся новый объект данного класса сразу после метода __new__() вызывается метод __init__().

Разница в том, что в прошлом параграфе мы не вносили в него никаких изменений и он просто создавал объект.

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

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

Далее разберёмся с тем что такое self и продолжим изучение __init__()

self

Когда создается новый объект класса метод __new__() выделяет память под этот объект и передаёт его в метод __init__()

То есть метод __init__() изначально ожидает получить хотя бы один аргумент.

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

self.атрибут = значение

Или если вы делаете атрибут, который не должен быть доступен извне - добавьте перед именем _

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

self._атрибут = значение

Создадим класс Cat, у которого будет один атрибут, отвечающий за породу - breed.

class Cat: def __init__(kitty): kitty.breed = "burma" my_cat = Cat() print(my_cat.breed)

burma

У класса Cat минимум два недостатка:

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

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

Внесём исправления

class Cat: def __init__(self, breed): self.breed = breed

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

Переименовываем Cat на Dog

class Dog: def __init__(self, breed): self.breed = breed

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

my_dog = Dog()

Уже не сработает

Traceback (most recent call last): File "/home/andrei/python/sample.py", line 6, in <module> my_dog = Dog() TypeError: __init__() missing 1 required positional argument: 'breed'

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

my_dog = Dog("husky")

Либо в методе __init__() нужно указать значение по умолчанию

class Dog: def __init__(self, breed="aidi"): self.breed = breed

Новичкам может показаться странной строка

self.breed = breed

Здесь главное усвоить следующее:

Именно self.breed - задаёт название атрибута, которое будет у объекта данного класса.

В данном случае атрибут будет называться breed

breed - это название параметра у метода __init__().

В данном случае - первый аргумент, передаваемый вами в метод __init__() называется breed

Можно назвать его и по-другому, например arg1, суть от этого не изменится, но если их будет много - тяжелее станет запоминать какому атрибуту соответствует, скажем, arg99.

class Dog: def __init__(self, arg1): self.breed = arg1

Теперь попробую объяснить тоже самое, но немного по-другому и уже с двумя атрибутами

class Dog: def __init__(self, name, age): self.name = name self.age = age

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

Зелёным я выделил названия параметров. Часто в примерах у атрибута и параметра одинаковые названия. Это не обязательное условие, которое может помешать пониманию сути.

Сравните следующие объявления классов.

Только формальные названия из которых ничего не понятно

class Dog: def __init__(self, arg1, arg2): self.attr1 = arg1 self.attr2 = arg2 pet = Dog("Tuzik", 2) print(pet.attr1) print(pet.attr2)

Понятные названия, но у атрибутов и у параметров __init__() они разные

class Dog: def __init__(self, name, age): self.nick = name self.years = age pet = Dog("Tuzik", 2) print(pet.nick) print(pet.year)

Понятные и одинаковые названия

class Dog: def __init__(self, name, age): self.name = name self.age = age pet = Dog("Tuzik", 2) print(pet.name) print(pet.age)

Результат вызова у всех трёх вариантов одинаковый

Tuzik
2

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

Если вам кажется, что self.name = name это избыточный код , изучите статью «Python dataclass»

Важно понимать, что метод __init__() может создавать атрибуты не по принципу один к одному - получил аргумет и записал его в атрибут а комбинируя различные аргументы

class Employee: def __init__(self, name, surname): self.fullname = name + "_" + surname tester = Employee("Max", "Petrov") print(tester.fullname)

В примере выше при создании объекта tester мы в __init__() передали аргументы name и surname, на основе которых был создан атрибут fullname.

Никаких отдельных атрибутов для name и surname создано не было и попытка обратиться к ним приведёт к ошибкам

AttributeError: 'Employee' object has no attribute 'name'
AttributeError: 'Employee' object has no attribute 'surname'

Пример с тремя атрибутами, сделаем третий атрибут логическим типом (boolean)

class Dog: def __init__(self, breed, name, spots): self.breed = breed self.name = name self.spots = spots my_dog = Dog("husky", "Barbos", True) print(my_dog.breed, my_dog.name, my_dog.spots)

husky Barbos True

В начале статье мы обсуждали атрибут __dict__

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

print(my_dog.__dict__)

{'breed': 'husky', 'name': 'Barbos', 'spots': True}

Статические атрибуты

Можно задавать атрибуты сразу для всех элементов класса. Они называются статическими (static attributes, class object attributes)

class Dog: # CLASS OBJECT ATTRIBUTE # SAME FOR ANY INSTANCE OF A CLASS bioclass = "mammal" def __init__(self, breed, name, spots): self.breed = breed self.name = name self.spots = spots # При создании объекта static атрибут можно не указывать явно my_dog = Dog("husky", "Barbos", True) # Проверим значение bioclass заданное по умолчанию print(Dog.bioclass, my_dog.bioclass)

mammal mammal

# Переопределим значение bioclass для my_dog my_dog.bioclass = "super mammal" print(Dog.bioclass, my_dog.bioclass)

mammal super mammal

Объекты

Все классы в Python кроме Exception наследуются от object

>>> o = object()
>>> dir(o)

['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

Пример

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

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

Объекты класса (class objects) и экземпляры класса (instance of class) это разные вещи.

class создаёт именованную ссылку на объект класса.

Рассмотрим класс Resolver из статьи про кэширование

import socket class Resolver: def __init__(self): self._cache = {} def __call__(self, host): if host not in self._cache: self._cache[host] = socket.gethostbyname(host) return self._cache[host] def clear(self): self._cache.clear() def has_host(self, host): return host in self._cache

Импортируем в REPL класс Resolver

python >>> from resolver import Resolver

Если с помощью REPL выполнить (evaluate) Resolver

>>> Resolver

REPL покажет представление (representation) объекта класса (class object)

<class 'resolver.Resolver'>

Этот объект класса является вызываемым (callable) чем мы и пользуемся

resolve = Resolver()

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

Классы создают новые сущности в момент вызова.

О создании сущностей мы поговорим позже.

def main(): seq = sequence_class(immutable=True) t = seq("Timbuktu") print(t) print(type(t)) def sequence_class(immutable): if immutable: cls = tuple else: cls = list return cls if __name__ == "__main__": main()

python sequence_class.py

('T', 'i', 'm', 'b', 'u', 'k', 't', 'u') <class 'tuple'>

def main(): seq = sequence_class(immutable=False) t = seq("Timbuktu") print(t) print(type(t)) def sequence_class(immutable): return tuple if immutable else list if __name__ == "__main__": main()

python sequence_class.py

['T', 'i', 'm', 'b', 'u', 'k', 't', 'u'] <class 'list'>

__dict__

Объекты в Python имеют вид словарей . Изучить словарь нашего нового объекта my_site можно с помощью атрибута __dict__

class Site: pass my_site = Site() print(my_site.__dict__)

{}

Словарь пустой, потому что класс пустой.

При помощи __dict__ просходит создание атрибутов и обращение к ним.

Хотя обычно для краткости пользуются обращением через .

x.__dict__["a"] # то же самое что x.a

Пример

class Site: pass hh = Site() hh.__dict__["url"] = "https://topbicycle.ru" print(hh.__dict__["url"]) # то же самое что print(hh.url) hh.url = "https://www.heihei.ru" print(hh.__dict__["url"]) # то же самое что print(hh.url)

https://topbicycle.ru https://topbicycle.ru https://www.heihei.ru https://www.heihei.ru

Вернёмся к классу Dog с тремя атрибутами и изучим содержимое __dict__

class Dog: def __init__(self, breed, name, spots): self.breed = breed self.name = name self.spots = spots my_dog = Dog("husky", "Barbos", True) print(my_dog.__dict__)

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

{'breed': 'husky', 'name': 'Barbos', 'spots': True}

Похожие статьи
ООП в Python
Классы
Методы
class variables
class methods
Статические методы
Наследование
Специальные методы
dataclass
__slots__
Декоратор property
super()

Поиск по сайту

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Перейти на канал

@aofeed

Задать вопрос в Телеграм-группе

@aofeedchat

Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@urn.su если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящую по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: