Что такое атрибут в python
Кроме атрибутов объектов в классе можно определять атрибуты классов. Подобные атрибуты определяются в виде переменных уровня класса. Например:
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. Поэтому такой метод можно сделать статическим.
Объекты и атрибуты
Часто можно услышать, что в Python всё является объектом, и это правда. Но что такое объект? И из чего складывается это “всё”?
Когда переменных в коде становится много, их объединяют в группы — объекты. Добраться до переменной, прикрепленной к объекту можно через точку . . Выглядит это похоже на вызов метода. Для сравнения, вызовем метод __len__ и вычислим длину строки:
print('строка это тоже объект'.__len__()) # выведет 22
А теперь узнаем значение прикрепленной переменной __class__ . Она хранит тип данных и имеется у любого объекта в Python, в том числе у строки:
print('строка это тоже объект'.__class__) # выведет
В этот раз мы заменили __len__ на __class__ и не поставили круглые скобки () , на этом отличия заканчиваются. Прикрепленные переменные и прикрепленные функции — методы — имеют так много общего, что вместе их называют атрибутами.
Объект — это набор атрибутов: прикрепленных переменных и функций. До любого атрибута можно добраться через точку . :
print('строка это тоже объект'.__len__) # выведет print('строка это тоже объект'.__class__) # выведет print('строка это тоже объект'.__doc__) # выведет документацию по типу данных Строка
Объекты в Python встречаются повсюду и у всех них есть атрибуты. Строки, числа, функции и даже модули — все это объекты, а значит у них есть атрибут __class__ :
print(''.__class__) # выведет number = 1 print(number.__class__) # выведет print(print.__class__) # выведет import os # подключаем стандартный модуль os print(os.__class__) # выведет
Чтобы не писать каждый раз длинное название __class__ используют функцию type() . Она умеет работать с любыми данными, благодаря тому что сама обращается к атрибуту __class__ :
print(type('')) # выведет print(type(1)) # выведет print(type(print)) # выведет import os # подключаем стандартный модуль os print(type(os)) # выведет
Попробуйте бесплатные уроки по Python
Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.
Переходите на страницу учебных модулей «Девмана» и выбирайте тему.
Атрибуты класса и переменные экземпляра класса в 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 ищет специальные методы в классах
- Шаблон проектирования Фабрика и его реализация
Методы класса и статические методы
Когда интерпретатор достигает инструкции class (а не тогда, когда происходит вызов класса), он выполняет все инструкции в ее теле от начала и до конца. Все присваивания, которые производятся в ходе этого процесса, создают имена в локальной области видимости класса, которые становятся атрибутами объекта класса.
Благодаря этому классы напоминают модули и функции:
- Подобно функциям, инструкции class являются локальными областями видимости, где располагаются имена, созданные вложенными операциями присваивания.
- Подобно именам в модуле, имена, созданные внутри инструкции class, становятся атрибутами объекта класса.
Для работы с классом питон создает отдельный объект, описывающий весь класс целиком как набор правил, а не отдельный экземпляр класса. (class object).
У этого объекта тоже могут быть свои поля и методы.
Они нужны для атрибутов и методов, которые относятся не к конкретному экземпляру, а ко всему классу целиком.
Например, для класса, описывающих дату, это может быть список названий месяцев.
Переменные класса
class Date(): month = ['январь', 'февраль', ..] def __init__(self, day, month, year): self.day = day self.month = month self.year = year self.month_name = Date.month[month-1]
Попробуем посчитать, сколько экземпляров класса Circle было создано за время работы программы.
class Circle(): counter = 0 # сколько экземпляров класса было создано def __init__(self, x=0, y=0, r=1): Circle.counter += 1 c = Circle() d = Circle() print(
При чтении переменных идет поиск этой переменной в пространстве имен.
При = изменяется сам объект. (Быть может создается атрибут этого объекта).
class A(object): shared_data = 42 x = A() y = A() print(x.shared_data, y.shared_data, A.shared_data) # 42, 42, 42 A.shared_data = 99 # А - объект - класс print(x.shared_data, y.shared_data, A.shared_data) # 99, 99, 99 # x - объект - экземпляр класса x.shared_data = 100 # создали новый атрибут ЭКЗЕМПЛЯРА класса print(x.shared_data, y.shared_data, A.shared_data) # 100, 99, 99
В классе и экземпляре класса может быть поле с одинаковым именем (не пишите так!):
class B(object): data = 'shared' # присваивание атрибуту класса def __init__(self, data): self.data = data # присваивание атрибуту экземпляра def prn(self): print(self.data, B.data) # атрибут экземпляра, атрибут класса x = B(1) y = B(2) x.prn() # 1 shared y.prn() # 2 shared
Вызов методов
Для любого объекта класса класс допустимы варианты вызова метода экземпляра класса:
экземпляр.метод(аргументы. ) класс.метод(экземпляр, аргументы. )
class A(object): def func(self, value): print(value) x = A() x.func('первый вызов') # первый вызов A.func(x, 'второй вызов') # второй вызов
методы класса и статические методы
@classmethod можно переопределить в наследнике класса
У метода класса есть cls, но нет self
@staticmethod нельзя переопределить при наследовании классов
class Date(object): def __init__(self, day=0, month=0, year=0): self.day = day self.month = month self.year = year @classmethod def from_string(cls, date_as_string): day, month, year = map(int, date_as_string.split('-')) date1 = cls(day, month, year) return date1 @staticmethod def is_date_valid(date_as_string): day, month, year = map(int, date_as_string.split('-')) return day 31 and month 12 and year 3999 date2 = Date.from_string('11-09-2012') is_date = Date.is_date_valid('11-09-2012')
Пример static factory
Допустим, у нас должно быть не более 1 экземпляра данного класса.
Когда какой метод делаем?
- экземпляра класса (self) — функция обращается к атрибутам экземпляра;
- класса — функция не обращается к атрибутам экземпляра класса, но обращается к атрибутам класса;
- static — функция не обращается ни к каким атрибутам класса или объекта.
