Как работает атрибут имени класса в 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