Как работает атрибут имени класса в Python

Раздел: Основы Python -> Классы и ООП

Имя класса в Python: базовые правила и доступ

Имя класса в Python, это не только имя переменной, которой присвоен объект класса. Класс создаётся при выполнении объявления, и его исходное название сохраняется в специальном атрибуте __name__. Из любого экземпляра можно добраться до класса через __class__, затем получить __name__. Такой способ не зависит от того, в какой переменной хранится класс.

class Instrument:
    pass

current = Instrument()
print(current.__class__.__name__)

Python class name (имя класса в python)

Instrument

Внутри метода класса или экземпляра допустимо обратиться к __class__ как к атрибуту без аргументов.

class Task:
    def type_name(self):
        return self.__class__.__name__

print(Task().type_name())
Task

Атрибут __qualname__ содержит более полный путь для вложенных классов и помогает восстановить реальное положение класса в коде.

class Container:
    class Nested:
        pass

print(Container.Nested.__name__)
print(Container.Nested.__qualname__)
Nested
Container.Nested

Как определить имя класса, когда под рукой только экземпляр?

Самый частый вариант в программе: имеется объект, созданный от какого-то класса, и нужно понять, от какого. Для этого применяется функция type.

class Engine:
    pass

my_machine = Engine()
print(type(my_machine).__name__)
Engine

Этот приём работает и для встроенных типов: строка, список, словарь.

values = [1, 2, 3]
print(type(values).__name__)
list

Как получить имя класса внутри метода без обращения к глобальной переменной?

В методах экземпляра есть self, через него доступен __class__.

class Report:
    def header(self):
        return self.__class__.__name__ + ' report'

document = Report()
print(document.header())
Report report

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

class Counter:
    def label(self):
        return __class__.__name__

    def another_label(self):
        return type(self).__name__

c = Counter()
print(c.label())
print(c.another_label())
Counter
Counter

Как использовать имя класса в строковых представлениях объекта?

Часто требуется, чтобы repr или str содержали название типа. Тогда не следует хардкодить имя, потому что при наследовании подкласс получит неправильную строку. Лучше взять self.__class__.__name__ или type(self).__name__.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'{self.__class__.__name__}({self.x}, {self.y})'

class ColorPoint(Point):
    pass

cp = ColorPoint(1, 2)
print(repr(cp))
ColorPoint(1, 2)

Зачем нужен __qualname__, если есть __name__?

Для классов, объявленных внутри других классов или функций, __name__ показывает только короткое имя. __qualname__ сохраняет путь, что важно для отладки и некоторых сериализаторов.

def make_class():
    class Temporary:
        pass
    return Temporary

sample = make_class()
print(sample.__name__)
print(sample.__qualname__)
Temporary
make_class..Temporary

Можно ли изменить имя класса после его создания?

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

class Account:
    pass

Account.__name__ = 'Profile'
print(Account.__name__)
Profile

Обычно менять __name__ не требуется. Лучше создавать новый класс или выбирать имя сразу при объявлении. Изменение __name__ полезно только в декораторах и инструментах генерации кода, когда класс должен отображаться иначе без замены глобального имени.

Как задать имя при динамическом создании класса?

Когда класс создаётся вызовом type, первым аргументом передаётся строка с именем. Именно она попадает в __name__.

SortableItem = type('SortableItem', (), {})
print(SortableItem.__name__)
print(SortableItem)
SortableItem
<class '__main__.SortableItem'>

Если имя передаётся в переменной, класс будет хранить это имя независимо от имени, под которым переменная существует в пространстве имён.

alias_for_class = type('StoredAlias', (), {})
print(type(alias_for_class).__name__)
type

Возможные ошибки при работе с именем класса

Когда классу присваивается новая переменная, объект класса не меняет __name__. Поэтому нельзя полагаться на имя переменной как на тип. Название класса определяется при его создании, а переменная это просто ссылка.

class Article:
    pass

JournalArticle = Article
print(JournalArticle.__name__)
print(Article.__name__)
Article
Article

