Как изменить атрибут класса 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
Переменные экземпляра класса предназначены для данных, уникальных для каждого экземпляра класса, а переменные класса (атрибуты данных класса) — для атрибутов и методов, общих для всех экземпляров класса.
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 классы создаются с помощью инструкции class , за которой следует произвольное имя класса, после которого ставится двоеточие, далее с новой строки и с отступом реализуется тело класса:
class ИмяКласса: код_тела_класса
Если класс является дочерним, то родительские классы перечисляются в круглых скобках после имени класса.
Объект создается путем вызова класса по его имени. При этом после имени класса обязательно ставятся скобки:
ИмяКласса()
То есть класс вызывается подобно функции. Однако в случае вызова класса происходит не выполнение его тела, как это происходило бы при вызове функции, а создается объект. Поскольку в программном коде важно не потерять ссылку на только что созданный объект, то обычно его связывают с переменной. Поэтому создание объекта чаще всего выглядит так:
имя_переменной = ИмяКласса()
В последствии к объекту обращаются через связанную с ним переменную.
Пример «пустого» класса и двух созданных на его основе объектов:
>>> class A: . pass . >>> a = A() >>> b = A()
Класс как пространство имен
С точки зрения пространства имен класс можно представить подобным модулю. Также как в модуле в классе могут быть свои переменные со значениями и функции. Также как в модуле у класса есть собственное пространство имен, доступ к которому возможен через имя класса:
>>> class B: . n = 5 . def adder(v): . return v + B.n . >>> B.n 5 >>> B.adder(4) 9
Однако в случае классов используется особая терминология. Пусть имена, определенные в классе, называются атрибутами этого класса. В примере имена n и adder – это атрибуты класса B . Атрибуты-переменные часто называют полями или свойствами (в других языках понятия «поле» и «свойство» не совсем одно и то же). Полем является n . Атрибуты-функции называются методами. Методом в классе B является adder . Количество свойств и методов в классе может быть любым.
Класс как шаблон для создания объектов
На самом деле классы – не модули. Они своего рода шаблоны, от которых создаются объекты-экземпляры. Такие объекты наследуют от класса его атрибуты. Вернемся к нашему классу B и создадим на его основе два объекта:
>>> class B: . n = 5 . def adder(v): . return v + B.n . >>> a = B() >>> b = B()
У объектов, связанных с переменными a и b , нет собственного поля n . Однако они наследуют его от своего класса:
>>> a.n 5 >>> a.n is B.n True
То есть поля a.n и B.n – это одно и то же поле, к которому можно обращаться и через имя a , и через имя b , и через имя класса. Поле одно, ссылок на него три.
Однако что произойдет в момент присваивания этому полю значения через какой-нибудь объект-экземпляр?
>>> a.n = 10 >>> a.n 10 >>> b.n 5 >>> B.n 5
В этот момент у экземпляра появляется собственный атрибут n , который перекроет (переопределит) родительский, то есть тот, который достался от класса.
>>> a.n is B.n False >>> b.n is B.n True
При этом присвоение через B.n отразится только на b и B , но не на a :
>>> B.n = 100 >>> B.n, b.n, a.n (100, 100, 10)
Иная ситуация нас ожидает с атрибутом adder . При создании объекта от класса функция adder не наследуется как есть, а как бы превращается для объекта в одноименный метод:
>>> B.adder is b.adder False >>> type(B.adder) >>> type(b.adder)
Через имя класса мы вызываем функцию adder :
>>> B.adder(33) 133
Через имя объекта вызываем метод adder :
>>> b.adder(33) Traceback (most recent call last): File "", line 1, in TypeError: adder() takes 1 positional argument but 2 were given
В сообщении об ошибке говорится, что adder принимает только один аргумент, а было передано два. Откуда появился второй, если в скобках было указано только одно число?
Дело в том, что в отличии от функции в метод первым аргументом всегда передается объект, к которому применяется этот метод. То есть выражение b.adder(33) как бы преобразовывается в adder(b, 33) . Сам же b.adder как объект типа method хранит сведения, с каким классом он связан и какому объекту-экземпляру принадлежит:
>>> b.adder >
В нашем случае, чтобы вызывать adder через объекты-экземпляры, класс можно переписать так:
>>> class B: . n = 5 . def adder(obj, v): . return v + obj.n . >>> b = B() >>> b.adder(33) 38
В коде выше при вызове метода adder переменной-параметру obj присваивается объект, связанный с переменной, к которой применяется данный метод. В данном случае это объект, связанный с b . Если adder будет вызван на другой объект, то уже он будет присвоен obj :
>>> a = B() >>> a.n = 9 >>> a.adder(3) 12
В Python переменную-параметр метода, которая связывается с экземпляром своего класса, принято называть именем self. Таким образом, более корректный код будет таким:
>>> class B: . n = 5 . def adder(self, v): . return v + self.n
Можем ли мы все также вызывать adder как функцию, через имя класса? Вполне. Только теперь в функцию надо передавать два аргумента:
>>> B.adder(B, 200) 205 >>> B.adder(a, 200) 209
Здесь первым аргументом в функцию передается объект, у которого есть поле n лишь только потому, что далее к этому полю обращаются через выражение self.n .
Однако если атрибут определен так, что предполагается его работа в качестве метода, а не функции, то через класс его уже не вызывают (нет смысла, логика программы этого не подразумевает).
С другой стороны, в ООП есть понятие «статический метод». По сути это функция, которая может вызываться и через класс, и через объект, и которой первым аргументом не подставляется объект, на который она вызывается. В Python статический метод можно создать посредством использования специального декоратора.
Атрибут __dict__
В Python у объектов есть встроенные специальные атрибуты. Мы их не определяем, но они есть. Одним из таких атрибутов объекта является свойство __dict__ . Его значением является словарь, в котором ключи – это имена свойств экземпляра, а значения – текущие значения свойств.
>>> class B: . n = 5 . def adder(self, v): . return v + self.n . >>> w = B() >>> w.__dict__ <> >>> w.n = 8 >>> w.__dict__
В примере у экземпляра класса B сначала нет собственных атрибутов. Свойство n и метод adder – это атрибуты объекта-класса, а не объекта-экземпляра, созданного от этого класса. Лишь когда мы выполняем присваивание новому полю n экземпляра, у него появляется собственное свойство, что мы наблюдаем через словарь __dict__ .
В следующем уроке мы увидим, что свойства экземпляра обычно не назначаются за пределами класса. Это происходит в методах классах путем присваивание через self . Например, self.n = 10 .
Атрибут __dict__ используется не только для просмотра свойств объекта. С его помощью можно удалять, добавлять свойства, а также изменять их значения.
>>> w.__dict__['m'] = 100 >>> w.__dict__ >>> w.m 100
Практическая работа
Напишите программу по следующему описанию. Есть класс «Воин». От него создаются два экземпляра-юнита. Каждому устанавливается здоровье в 100 очков. В случайном порядке они бьют друг друга. Тот, кто бьет, здоровья не теряет. У того, кого бьют, оно уменьшается на 20 очков от одного удара. После каждого удара надо выводить сообщение, какой юнит атаковал, и сколько у противника осталось здоровья. Как только у кого-то заканчивается ресурс здоровья, программа завершается сообщением о том, кто одержал победу.
Курс с примерами решений практических работ:
pdf-версия
X Скрыть Наверх
Объектно-ориентированное программирование на Python
Как можно изменить атрибут экземпляра одного класса при добавлении/изменении экземпляра другого класса
Мне нужно, чтобы при создании города/деревни уже существующей области (т.е. уже создана область с таким названием) изменялись атрибуты экземпляра области (если он уже создан), т.е. площадь области увеличивалась/уменьшалась на площадь добавленного/удалённого города, а количество населённых пунктов увеличивалось/уменьшалось на один при добавлении/удалёнии города. Как нужно изменить мой код, чтобы это сделать?
Нужно, чтобы выводилось:
Тип места: область
Название: Саратовская область
Площадь: 100200 кв.км
Количество населенных пунктов: 1762
Руководство: Радаев
Тип места: город
Название: Саратов
Площадь: 394 кв.км
Область: Саратовская область
Количество жителей: 838 тыс. человек
Мэр: Исаев
Тип места: область
Название: Саратовская область
Площадь: 100594 кв.км
Количество населенных пунктов: 1763
Руководство: Радаев
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
class Place: place_count = 0 def __init__(self, name, area): self.name = name self.area = area Place.place_count += 1 def p_count(self): print('Всего мест: %d' % Place.place_count) def get_place(self): print('Название: <>\nПлощадь: <> кв.км'.format(self.name, self.area)) def set_name(self, n): self.name = n def set_area(self, a): self.area = a def del_place(self): del self.name del self.area Place.place_count -= 1 class Region(Place): region_count = 0 def __init__(self, name, area, settlements, management): Place.__init__(self, name, area) self.settlements = settlements self.management = management Region.region_count += 1 def r_count(self): print('Всего областей: %d' % Region.region_count) def get_place(self): print('Тип места: область') super(Region, self).get_place() print('Количество населенных пунктов: <>\nРуководство: <>'.format(self.settlements, self.management)) def set_settlements(self, s): self.settlements = s def set_management(self, m): self.management = m def del_place(self): super(Region, self).del_place() del self.settlements del self.management Region.region_count -= 1 class City(Place): city_count = 0 def __init__(self, name, area, region, inhabitants, mayor): Place.__init__(self, name, area) self.region = region self.inhabitants = inhabitants self.mayor = mayor City.city_count += 1 def c_count(self): print('Всего городов: %d' % City.city_count) def get_place(self): print('Тип места: город') super(City, self).get_place() print('Область: <>\nКоличество жителей: <> тыс. человек\nМэр: <>'.format(self.region, self.inhabitants, self.mayor)) def set_region(self, r): self.region = r def set_inhabitants(self, i): self.inhabitants = i def set_mayor(self, m): self.mayor = m def del_place(self): super(City, self).del_place() del self.region del self.inhabitants del self.mayor City.city_count -= 1 class Village(Place): village_count = 0 def __init__(self, name, area, region): Place.__init__(self, name, area) self.region = region Village.village_count += 1 def v_count(self): print('Всего деревень: %d' % Village.village_count) def get_place(self): print('Тип места: деревня') super(Village, self).get_place() print('Область: <>'.format(self.region)) def set_region(self, r): self.region = r def del_place(self): super(Village, self).del_place() del self.region Village.village_count -= 1 region1 = Region('Саратовская область', 100200, 1762, 'Радаев') region1.get_place() print() city1 = City('Саратов', 394, 'Саратовская область', 838, 'Исаев') city1.get_place() print() region1.get_place() print()