EASYTUTORGUIDE

Practical tutorials, tools, courses, digital skills, and business promotion.

Free Learning

JavaScript – Chapter 2: Development Environment

Learn what JavaScript is, where it runs, why it is important, and how beginners can start using it with simple examples and clear output.

Beginner Friendly JavaScript Basics Web Development Code Examples
JavaScript Lesson 2 Chapter 2 Topics Day / Night Mode

Main reading content

python-course-chapter-20

Chapter 20: Dataclasses, Enums, and Advanced Class Tools

Learn how Python provides efficient tools for data-focused classes, fixed choices, memory control, validation, and advanced class creation.

Goal: Build clearer Python classes with dataclasses, enums, named tuples, slots, descriptors, metaclasses, and practical design principles.

Chapter 20 Topics

20.1 Introduction to Dataclasses

A dataclass is a special kind of Python class designed mainly for storing related data. It automatically creates useful methods such as an initializer, a readable text representation, and object comparison. Dataclasses reduce repeated code, so beginners can focus on the information an object should contain instead of writing the same class methods manually.

Example

from dataclasses import dataclass

@dataclass
class Student:
    name: str
    age: int

student = Student("Mina", 18)
print(student)

Output

Student(name='Mina', age=18)

Output Explanation: The @dataclass decorator tells Python to generate common class methods automatically. Python creates an initializer that accepts name and age. When the object is printed, the generated representation displays the class name and both stored values.

20.2 Creating Dataclasses

To create a dataclass, import dataclass, place the @dataclass decorator above the class, and describe each field using a name and type annotation. Python uses those fields to build the constructor. You can then create objects by passing values in the same order as the fields appear.

Example

from dataclasses import dataclass

@dataclass
class Book:
    title: str
    pages: int
    price: float

book = Book("Python Basics", 250, 29.99)
print(book.title)
print(book.pages)
print(book.price)

Output

Python Basics
250
29.99

Output Explanation: The Book dataclass contains three fields. The object receives one value for each field. Accessing book.title, book.pages, and book.price prints the values stored inside the object.

20.3 Default Values

A default value is used when the programmer does not provide a value for a field. Defaults make object creation easier when some information commonly stays the same. Fields with default values must usually come after fields without defaults, because Python needs to know which constructor arguments are required and which ones are optional.

Example

from dataclasses import dataclass

@dataclass
class User:
    username: str
    active: bool = True

user1 = User("ali")
user2 = User("sara", False)

print(user1)
print(user2)

Output

User(username='ali', active=True)
User(username='sara', active=False)

Output Explanation: The first object does not provide an active value, so Python uses the default value True. The second object provides False, which replaces the default for that object.

20.4 Default Factories

A default factory creates a new default object for every dataclass instance. It is especially important for mutable values such as lists, sets, and dictionaries. Using one shared list as a normal default could cause several objects to accidentally change the same data. A factory safely gives each object its own separate collection.

Example

from dataclasses import dataclass, field

@dataclass
class ShoppingList:
    owner: str
    items: list[str] = field(default_factory=list)

list1 = ShoppingList("Mina")
list2 = ShoppingList("Omid")

list1.items.append("Milk")

print(list1.items)
print(list2.items)

Output

['Milk']
[]

Output Explanation: The default_factory=list creates a different empty list for every object. Adding Milk to list1 does not change list2, so its list remains empty.

20.5 Frozen Dataclasses

A frozen dataclass prevents its fields from being changed after the object is created. This is useful when an object should behave like a fixed record. Frozen objects can make programs safer because important data cannot be accidentally replaced. Attempting to assign a new value to a frozen field raises an error.

Example

from dataclasses import dataclass

@dataclass(frozen=True)
class Coordinate:
    x: int
    y: int

point = Coordinate(4, 7)
print(point.x)
print(point.y)

Output

4
7

Output Explanation: The object is successfully created with x equal to 4 and y equal to 7. Because the dataclass is frozen, these values can be read but should not be reassigned later.

20.6 Ordered Dataclasses

An ordered dataclass can compare objects using operators such as less than, greater than, less than or equal to, and greater than or equal to. Python compares the fields in the order they are declared. Ordered dataclasses are useful when records need to be sorted according to their stored values.

