How to get a class name in Python
Learn how to get a class name in Python. This guide covers various methods, real-world applications, common errors, and debugging tips.
.avif)
To get a class name in Python is a common need for introspection and dynamic behavior. Python offers built-in tools like the __name__ attribute to access this information easily.
In this article, we'll explore various techniques to retrieve class names. We'll also cover practical tips, real-world applications, and debugging advice to help you master this essential skill.
Basic approach with __class__.__name__
class MyClass:
pass
obj = MyClass()
class_name = obj.__class__.__name__
print(class_name)--OUTPUT--MyClass
The expression obj.__class__.__name__ is the most direct way to get a class name from an object instance. Every object in Python has a built-in __class__ attribute that points to the class it was created from. So, obj.__class__ returns the MyClass class object itself, not just its name.
From there, you access the __name__ attribute of the class object. This attribute holds the class's name as a string—in this case, 'MyClass'. It's a fundamental technique for runtime type inspection and is widely used for logging, debugging, and dynamic dispatch.
Working with class names and types
Beyond using obj.__class__, Python's type() function offers another route, while attributes like __qualname__ provide even more detail for complex class structures.
Using type() function with __name__
class Person:
pass
person = Person()
class_name = type(person).__name__
print(f"The class name is: {class_name}")--OUTPUT--The class name is: Person
The type() built-in function offers another way to get an object's class. Calling type(person) returns the Person class object, which is functionally the same as using person.__class__. Many developers find type() more readable because it explicitly asks for the object's type.
- After getting the class object, you simply chain
.__name__to retrieve the name as a string. - It’s a common and Pythonic approach for runtime type checking.
Getting class name from the class itself
class Book:
pass
# Get name directly from class (not instance)
class_name = Book.__name__
print(class_name)
# Compare with getting name from instance
print(Book().__class__.__name__)--OUTPUT--Book
Book
You don't always need an object to find out a class's name. You can access the __name__ attribute directly on the class itself. For instance, Book.__name__ returns the string 'Book' without you having to create a Book object first.
- This approach is more direct than creating an instance just to inspect its type, as in
Book().__class__.__name__. - It’s especially handy in situations where you're working with class types before they are instantiated, such as in factory patterns or metaprogramming.
Using type().__qualname__ for nested classes
class Outer:
class Inner:
pass
inner_obj = Outer.Inner()
print(type(inner_obj).__name__)
print(type(inner_obj).__qualname__)--OUTPUT--Inner
Outer.Inner
When you're working with nested classes, the __name__ attribute can be a bit limited. It only returns the name of the immediate class, like 'Inner', which lacks context about its parent.
This is where __qualname__ shines. It provides the "qualified name," a dotted path showing the class's full location.
__name__gives you justInner.__qualname__gives you the full path:Outer.Inner.
Using __qualname__ helps you avoid ambiguity, making your code clearer in projects with complex class structures.
Advanced class name techniques
Beyond simple attribute access, Python’s inspect module and other techniques give you the power to handle inheritance hierarchies and find class names dynamically.
Using the inspect module
import inspect
class Vehicle:
pass
car = Vehicle()
# Get more information about the class
print(inspect.getmodule(car.__class__).__name__)
print(inspect.isclass(Vehicle))
print(inspect.getmro(Vehicle)[0].__name__)--OUTPUT--__main__
True
Vehicle
For more advanced introspection, Python’s inspect module offers a suite of powerful tools. It goes beyond simple name retrieval, letting you explore the structure and context of your classes. It’s particularly useful for building frameworks or tools that need to understand code dynamically.
inspect.getmodule()helps you find the module where a class is defined.inspect.isclass()provides a reliable way to check if an object is a class.inspect.getmro()reveals the Method Resolution Order, showing the class's inheritance hierarchy. Accessing the first element with[0]gives you the class itself.
Working with inheritance hierarchies
class Animal:
pass
class Dog(Animal):
pass
dog = Dog()
# Get class name and base classes
print(f"Class: {dog.__class__.__name__}")
print(f"Base class: {dog.__class__.__base__.__name__}")
print(f"MRO: {[cls.__name__ for cls in Dog.__mro__]}")--OUTPUT--Class: Dog
Base class: Animal
MRO: ['Dog', 'Animal', 'object']
When dealing with inheritance, you can find more than just an object's immediate class name. While dog.__class__.__name__ correctly identifies the instance as a Dog, you can also trace its lineage back to its parent classes.
- The
__base__attribute reveals the direct parent class. For thedogobject,dog.__class__.__base__.__name__returns 'Animal'. - For the complete inheritance chain, you can use the
__mro__(Method Resolution Order) attribute. It gives you a sequence of all parent classes, all the way up to Python's baseobject.
Getting class names dynamically
def create_class_with_name(name):
return type(name, (), {})
# Create classes dynamically
MyDynamicClass = create_class_with_name("MyDynamicClass")
instance = MyDynamicClass()
print(instance.__class__.__name__)
print(type(instance).__module__)--OUTPUT--MyDynamicClass
__main__
Python's type() function isn't just for checking an object's type; it can also create classes programmatically. When you call type() with three arguments (a name string, a tuple of base classes, and an attribute dictionary), you can generate a new class on the fly. This is a powerful technique used in metaprogramming.
- The name string you provide becomes the class's
__name__. - Even with dynamic creation, you can get the name just like with any other class using
instance.__class__.__name__. - The
__module__attribute shows where the class was defined, which is often__main__in scripts.
Move faster with Replit
Replit is an AI-powered development platform that helps you go from learning techniques, like getting a class name, to building complete applications. It comes with all Python dependencies pre-installed, so you can skip setup and start coding instantly.
Instead of piecing together code, you can describe what you want to build and have Agent 4 create a working product. You can build tools that use class introspection for practical tasks:
- A dynamic plugin loader that uses class introspection to automatically register and manage different components.
- A custom serialization utility that converts various Python objects to JSON, using their
__class__.__name__to tag the data type. - An advanced error reporting tool that inspects an exception's class hierarchy with
__mro__to provide detailed debugging context.
Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.
Common errors and challenges
While getting a class name is often straightforward, a few common errors can trip you up if you're not careful.
Confusion about __class__.__name__ in inherited methods
It's easy to get confused when working with inherited methods. If a child class inherits a method from a parent, calling self.__class__.__name__ inside that method will return the name of the child class, not the parent. That’s because self always refers to the instance on which the method was called.
Using __name__ when full class path is needed
A frequent mistake is using __name__ when the context requires the full path, which can create ambiguity with nested classes. Remember that __name__ only provides the simple class name. For clarity in complex projects, you should use __qualname__ to get the complete dotted path.
Missing an underscore in __class__.__name__
A simple typo, like writing __class_.__name__ or __class__.__name_, is a very common source of bugs. Python's "dunder" (double underscore) attributes are strict. Any mistake in the number of underscores will result in an AttributeError, so it's always one of the first things to double-check.
Confusion about __class__.__name__ in inherited methods
When a child class calls an inherited method, self.__class__.__name__ returns the child's name, not the parent's. This is because self always refers to the instance. To get the parent's name, you must call it directly. The code below demonstrates this.
class Parent:
def get_class_name(self):
# Expecting this to return "Parent" regardless of caller
return Parent.__name__
class Child(Parent):
pass
child = Child()
print(child.get_class_name()) # Returns "Parent", not "Child"
Hardcoding Parent.__name__ works, but it creates a rigid solution that isn't reusable. If the class is renamed, the code breaks. See the example below for a more flexible way to handle this.
class Parent:
def get_class_name(self):
# This returns the actual instance class name
return self.__class__.__name__
class Child(Parent):
pass
child = Child()
print(child.get_class_name()) # Correctly returns "Child"
The solution is to use self.__class__.__name__ instead of hardcoding the parent class name. Because self always refers to the instance calling the method, this expression correctly returns the child class's name—even when the method is defined in the parent. This dynamic approach is crucial in inheritance hierarchies, as it ensures your methods adapt to the specific object they're working with and avoids rigid, brittle code.
Using __name__ when full class path is needed
When your code has nested classes, relying on __name__ can be misleading. It only gives you the simple name, not the full path, which can create confusion. The following example demonstrates what happens when this context is lost.
class Outer:
class Inner:
pass
inner = Outer.Inner()
# This will only print "Inner", losing the context
print(f"Class name: {inner.__class__.__name__}")
The __name__ attribute returns only "Inner", which is ambiguous without its Outer context. This can lead to naming conflicts in complex code. The example below demonstrates how to retrieve the complete, qualified class path instead.
class Outer:
class Inner:
pass
inner = Outer.Inner()
# This prints "Outer.Inner", preserving the hierarchy
print(f"Class name: {inner.__class__.__qualname__}")
The solution is to use the __qualname__ attribute, which provides the full dotted path to the class. This is crucial in projects with nested classes or complex structures where a simple name could be ambiguous.
__name__returns only the immediate class name, likeInner.__qualname__returns the complete path, such asOuter.Inner.
Using __qualname__ prevents naming conflicts and makes your code more robust by preserving the class's full context.
Missing an underscore in __class__.__name__
It's easy to make a typo with Python's "dunder" attributes. Forgetting an underscore in an expression like __class__.__name__ is a frequent mistake that leads to an AttributeError. Because these names are so specific, even a small error breaks the code. See it happen below.
try:
1/0
except Exception as e:
# Typo in attribute name (missing underscore)
error_type = e.__class__.__name
print(f"Caught error of type: {error_type}")
The code attempts to get the exception's class name, but the typo __name instead of __name__ raises a new AttributeError. This masks the original ZeroDivisionError. The example below demonstrates the correct way to access the attribute.
try:
1/0
except Exception as e:
# Correct attribute name with both underscores
error_type = e.__class__.__name__
print(f"Caught error of type: {error_type}")
The solution is to use the correct dunder attribute __name__ with two underscores on each side. A typo is a common mistake that raises an AttributeError, which can be confusing during debugging. This is especially important in try...except blocks, where a new AttributeError can hide the original exception you're trying to catch and inspect. Always double-check the spelling of special attributes to avoid this pitfall.
Real-world applications
After navigating the common pitfalls, you can use class names to build practical tools for logging and creating simple plugin systems.
Using __class__.__name__ for custom logging
You can easily create more informative logs by using __class__.__name__ to automatically include the name of the class that an action is associated with.
def log_action(obj, action):
class_name = obj.__class__.__name__
print(f"[{class_name}] {action}")
class User:
def __init__(self, name):
self.name = name
user = User("Alice")
log_action(user, "logged in successfully")
log_action(user, "updated profile")
The log_action function is a flexible utility that centralizes logging. It uses obj.__class__.__name__ to dynamically get the name of the class from whatever object is passed in. This approach decouples the logging mechanism from the classes themselves, so the User class doesn't need to know how its actions are logged.
- Because the function isn't hardcoded, it works with any object type.
- This makes the logging system modular and reusable across your entire application.
Creating a simple plugin system with __name__
A class's __name__ attribute provides a straightforward way to build a plugin system, allowing you to register and call components by their name.
class PluginRegistry:
plugins = {}
@classmethod
def register(cls, plugin_class):
cls.plugins[plugin_class.__name__] = plugin_class
return plugin_class
@PluginRegistry.register
class TextProcessor:
def process(self, text):
return text.upper()
# Find and use a plugin by its class name
plugin_name = "TextProcessor"
if plugin_name in PluginRegistry.plugins:
plugin = PluginRegistry.plugins[plugin_name]()
result = plugin.process("hello world")
print(result)
This code demonstrates a simple registry pattern. The @PluginRegistry.register decorator automatically logs new classes in a central dictionary, making them discoverable by name.
- The
registermethod takes a class, likeTextProcessor, and stores it in thepluginsdictionary. - It uses the class’s
__name__attribute as the key, allowing you to retrieve the class later using a simple string.
This approach makes your system extensible. You can add new plugins without modifying the code that finds and uses them.
Get started with Replit
Now, turn this knowledge into a real tool. Tell Replit Agent to “build a Python logging utility that prefixes messages with the object’s class name” or “create a plugin system that registers classes by name.”
Replit Agent writes the code, tests for errors, and deploys your app. Start building with Replit.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.


.avif)
.avif)