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

Как получить атрибуты класса

  • автор:

Атрибуты класса и переменные экземпляра класса в Python

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

Python вызывает специальный метод __init__() , который называют конструктором класса, каждый раз при создании нового экземпляра класса.

class Dog: # атрибут данных (переменная класса), # общая для всех экземпляров класса kind = 'canine' def __init__(self, name): # переменная экземпляра класса # уникальна для каждого экземпляра self.name = name >>> d = Dog('Fido') >>> e = Dog('Buddy') # переменная `kind` будет общая для # всех экземпляров объекта `Dog` >>> d.kind # 'canine' >>> e.kind # 'canine' # переменная `name` будет уникальна # для каждого из экземпляров >>> d.name # 'Fido' >>> e.name # 'Buddy' 

Как говорилось в материале «Классы в языке Python», общие данные могут иметь неожиданный эффект при использовании изменяемых объектов, таких как списки и словари. Например, список tricks (трюки, которые может делать отдельная собака) в примере ниже не следует использовать как атрибут данных/переменную класса, потому что для всех экземпляров класса Dog будет использоваться только один атрибут данных tricks :

class Dog: # ошибочное использование атрибута tricks - # переменной класса tricks = [] def __init__(self, name): self.name = name def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') # неожиданно разделяется всеми собаками >>> d.tricks # ['roll over', 'play dead'] 

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

class Dog: def __init__(self, name): self.name = name # создает новый пустой список # трюков для каждой собаки self.tricks = [] def add_trick(self, trick): self.tricks.append(trick) >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.add_trick('roll over') >>> e.add_trick('play dead') >>> d.tricks # ['roll over'] >>> e.tricks # ['play dead'] 

Если одно и то же имя атрибута встречается как в экземпляре класса, так и в самом классе, то поиск атрибута определяет приоритет экземпляра класса:

>>> class Warehouse: purpose = 'storage' region = 'west' >>> w1 = Warehouse() >>> print(w1.purpose, w1.region) # storage west >>> w2 = Warehouse() >>> w2.region = 'east' >>> print(w2.purpose, w2.region) # storage east 

На атрибуты данных класса могут ссылаться как методы, так и обычные пользователи — «клиенты» объекта. Другими словами, классы не могут использоваться для реализации чисто абстрактных типов данных. Фактически, ничто в Python не позволяет принудительно скрывать данные — все основано на соглашении.

Клиенты должны использовать переменные класса с осторожностью — клиенты могут испортить инварианты, поддерживаемые методами, изменив их атрибуты данных. Обратите внимание, что клиенты могут добавлять свои собственные атрибуты данных к объекту экземпляра, не влияя на достоверность методов, до тех пор, пока избегаются конфликты имен — опять же, соглашение об именовании может сэкономить здесь много головной боли.

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

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

  • getattr(obj, name [, default]) — для доступа к атрибуту name объекта класса obj .
  • hasattr(obj, name) — проверить, есть ли в классе obj атрибут name .
  • setattr(obj, name, value) — задать атрибут name со значением value . Если атрибут не существует, он будет создан.
  • delattr(obj, name) — удалить атрибут name из объекта класса obj .
Встроенные атрибуты класса.

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

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

Где хранятся атрибуты класса и экземпляра класса?

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

class MyClass: class_attr = "Class" def __init__(self): self.instance_attr = "Instance" >>> my_object = MyClass() # атрибут экземпляра класса >>> my_object.__dict__ # # атрибут экземпляра класса >>> MyClass.__dict__['class_attr'] # 'Class' >>> my_object.class_attr 'Class' >>> my_object.instance_attr 'Instance' 
  • ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
  • Пространство имен и область видимости в классах
  • Определение классов
  • Объект класса и конструктор класса
  • Создание экземпляра класса
  • Метод экземпляра класса
  • Что такое метод класса и зачем нужен
  • Что такое статический метод в классах Python и зачем нужен
  • Атрибуты класса и переменные экземпляра класса
  • Кэширование методов экземпляра декоратором lru_cache
  • Закрытые/приватные методы и переменные класса Python
  • Наследование классов
  • Множественное наследование классов
  • Абстрактные классы
  • Перегрузка методов в классе Python
  • Что такое миксины и как их использовать
  • Класс Python как структура данных, подобная языку C
  • Создание пользовательских типов данных
  • Специальные (магические) методы класса Python
  • Базовая настройка классов Python магическими методами
  • Настройка доступа к атрибутам класса Python
  • Дескриптор класса для чайников
  • Протокол дескриптора класса
  • Практический пример дескриптора
  • Использование метода .__new__() в классах Python
  • Специальный атрибут __slots__ класса Python
  • Специальный метод __init_subclass__ класса Python
  • Определение метаклассов metaclass
  • Эмуляция контейнерных типов в классах Python
  • Другие специальные методы класса
  • Как Python ищет специальные методы в классах
  • Шаблон проектирования Фабрика и его реализация

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

