Атрибуты классов и экземпляров в языке программирования Python
Основные концепции атрибутов класса и экземпляра
В Python каждый класс может иметь атрибуты, которые делятся на атрибуты класса (class attributes) и атрибуты экземпляра (instance attributes). Атрибут класса является общим для всех экземпляров, тогда как атрибут экземпляра принадлежит только конкретному объекту. Наиболее эффективный способ работы с ними - использовать атрибуты класса для хранения общих данных (константы, счетчики, настройки по умолчанию) и атрибуты экземпляра для индивидуальных свойств объекта.
class Dog:
# атрибут класса
species = "Canis familiaris"
def __init__(self, name, age):
# атрибуты экземпляра
self.name = name
self.age = age
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)
# Доступ к атрибуту класса через экземпляр и через класс
print(dog1.species) # Canis familiaris
print(Dog.species) # Canis familiaris
# Изменение атрибута класса через класс
Dog.species = "Canis lupus"
print(dog2.species) # Canis lupus
атрибуты класса python (атрибуты классов и объектов в python)
Canis familiaris Canis familiaris Canis lupus
библиотека классов python (библиотека классов в python)
Важно: изменение атрибута класса через экземпляр (например, dog1.species = "Cat") создаёт новый атрибут экземпляра, который затеняет атрибут класса, но не меняет его у других экземпляров.
Как организовать подсчёт созданных экземпляров с помощью атрибута класса?
Хранение счётчика в атрибуте класса позволяет отслеживать количество объектов, созданных от данного класса. Каждый раз в __init__ значение увеличивается.
class Counter:
count = 0
def __init__(self):
Counter.count += 1
a = Counter()
b = Counter()
print(Counter.count) # 2
метод объекта python (методы объектов в python)
2
Python структура объекта (структура объекта в python)
Ошибка: если использовать self.count += 1, то будет создан атрибут экземпляра, а счётчик класса останется без изменений. Изменение атрибута класса следует выполнять через имя класса (Counter.count += 1) или через type(self).count.
Как сделать атрибут доступным только для чтения с помощью property?
Инкапсуляция данных: атрибут, который вычисляется или защищается от прямого изменения. Property позволяет определить getter, setter, deleter.
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Радиус не может быть отрицательным")
self._radius = value
@property
def area(self):
return 3.14159 * self._radius ** 2
c = Circle(5)
print(c.radius) # 5
print(c.area) # 78.53975
c.radius = 10
print(c.area) # 314.159
# c.radius = -1 # ValueError
Python создание объектов (создание объектов в python)
5 78.53975 314.159
Self object python (объект self в python)
Проблема: если забыть декоратор @property, то обращение c.radius вызовет метод, а не атрибут. Getter необходимо помечать декоратором @property.
Как использовать __slots__ для ограничения атрибутов экземпляра и экономии памяти?
Определение __slots__ в классе фиксирует набор допустимых атрибутов экземпляра, что уменьшает объём памяти и ускоряет доступ.
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
# p.z = 3 # AttributeError: 'Point' object has no attribute 'z'
Ошибка: при добавлении атрибута, не указанного в __slots__, возникает AttributeError. Если необходимы динамические атрибуты, __slots__ применять не следует.
Типичные ошибки при работе с атрибутами
- Затенение атрибута класса атрибутом экземпляра - при присваивании
self.attr = valueсоздаётся новый атрибут экземпляра, даже если существовал атрибут класса. Решение: изменение атрибута класса должно выполняться черезClassName.attr. - Изменение изменяемого объекта (список, словарь) в атрибуте класса - операции вроде
self.shared_list.append(item)изменяют общий объект, что может быть неожиданным. Решением является копирование объекта для каждого экземпляра или использование неизменяемых типов. - AttributeError при доступе к несуществующему атрибуту - происходит, если атрибут не определён ни в классе, ни в экземпляре. Рекомендуется применять
hasattr()илиgetattr()с значением по умолчанию.
Расширенные примеры и детали работы атрибутов
# Пример наследования и переопределения атрибута класса
class Base:
attr = "base"
class Derived(Base):
attr = "derived"
print(Base.attr, Derived.attr) # base derived
# Изменение атрибута в родительском классе
Base.attr = "new_base"
print(Derived.attr) # derived (не затронут)
# Изменение атрибута в производном классе
Derived.attr = "new_derived"
print(Base.attr) # new_base (не затронут)
base derived derived new_base
Пояснение: атрибуты класса независимы для каждого класса в иерархии. При поиске через экземпляр используется MRO (Method Resolution Order), но для атрибутов действует тот же принцип: сначала экземпляр, потом класс, потом родители.
# Исследование __dict__ класса и экземпляра
class A:
class_var = 10
def __init__(self, val):
self.instance_var = val
a = A(5)
print(A.__dict__) # включает class_var
print(a.__dict__) # включает instance_var
# Добавление атрибута экземпляра вручную
a.new_attr = 100
print(a.__dict__) # теперь три атрибута
{'__module__': '__main__', 'class_var': 10, '__init__': <function A.__init__ at 0x...>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
{'instance_var': 5, 'new_attr': 100}
# Пример с дескриптором (property как дескриптор)
class Descriptor:
def __get__(self, instance, owner):
return instance._value * 2
def __set__(self, instance, value):
instance._value = value / 2
class MyClass:
attr = Descriptor()
def __init__(self, val):
self._value = val
obj = MyClass(10)
print(obj.attr) # 20
obj.attr = 40
print(obj.attr) # 20? Нет, после set: _value = 20, get: 20*2=40. Print 40.
20 40
# Использование getattr и setattr
class Dynamic:
pass
d = Dynamic()
# Эквивалентно d.x = 10
setattr(d, 'x', 10)
print(getattr(d, 'x', 'default')) # 10
print(getattr(d, 'y', 'default')) # default
10 default