Example

from dataclasses import dataclass

@dataclass(order=True)
class Score:
    points: int
    player: str

score1 = Score(80, "Ali")
score2 = Score(95, "Sara")

print(score1 < score2)
print(sorted([score2, score1]))

Output

True
[Score(points=80, player='Ali'), Score(points=95, player='Sara')]

Output Explanation: Python first compares the points field because it appears first. Since 80 is less than 95, the first comparison is True. Sorting places the lower score before the higher score.

20.7 Post-Initialization Processing

The __post_init__() method runs immediately after the dataclass initializer finishes. It is useful for calculating a value, validating input, or adjusting data after all fields have been assigned. This lets a dataclass keep automatically generated initialization while still performing additional setup work.

Example

from dataclasses import dataclass

@dataclass
class Rectangle:
    width: float
    height: float
    area: float = 0

    def __post_init__(self):
        self.area = self.width * self.height

shape = Rectangle(5, 3)
print(shape.area)

Output

15

Output Explanation: Python first stores width as 5 and height as 3. Then __post_init__() multiplies them and stores the result in area. Printing the area displays 15.

20.8 Comparing Dataclass Objects

Dataclasses normally generate an equality method automatically. Two objects are considered equal when they are instances of the same dataclass and all comparable fields contain equal values. This is convenient because you do not need to write a custom __eq__() method for simple data objects.

Example

from dataclasses import dataclass

@dataclass
class Product:
    name: str
    price: float

product1 = Product("Keyboard", 40.0)
product2 = Product("Keyboard", 40.0)
product3 = Product("Mouse", 20.0)

print(product1 == product2)
print(product1 == product3)

Output

True
False

Output Explanation: The first two objects contain the same name and price, so Python considers them equal. The third object has different field values, so the second comparison produces False.

20.9 Converting Dataclasses

Python provides helper functions that convert dataclass objects into dictionaries or tuples. The asdict() function creates a dictionary, while astuple() creates a tuple. These conversions are helpful when saving data, preparing information for JSON, sending values to another function, or displaying records in another format.

Example

from dataclasses import dataclass, asdict, astuple

@dataclass
class Person:
    name: str
    age: int

person = Person("Nima", 25)

print(asdict(person))
print(astuple(person))

Output

{'name': 'Nima', 'age': 25}
('Nima', 25)

Output Explanation: The first function uses field names as dictionary keys and field values as dictionary values. The second function returns only the values in field order, producing a tuple.

20.10 Introduction to Enums

An enum is a class that represents a fixed group of named choices. Instead of using unexplained numbers or strings throughout a program, enums provide clear names such as PENDING, SHIPPED, or DELIVERED. They make code easier to read and reduce mistakes caused by invalid values.

Example

from enum import Enum

class OrderStatus(Enum):
    PENDING = "pending"
    SHIPPED = "shipped"
    DELIVERED = "delivered"

status = OrderStatus.SHIPPED

print(status)
print(status.value)

Output

OrderStatus.SHIPPED
shipped

Output Explanation: Printing the enum member shows its class and member name. Accessing status.value prints the actual value assigned to that member, which is the string shipped.

20.11 Creating Enums

To create an enum, import Enum, inherit from it, and define named members inside the class. Each member receives a value. Program code should normally refer to the member name rather than repeating the raw value. This keeps choices organized in one reliable location.

Example

from enum import Enum

class Direction(Enum):
    NORTH = 1
    EAST = 2
    SOUTH = 3
    WEST = 4

direction = Direction.NORTH

print(direction.name)
print(direction.value)

Output

NORTH
1

Output Explanation: The name property returns the member name NORTH. The value property returns the value assigned to that member, which is 1.

20.12 Auto Values

The auto() function automatically assigns values to enum members. It is useful when the exact numeric values are not important and the program mainly needs unique named choices. Auto values reduce typing and prevent accidental duplicate numbering when more members are added later.

Example

from enum import Enum, auto

class Priority(Enum):
    LOW = auto()
    MEDIUM = auto()
    HIGH = auto()

print(Priority.LOW.value)
print(Priority.MEDIUM.value)
print(Priority.HIGH.value)

Output

1
2
3