Меня зовут Сергей Балакирев и на этом занятии мы с вами узнаем, как в Python определять классы, создавать объекты (экземпляры) этих классов, а также добавлять и удалять их атрибуты (то есть, данные).

Предположим, мы хотим определить класс для хранения координат точек на плоскости. Для начала я его запишу без какого-либо содержимого, только имя класса Point и все:

class Point: pass

Здесь оператор pass указывает, что мы в классе ничего не определяем. Также обратите внимание, что в соответствии со стандартом PEP8 имя класса принято записывать с заглавной буквы. И, конечно же, называть так, чтобы оно отражало суть этого класса. В дальнейшем я буду придерживаться этого правила.

Итак, у нас получилось простейшее определение класса с именем Point. Но в таком виде он не особо полезен. Поэтому я пропишу в нем два атрибута: color – цвет точек; circle – радиус точек:

class Point: color = 'red' circle = 2

Обратите внимание, переменные внутри класса обычно называются атрибутами класса или его свойствами. Я буду в дальнейшем использовать эту терминологию. Теперь в нашем классе есть два атрибута color и circle. Но, как правильно воспринимать эту конструкцию? Фактически, сам класс образует пространство имен, в данном случае с именем Point, в котором находятся две переменные color и circle. И мы можем обращаться к ним, используя синтаксис для пространства имен, например:

Point.color = 'black'

или для считывания значения:

Point.circle

(В консольном режиме увидим значение 2). А чтобы увидеть все атрибуты класса можно обратиться к специальной коллекции __dict__:

Point.__dict__

Здесь отображается множество служебных встроенных атрибутов и среди них есть два наших: color и circle.

Теперь сделаем следующий шаг и создадим экземпляры этого класса. В нашем случае для создания объекта класса Point достаточно после его имени прописать круглые скобки:

a = Point()

Смотрите, справа на панели в Python Console у нас появилась переменная a, через которую доступны два атрибута класса: color и circle.

Давайте создадим еще один объект этого класса:

b = Point()

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

С помощью функции type мы можем посмотреть тип данных для переменных a или b:

type(a)

Видим, что это класс Point. Эту принадлежность можно проверить, например, так:

type(a) == Point
isinstance(a, Point)

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

Во-первых, объекты a и b образуют свое пространство имен – пространство имен экземпляров класса и, во-вторых, не содержат никаких собственных атрибутов. Свойства color и circle принадлежат непосредственно классу Point и находятся в нем, а объекты a и b лишь имеют ссылки на эти атрибуты класса. Поэтому я не случайно называю их именно атрибутами класса, подчеркивая этот факт. То есть, атрибуты класса – общие для всех его экземпляров. И мы можем легко в этом убедиться. Давайте изменим значение свойства circle на 1:

Point.circle = 1

И в обоих объектах это свойство стало равно 1. Мало того, если посмотреть коллекцию __dict__ у объектов:

a.__dict__

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

a.color b.circle

Но, если мы выполним присваивание, например:

a.color = 'green'

То, смотрите, в объекте a свойство color стало ‘green’, а в b – прежнее. Почему? Дело в том, что мы здесь через переменную a обращаемся к пространству имен уже экземпляра класса и оператор присваивания в Python создает новую переменную, если она отсутствует в текущей локальной области видимости, то есть, создается атрибут color уже непосредственно в объекте a:

Мы можем в этом убедиться, если отобразим коллекцию __dict__ этого объекта:

a.__dict__

То есть, мы с вами создали локальное свойство в объекте a. Этот момент нужно очень хорошо знать и понимать. На этом принципе в Python построено формирование атрибутов классов и локальных атрибутов их экземпляров.

Добавление и удаление атрибутов класса

Кстати, по аналогии, мы можем создавать новые атрибуты и в классе, например, так:

Point.type_pt = 'disc'

Или то же самое можно сделать с помощью специальной функции:

setattr(Point, 'prop', 1)

Она создает новый атрибут в указанном пространстве имен (в данном случае в классе Point) с заданным значением. Если эту функцию применить к уже существующему атрибуту:

setattr(Point, 'type_pt', 'square')

то оно будет изменено на новое значение.

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

Point.circle