Такое поведение объясняет, почему функция type(instance).__name__ надёжнее, чем проверка глобальных имён через globals().

Ещё одна типичная ошибка возникает в декораторе, который возвращает класс, изменённый с помощью wrapper вокруг исходного. Если декоратор создаёт новый класс, нужно правильно установить __name__, __qualname__ и __doc__. Иначе инструменты документации и профилирования покажут чужое имя.

def decorate(cls):
    wrapper = type('Wrapped' + cls.__name__, (cls,), {})
    return wrapper

@decorate
class Original:
    pass

print(Original.__name__)
WrappedOriginal

Здесь результат ожидаемый, но если декоратор хотел сохранить имя Original, требуется явно назначить wrapper.__name__ = cls.__name__.

Примеры ниже продолжают тему и помогают увидеть поведение имени класса в реальных конструкциях. Для каждого фрагмента блок с классом ex_c содержит код, блок с классом ex_r содержит результат.

Пример использования type(obj).__name__ в списке разных объектов

Пример
class Cat:
    def sound(self):
        return 'мяу'

class Dog:
    def sound(self):
        return 'гав'

pets = [Cat(), Dog(), Cat()]
for pet in pets:
    print(type(pet).__name__, pet.sound())
Cat мяу
Dog гав
Cat мяу

Пример включения имени класса в методы родительского класса

Базовый класс возвращает фактическое имя класса экземпляра, поэтому подкласс не требует переопределения.

Пример
class Base:
    def describe(self):
        return f'instance of {self.__class__.__name__}'

class Child(Base):
    pass

for obj in (Base(), Child()):
    print(obj.describe())
instance of Base
instance of Child

Пример динамического создания класса на основе аргумента

Пример
def build_class(class_name, base, attr_name, attr_value):
    namespace = {attr_name: attr_value}
    return type(class_name, (base,), namespace)

Item = build_class('ImportantItem', object, 'priority', 10)
instance = Item()
print(Item.__name__)
print(instance.priority)
ImportantItem
10

Пример изменения __qualname__ для временного класса

Пример
def builder(prefix):
    class Temporary:
        pass
    Temporary.__qualname__ = prefix + '.' + Temporary.__qualname__
    return Temporary

Built = builder('app.models')
print(Built.__name__)
print(Built.__qualname__)
Temporary
app.models.builder..Temporary

Пример обновления имени в декораторе класса

Пример
def wrap_class(cls):
    cls.__name__ = cls.__name__ + 'Decorated'
    return cls

@wrap_class
class Handler:
    pass

print(Handler.__name__)
HandlerDecorated

Пример использования __class__.__name__ в сообщениях об ошибке

Пример
class HttpError(Exception):
    def __str__(self):
        message = self.args[0] if self.args else ''
        return f'{type(self).__name__}: {message}'

class NotFoundError(HttpError):
    pass

try:
    raise NotFoundError('resource missing')
except HttpError as exc:
    print(str(exc))
NotFoundError: resource missing

Пример обхода коллекции объектов разных классов через isinstance

Пример
class Shape:
    def area(self):
        return 0

class Circle(Shape):
    pass

class Square(Shape):
    pass

shapes = [Circle(), Shape(), Square()]
for shape in shapes:
    if isinstance(shape, Shape):
        print(type(shape).__name__)
Circle
Shape
Square

Пример сравнения __name__ у разных ссылок на один класс

Пример
class Data:
    pass

first_alias = Data
second_alias = Data
print(first_alias.__name__)
print(second_alias.__name__)
print(first_alias is Data)
Data
Data
True

Пример работы с __name__ в метаклассе

Метакласс может записать имя класса как отдельный атрибут при создании класса. Это полезно для справочников и реестров.

Пример
class RegisteredType(type):
    def __new__(mcs, name, bases, namespace):
        cls = super().__new__(mcs, name, bases, namespace)
        cls.registration_name = name
        return cls

class Product(metaclass=RegisteredType):
    pass

print(Product.registration_name)
Product

Имя класса в Python - comments

En
Python class name (python)