Class in a Class in Python (Nested and Inner Classes)

Yes, you can put a class in a class in Python. Define the inner class inside the outer class body, and reach it as Outer.Inner. It’s called a nested or inner class, and it groups a helper type with its owner:

class Store:
    class Address:
        ...

Store.Address("120 Pine St", "Portland", "OR")

Each example was saved as a .py file and run with Python 3.12.5; the screenshots are the actual Windows Command Prompt windows.

Below, I’ll build a nested class, show the scope rule that causes most NameErrors, explain how an inner class can reach the outer object, and cover nested enums and dataclasses.

How to define a class inside a class in Python

Indent the inner class under the outer one, like a method. From inside the outer class you write Store.Address, and from outside it works exactly the same way:

class Store:
    class Address:
        def __init__(self, street, city, state):
            self.street, self.city, self.state = street, city, state

        def label(self):
            return f"{self.street}, {self.city}, {self.state}"

    def __init__(self, name, street, city, state):
        self.name = name
        self.address = Store.Address(street, city, state)

shop = Store("Corner Books", "120 Pine St", "Portland", "OR")
print(shop.name, "-", shop.address.label())

home = Store.Address("9 Elm Ave", "Tulsa", "OK")    # create one from outside
print(home.label())
print(type(home).__qualname__)

Output:

Corner Books - 120 Pine St, Portland, OR
9 Elm Ave, Tulsa, OK
Store.Address
Command Prompt output of a Python Store class with a nested Address class created inside and outside the outer class
The inner class works anywhere you can see the outer class. Its qualified name is Store.Address.

Nesting doesn’t make Address private. It just puts the name inside the Store namespace, so nobody confuses it with some other Address class.

Why does a nested class give a NameError?

Here’s the catch: names defined in a class body aren’t visible inside its methods. So a bare Item(...) inside a method fails, even though Item is right there:

class Order:
    class Item:
        def __init__(self, name, price):
            self.name, self.price = name, price

    def __init__(self):
        self.items = []

    def add_wrong(self, name, price):
        self.items.append(Item(name, price))          # bare name: not in scope here

    def add(self, name, price):
        self.items.append(self.Item(name, price))     # or Order.Item(...)

order = Order()
try:
    order.add_wrong("Coffee", 4.50)
except NameError as err:
    print("NameError:", err)

order.add("Coffee", 4.50)
order.add("Bagel", 2.75)
print([i.name for i in order.items], f"${sum(i.price for i in order.items):.2f}")

Output:

NameError: name 'Item' is not defined
['Coffee', 'Bagel'] $7.25
Command Prompt output of a Python nested class raising NameError when used by its bare name inside a method, then fixed with self.Item
Item alone raises NameError. self.Item or Order.Item finds it.

Methods skip the class scope when they look up names; they go straight from local to global. Python variable scope walks through the full lookup order.

Can an inner class access the outer class?

Not on its own. An inner class object has no hidden link to an instance of the outer class, unlike Java’s inner classes. If it needs one, pass the outer object in:

class Account:
    rate = 0.04

    def __init__(self, owner, balance):
        self.owner, self.balance = owner, balance

    class Statement:
        def __init__(self, account):
            self.account = account                   # pass the outer object in

        def summary(self):
            a = self.account
            interest = a.balance * Account.rate
            return f"{a.owner}: ${a.balance:,.2f}, yearly interest ${interest:,.2f}"

acct = Account("Maria Lopez", 12_500.00)
print(Account.Statement(acct).summary())

try:
    print(Account.Statement(acct).owner)
except AttributeError as err:
    print("AttributeError:", err)

Output:

Maria Lopez: $12,500.00, yearly interest $500.00
AttributeError: 'Statement' object has no attribute 'owner'
Command Prompt output of a Python inner Statement class receiving an outer Account instance, and an AttributeError when it doesn't
The Statement only knows about the account because we handed it in.

Class-level values like Account.rate are easy, since you can always name the outer class. Instance data like owner needs the instance.

Nesting an Enum inside a class

This is the nested class I use most. Status codes that only make sense for one class read nicely as Ticket.Status.OPEN:

from enum import Enum

class Ticket:
    class Status(Enum):
        OPEN = "open"
        IN_PROGRESS = "in progress"
        CLOSED = "closed"

    def __init__(self, title):
        self.title = title
        self.status = Ticket.Status.OPEN

    def close(self):
        self.status = Ticket.Status.CLOSED

t = Ticket("Printer jam in Dallas office")
print(t.title, "->", t.status.value)
t.close()
print(t.title, "->", t.status.value)
print([s.name for s in Ticket.Status])

Output:

Printer jam in Dallas office -> open
Printer jam in Dallas office -> closed
['OPEN', 'IN_PROGRESS', 'CLOSED']
Command Prompt output of a Python Ticket class with a nested Status Enum changing from open to closed
Ticket.Status keeps the allowed values right next to the class that uses them.

You’ll see the same idea in Django, where models carry a nested Meta class for settings, and in many libraries that group config this way.

Nested classes with dataclasses

Dataclasses nest the same way. Quote the type hint as "Employee.Contact", because the name Employee doesn’t exist yet while its body is still running:

from dataclasses import dataclass, field

@dataclass
class Employee:
    @dataclass
    class Contact:
        email: str
        phone: str

    name: str
    city: str
    contact: "Employee.Contact"
    skills: list = field(default_factory=list)

e = Employee("Sam Carter", "Raleigh", Employee.Contact("sam@example.com", "919-555-0142"))
print(e)
print(e.contact.email)

Output:

Employee(name='Sam Carter', city='Raleigh', contact=Employee.Contact(email='sam@example.com', phone='919-555-0142'), skills=[])
sam@example.com

For more on these, my Python dataclass guide covers fields, defaults and field(default_factory=...).

When to use a class in a class (and when not to)

Good fit for a nested classBetter as a separate class
Only the outer class uses itOther modules need it too
Small helper types, enums, configLarge classes with lots of logic
You want Outer.Inner namingYou’d need to pass the outer object everywhere
Stays short and readableIndentation gets three levels deep

My rule of thumb: if the inner class grows past a screen, move it to module level. If you’re really modeling “has a”, look at composition vs inheritance instead.

Here’s where I’d go next with Python classes:

Frequently asked questions

Can you have a class inside a class in Python?

Yes. Define it in the outer class body and use it as Outer.Inner. Python calls it a nested or inner class.

How do I create an object of an inner class?

Call it through the outer class: Outer.Inner(args). Inside the outer class’s methods, self.Inner(args) works too.

Why can’t my method see the inner class?

Methods don’t look up names in the class body, so a bare Inner is a NameError. Prefix it with self. or the outer class name.

Can an inner class access outer class variables?

Class variables, yes, by writing Outer.name. Instance variables only if you pass an outer instance to the inner class.

Is a nested class private in Python?

No. Anyone can use Outer.Inner. Nesting is about organizing names, not hiding them.

Are nested classes Pythonic?

They’re fine for small helpers like enums. The Python classes tutorial explains the scoping rules behind them.