Но, при обращении к несуществующему атрибуту класса, например:

Point.a

возникнет ошибка. Этого можно избежать, если воспользоваться специальной встроенной функцией:

getattr(Point, 'a', False)

Здесь третий аргумент – возвращаемое значение, если атрибут не будет найден. Эту же функцию можно вызвать и с двумя аргументами:

getattr(Point, 'a')

Но тогда также будет сгенерирована ошибка при отсутствии указанного атрибута. Иначе:

getattr(Point, 'color')

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

Point.color

Наконец, мы можем удалять любые атрибуты из класса. Сделать это можно, по крайней мере, двумя способами. Первый – это воспользоваться оператором del:

del Point.prop

Если повторить эту команду и попытаться удалить несуществующий атрибут, возникнет ошибка. Поэтому перед удалением рекомендуется проверять существование удаляемого свойства. Делается это с помощью функции hasattr:

hasattr(Point, 'prop')

Она возвращает True, если атрибут найден и False – в противном случае.

Также удалить атрибут можно с помощью функции:

delattr(Point, 'type_pt')

Она работает аналогично оператору del.

И, обратите внимание, удаление атрибутов выполняется только в текущем пространстве имен. Например, если попытаться удалить свойство color из объекта b:

del b.color

то получим ошибку, т.к. в объекте b не своих локальных свойств и удалять здесь в общем то нечего. А вот в объекте a есть свое свойство color, которое мы с вами добавляли:

a.__dict__

и его можно удалить:

del a.color

Смотрите, после удаления локального свойства color в объекте a становится доступным атрибут color класса Point с другим значение ‘black’. И это логично, т.к. если свойство не обнаруживается в локальной области, то поиск продолжается в следующей (внешней) области видимости. А это (для объекта a) класс Point. Вот этот момент также следует хорошо понимать при работе с локальными свойствами объектов и атрибутами класса.

Атрибуты экземпляров классов

Теперь, когда мы знаем, как создаются атрибуты, вернемся к нашей задаче формирования объектов точек на плоскости. Мы полагаем, что атрибуты color и circle класса Point – это общие данные для всех объектов этого класса. А вот координаты точек должны принадлежать его экземплярам. Поэтому для объектов a и b мы определим локальные свойства x и y:

a.x = 1 a.y = 2 b.x = 10 b.y = 20

То есть, свойства x, y будут существовать непосредственно в объектах, но не в самом классе Point:

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

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

class Point: "Класс для представления координат точек на плоскости" color = 'red' circle = 2

В результате, специальная переменная:

Point.__doc__

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

Заключение

Итак, из этого занятия вы должны себе хорошо представлять, как определяются классы в Python и создаются объекты класса. Что из себя представляют атрибуты класса и атрибуты объектов, как они связаны между собой. Уметь обращаться к этим атрибутам, добавлять, удалять их, а также проверять существование конкретного свойства в классе или объекте класса.

Видео по теме

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

#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 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта

Как получить атрибуты класса

Кроме атрибутов объектов в классе можно определять атрибуты классов. Подобные атрибуты определяются в виде переменных уровня класса. Например:

class Person: type = "Person" description = "Describes a person" print(Person.type) # Person print(Person.description) # Describes a person Person.type = "Class Person" print(Person.type) # Class Person

Здесь в классе Person определено два атрибута: type, который хранит имя класса, и description, который хранит описание класса.

Для обращения к атрибутам класса мы можем использовать имя класса, например: Person.type , и, как и атрибуты объекта, мы можем получать и изменять их значения.

Подобные атрибуты являются общими для всех объектов класса:

class Person: type = "Person" def __init__(self, name): self.name = name tom = Person("Tom") bob = Person("Bob") print(tom.type) # Person print(bob.type) # Person # изменим атрибут класса Person.type = "Class Person" print(tom.type) # Class Person print(bob.type) # Class Person

Атрибуты класса могут применяться для таких ситуаций, когда нам надо определить некоторые общие данные для всех объектов. Например:

class Person: default_name = "Undefined" def __init__(self, name): if name: self.name = name else: self.name = Person.default_name tom = Person("Tom") bob = Person("") print(tom.name) # Tom print(bob.name) # Undefined

В данном случае атрибут default_name хранит имя по умолчанию. И если в конструктор передана пустая строка для имени, то атрибуту name передается значение атрибута класса default_name. Для обращения к атрибуту класса внутри методов можно применять имя класса

self.name = Person.default_name
Атрибут класса

