Классы в 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__ , либо после создания, указывая имена новых атрибутов через . после имени объекта.
Можно ли сказать, что в Python атрибут это переменная связанная с экземпляром класса ( instance variable )?
Отчасти: каждая instance variable это атрибут, но не каждый атрибут - instance variable.
Синтаксис при создании и при доступе следующий
объект.имя_атрибута
Это равносильно следующему синтаксису с явным обращением к __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
Обращение к атрибутам
Если обратиться к несуществующему атрибуту появится AttributeError
class A: pass a = A() print(a.x)
Traceback (most recent call last): File "C:\A\attr_demo.py", line 6, in <module> print(a.x) ^^^ AttributeError: 'A' object has no attribute 'x'
Безопасный способ получить атрибут если он есть - это применение getattr()
class A: pass a = A() a.existing = 1 print(getattr(a, "existing", None)) print(getattr(a, "missing", None))
1 None
Или hasattr()
Создадим переменную типа int и применим hasattr()
x = 1 if hasattr(x, "real"): print(x.real) else: print(None)
1
Придумаем несуществующий атрибут и применим hasattr()
x = 1 if hasattr(x, "unreal"): print(x.real) else: print(None)
None
Скрипт отработал без ошибки.
__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__)
{}
Словарь пустой, потому что класс пустой.
Иногда в целях оптимизации памяти и запрета добавлять новые атрибуты использут __slots__ тогда у экземпляров класса не будет своих атрибутов __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}
Способы создания классов
В этой статье мы рассмотрели два способа создания классов без конструктора и классический способ с конструктором.
class Employee: pass tester = Employee() tester.name = "Max" tester.salary = 5500 class Employee: def __init__(self, name, salary): self.name = name self.salary = salary developer = Employee("Alex", 7000)
В следующих статьях будут показаны и другие варианты.
Классический способ с конструктором + с применением
__slots__
class Employee: __slots__ = ["name", "salary"] def __init__(self, name, salary): self.name = name self.salary = salary manager = Employee("Tim", 8000)
Классический dataclass и dataclass с использованием __slots__
@dataclass() class Project: site: str manager: str improvement = Project("Improvement", "Yuri") @dataclass(slots=True) class Upgrade: system: str version: int upg = Upgrade("Fixer", 16)
Специальные случаи, например, с наследованием от enum
from enum import Enum # Using enum class create enumerations class Days(Enum): Mon = 1 Tue = 2 Wed = 3 Sun = 7
Автор статьи: Андрей Олегович
| ООП в Python | |
| Классы | |
| Методы | |
| class variables | |
| class methods | |
| Статические методы | |
| Наследование | |
| super() | |
| Специальные методы | |
| dataclass | |
| __slots__ | |
| Декоратор property | |
| Полиморфизм |