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

Раздел: 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() с значением по умолчанию.
- Python класс данных (класс данных в python)
- Class method python (методы классов в python)
- Python object methods (методы объектов в python)

Расширенные примеры и детали работы атрибутов

Пример

# Пример наследования и переопределения атрибута класса
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

Атрибуты классов и объектов в Python - comments

En
атрибуты класса python (python)