Возможна ситуация, когда атрибут класса и атрибут объекта совпадает по имени. Если в коде для атрибута объекта не задано значение, то для него может применяться значение атрибута класса:

class Person: name = "Undefined" def print_name(self): print(self.name) tom = Person() bob = Person() tom.print_name() # Undefined bob.print_name() # Undefined bob.name = "Bob" bob.print_name() # Bob tom.print_name() # Undefined

Здесь метод print_name использует атрибут объект name, однако нигде в коде этот атрибут не устанавливается. Зато на уровне класса задан атрибут name. Поэтому при первом обращении к методу print_name, в нем будет использоваться значение атрибута класса:

tom = Person() bob = Person() tom.print_name() # Undefined bob.print_name() # Undefined

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

bob.name = "Bob" bob.print_name() # Bob tom.print_name() # Undefined

Причем второй объект — tom продолжит использовать атрибут класса. И если мы изменим атрибут класса, соответственно значение tom.name тоже изменится:

tom = Person() bob = Person() tom.print_name() # Undefined bob.print_name() # Undefined Person.name = "Some Person" # меняем значение атрибута класса bob.name = "Bob" # устанавливаем атрибут объекта bob.print_name() # Bob tom.print_name() # Some Person

Статические методы

Кроме обычных методов класс может определять статические методы. Такие методы предваряются аннотацией @staticmethod и относятся в целом к классу. Статические методы обычно определяют поведение, которое не зависит от конкретного объекта:

class Person: __type = "Person" @staticmethod def print_type(): print(Person.__type) Person.print_type() # Person - обращение к статическому методу через имя класса tom = Person() tom.print_type() # Person - обращение к статическому методу через имя объекта

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

Также в классе Person определен статический метод print_type , который выводит на консоль значение атрибута __type. Действие этого метода не зависит от конкретного объекта и относится в целом ко всему классу — вне зависимости от объекта на консоль будет выводится одно и то же значение атрибута __type. Поэтому такой метод можно сделать статическим.

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

Доступ к базовым атрибутам с помощью точечной нотации

Давайте возьмем образец класса.

class Book: def __init__(self, title, author): self.title = title self.author = author book1 = Book(title="Right Ho, Jeeves", author="P.G. Wodehouse")

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

>>> book1.title 'P.G. Wodehouse'

Если атрибут не существует, Python выдает ошибку:

>>> book1.series Traceback (most recent call last): File "", line 1, in AttributeError: 'Book' object has no attribute 'series' 

Сеттеры, геттеры и свойства

Для инкапсуляции данных иногда требуется иметь атрибут, значение которого исходит из других атрибутов или, вообще, какое значение должно быть вычислено в данный момент. Стандартный способ справиться с этой ситуацией — создать метод, называемый getter или setter.

class Book: def __init__(self, title, author): self.title = title self.author = author 

В приведенном выше примере легко увидеть, что произойдет, если мы создадим новую книгу, которая содержит заголовок и автора. Если все книги, которые мы добавляем в нашу Библиотеку, имеют авторов и заголовки, то мы можем пропустить методы получения и установки и использовать точечную запись. Однако предположим, что у нас есть книги, у которых нет автора, и мы хотим установить для автора значение «Неизвестно». Или, если у них несколько авторов, и мы планируем вернуть список авторов.

В этом случае мы можем создать геттер и сеттер для атрибута author.

class P: def __init__(self,title,author): self.title = title self.setAuthor(author) def get_author(self): return self.author def set_author(self, author): if not author: self.author = "Unknown" else: self.author = author 

Эта схема не рекомендуется.

Одна из причин в том, что здесь есть одна загвоздка: давайте предположим, что мы разработали наш класс с открытым атрибутом и без методов. Люди уже много его использовали, и они написали такой код:

 >>> book = Book(title="Ancient Manuscript", author="Some Guy") >>> book.author = "" #Cos Some Guy didn't write this one! 

Теперь у нас есть проблема. Поскольку автор не является атрибутом! Python предлагает решение этой проблемы, называемое свойствами. Метод для получения свойств украшен @property перед его заголовком. Метод, который мы хотим функционировать как установщик, перед ним имеет атрибут @ attributeName.setter.

Учитывая это, у нас теперь есть новый обновленный класс.

class Book: def __init__(self, title, author): self.title = title self.author = author @property def author(self): return self.__author @author.setter def author(self, author): if not author: self.author = "Unknown" else: self.author = author 

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

Если мы проверим код:

>>> book = Book(title="Ancient Manuscript", author="Some Guy") >>> book.author = "" #Cos Some Guy didn't write this one! >>> book.author Unknown 

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

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