Классы в 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() |