Output Explanation: Python automatically gives the members increasing integer values. In this example, LOW receives 1, MEDIUM receives 2, and HIGH receives 3.

20.13 Integer Enums

An integer enum uses members that also behave like integers. Python provides IntEnum for this purpose. It is useful when a program needs meaningful enum names but must also compare or pass the values as ordinary integers, such as status codes, levels, or menu selections.

Example

from enum import IntEnum

class AccessLevel(IntEnum):
    GUEST = 1
    USER = 2
    ADMIN = 3

level = AccessLevel.ADMIN

print(level)
print(level == 3)
print(level > AccessLevel.USER)

Output

3
True
True

Output Explanation: Because AccessLevel inherits from IntEnum, its members can behave like integers. The admin level equals 3 and is greater than the user level.

20.14 Flag Enums

A flag enum allows several named options to be combined into one value. It is useful for permissions, settings, or features where more than one choice may be active at the same time. Python uses bitwise operations internally, but beginners can combine flags clearly with the vertical bar operator.

Example

from enum import Flag, auto

class Permission(Flag):
    READ = auto()
    WRITE = auto()
    DELETE = auto()

user_permissions = Permission.READ | Permission.WRITE

print(user_permissions)
print(Permission.READ in user_permissions)
print(Permission.DELETE in user_permissions)

Output

Permission.READ|WRITE
True
False

Output Explanation: The user receives both read and write permissions. The membership checks show that READ is included, while DELETE is not included.

20.15 Named Tuples

A named tuple is a lightweight record that stores values like a tuple but lets you access them by meaningful names. It is useful when you want simple, fixed, read-only data without creating a full class. Named tuples are compact, easy to print, and support both field access and normal tuple indexing.

Example

from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])

point = Point(10, 20)

print(point.x)
print(point.y)
print(point[0])

Output

10
20
10

Output Explanation: The values can be accessed using the field names x and y. Because the object is also a tuple, index 0 returns the first value, which is 10.

20.16 Slots

Slots are a class feature that limits which instance attributes can exist. Normal objects usually store attributes in a flexible dictionary. A slotted class stores only the names declared in its slots. This can reduce memory usage and prevent accidental creation of misspelled or unexpected attributes.

Example

class Student:
    __slots__ = ("name", "grade")

    def __init__(self, name, grade):
        self.name = name
        self.grade = grade

student = Student("Lina", 9)

print(student.name)
print(student.grade)

Output

Lina
9

Output Explanation: The object stores only the declared name and grade attributes. The values assigned in the initializer can be read normally and are printed on separate lines.

20.17 The __slots__ Attribute

The __slots__ attribute lists the instance attributes that a class allows. When it is present, Python usually does not create the normal per-object attribute dictionary. This makes the object structure more controlled. Attempting to add an undeclared attribute normally raises an AttributeError.

Example

class Car:
    __slots__ = ("brand", "year")

    def __init__(self, brand, year):
        self.brand = brand
        self.year = year

car = Car("Toyota", 2026)

print(car.brand)
print(car.year)
print(hasattr(car, "__dict__"))

Output

Toyota
2026
False

Output Explanation: The first two lines display the allowed attributes. The last line is False because this simple slotted object does not have the usual instance __dict__.

20.18 Descriptors Introduction

A descriptor is an object that controls how another class attribute is read, assigned, or deleted. Properties, methods, and several Python tools use the descriptor system internally. Descriptors are an advanced feature, but the main idea is simple: one reusable object can manage access rules for attributes in many class instances.

Example

class UpperCase:
    def __get__(self, instance, owner):
        return instance._name

    def __set__(self, instance, value):
        instance._name = value.upper()

class Person:
    name = UpperCase()

    def __init__(self, name):
        self.name = name

person = Person("mina")
print(person.name)

Output

MINA

Output Explanation: Assigning mina calls the descriptor's __set__() method. That method converts the text to uppercase before storing it. Reading the attribute calls __get__(), which returns MINA.

20.19 Custom Descriptors

A custom descriptor can validate or transform values whenever an attribute is assigned. It usually defines methods such as __get__(), __set__(), and sometimes __delete__(). Custom descriptors are useful when several classes or fields need the same validation behavior without repeating property code.

Example

class PositiveNumber:
    def __set_name__(self, owner, name):
        self.storage_name = "_" + name

    def __get__(self, instance, owner):
        return getattr(instance, self.storage_name)

    def __set__(self, instance, value):
        if value <= 0:
            raise ValueError("Value must be positive")
        setattr(instance, self.storage_name, value)

class Product:
    price = PositiveNumber()

    def __init__(self, price):
        self.price = price

product = Product(25)
print(product.price)

Output

25

Output Explanation: The descriptor checks that the price is greater than zero before storing it. Because 25 is valid, it is saved and later returned when product.price is printed.

20.20 Metaclasses Introduction

A metaclass is the class used to create other classes. Most Python classes are created by the built-in metaclass named type. Metaclasses can inspect or modify a class when it is defined. They are advanced and uncommon in beginner projects, but understanding them helps explain how Python classes themselves are objects.

Example

class AddCategory(type):
    def __new__(mcls, name, bases, namespace):
        namespace["category"] = "Example"
        return super().__new__(mcls, name, bases, namespace)

class Item(metaclass=AddCategory):
    pass

print(Item.category)
print(type(Item))

Output

Example
<class '__main__.AddCategory'>

Output Explanation: The metaclass adds a category attribute while creating the Item class. The second line shows that Item was created by the custom metaclass AddCategory.

20.21 Practical Class Design

Good class design means choosing the simplest tool that clearly represents the data and behavior. Use a dataclass for data-focused objects, an enum for fixed choices, a named tuple for small read-only records, and a regular class when detailed behavior is required. Clear names and small responsibilities make classes easier to understand and maintain.

Example

from dataclasses import dataclass
from enum import Enum

class Membership(Enum):
    BASIC = "Basic"
    PREMIUM = "Premium"

@dataclass
class Customer:
    name: str
    membership: Membership

    def description(self):
        return f"{self.name}: {self.membership.value}"

customer = Customer("Sara", Membership.PREMIUM)
print(customer.description())

Output

Sara: Premium

Output Explanation: The enum safely represents the allowed membership choices. The dataclass stores the customer's information, and the method combines the name with the readable enum value.

20.22 Chapter Practice Exercises

Practice exercises help you remember when to use dataclasses, enums, named tuples, slots, descriptors, and related class tools. Try each exercise independently before checking an answer. Begin with simple data records and fixed choices, then move toward validation and class customization. Rewriting examples with your own names and values improves understanding.

Example

from dataclasses import dataclass
from enum import Enum

class Size(Enum):
    SMALL = "Small"
    MEDIUM = "Medium"
    LARGE = "Large"

@dataclass
class Shirt:
    color: str
    size: Size

shirt = Shirt("Blue", Size.MEDIUM)

print(shirt.color)
print(shirt.size.value)

Output

Blue
Medium

Output Explanation: This practice example combines two chapter ideas. The dataclass stores the shirt information, while the enum limits the size to named choices. The output displays the color and the readable enum value.

20.23 Chapter Mini Project

This mini project builds a small task system using a dataclass and an enum. The enum controls the allowed task statuses, while the dataclass stores each task's title, priority, and status. A method produces a readable summary. This design is clearer and safer than using unrelated dictionaries and unverified status strings.

Example

from dataclasses import dataclass
from enum import Enum

class TaskStatus(Enum):
    TODO = "To Do"
    WORKING = "Working"
    DONE = "Done"

@dataclass
class Task:
    title: str
    priority: int
    status: TaskStatus = TaskStatus.TODO

    def summary(self):
        return (
            f"{self.title} | "
            f"Priority: {self.priority} | "
            f"Status: {self.status.value}"
        )

task1 = Task("Study dataclasses", 1)
task2 = Task("Build mini project", 2, TaskStatus.WORKING)

print(task1.summary())
print(task2.summary())

Output

Study dataclasses | Priority: 1 | Status: To Do
Build mini project | Priority: 2 | Status: Working

Output Explanation: The first task uses the default status TODO. The second task explicitly uses WORKING. The summary() method formats the title, priority, and readable status value into one line for each task.

Chapter 2: Development EnvironmentPrepare Your JavaScript Workspace

A modern course built to help learners study step by step with clarity, comfort, and confidence.