📝 Python

Creating Classes: `__init__` and the Mystery of `self` 🏗️

0
Author
04e5cc8b-58ac-4bdc-bdee-661bbb
📅
Published
03.04.2026
⏱️
Reading time
5 min
👁️
Views
91
🌱
Level
Beginner

You know what OOP is. Now let’s create our first class and demystify two of Python’s main puzzles:

  1. __init__ — what’s with the strange name?
  2. self — why is it everywhere?

Let’s go! 🚀

🎯 Creating a Class: Syntax

Basic Structure

class ClassName:
    def __init__(self, parameters):
        # Initialize attributes
        self.attribute = value

    def method(self):
        # Object actions
        pass

Example: Zombie

class Zombie:
    def __init__(self, name, health):
        self.name = name
        self.health = health

    def groan(self):
        print(f"{self.name}: Граааах! 🧟")

# Creating an object
walker = Zombie("Ходок", 50)
walker.groan()  # Ходок: Граааах! 🧟

What’s happening?

  1. class Zombie: — declare the class
  2. __init__(self, name, health) — constructor (runs when the object is created)
  3. self.name = name — save attributes
  4. walker = Zombie("Ходок", 50) — create an object

🔑 The __init__ Method: Constructor

What Is It?

__init__ = constructor = a method that is called automatically when an object is created.

Analogy: When a baby 👶 is born, it immediately gets a name, gender, and date of birth. __init__ does the same for objects.

Syntax

def __init__(self, parameter1, parameter2):
    self.attribute1 = parameter1
    self.attribute2 = parameter2

Important:
- The name is always __init__ (two underscores on each side)
- The first parameter is always self
- Called automatically when you write Zombie("Ходок", 50)

Example: Human

class Human:
    def __init__(self, name, health, age):
        self.name = name
        self.health = health
        self.age = age
        self.kills = 0  # You can set a default value!

rick = Human("Рик", 100, 35)
print(rick.name)   # Рик
print(rick.kills)  # 0 (even though we didn't pass it!)

What happened here:

  1. We called Human("Рик", 100, 35)
  2. Python automatically called __init__(self, "Рик", 100, 35)
  3. Attributes were saved: name, health, age, kills
  4. The ready object rick was returned

🤔 The Mystery of self: What Is It?

Definition

self = a reference to the object itself.

When you write self.name, you’re saying: “The name attribute of this specific object”.

The Problem Without self

class Zombie:
    def __init__(self, name):
        name = name  # ❌ Doesn't work!

walker = Zombie("Ходок")
print(walker.name)  # AttributeError: 'Zombie' object has no attribute 'name'

Why doesn’t it work?
name is a local variable inside __init__. It dies after the method finishes!

The Solution: self

class Zombie:
    def __init__(self, name):
        self.name = name  # ✅ Works!

walker = Zombie("Ходок")
print(walker.name)  # Ходок

self.name = “The name attribute of the walker object”. It persists in the object!

Visualization

class Zombie:
    def __init__(self, name, health):
        self.name = name      # walker.name = "Ходок"
        self.health = health  # walker.health = 50

walker = Zombie("Ходок", 50)
runner = Zombie("Бегун", 30)

# walker and runner are different objects!
print(walker.name)   # Ходок
print(runner.name)   # Бегун

# Each has its own attributes
print(walker.health)  # 50
print(runner.health)  # 30

self lets each object have its own attributes!

🔄 self in Methods

Accessing Attributes

class Zombie:
    def __init__(self, name, health):
        self.name = name
        self.health = health

    def groan(self):
        # Use self to access name
        print(f"{self.name}: Граааах!")

    def take_damage(self, damage):
        # Use self to access health
        self.health -= damage
        print(f"{self.name} took {damage} damage. Health: {self.health}")

walker = Zombie("Ходок", 50)
walker.groan()            # Ходок: Граааах!
walker.take_damage(20)    # Ходок took 20 damage. Health: 30

Without self, methods wouldn’t know which attributes to use!

Calling Methods from Methods

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

    def groan(self):
        print(f"{self.name}: Граааах!")

    def attack(self):
        self.groan()  # ✅ Call another method via self!
        print(f"{self.name} attacks!")

walker = Zombie("Ходок")
walker.attack()
# Ходок: Граааах!
# Ходок attacks!

self.groan() = “Call the groan method of this object”.

🎨 Full Example: Human Class

class Human:
    def __init__(self, name, health=100):
        self.name = name
        self.health = health
        self.ammo = 12
        self.kills = 0

    def shoot(self):
        if self.ammo > 0:
            self.ammo -= 1
            print(f"{self.name} shoots! Ammo: {self.ammo}")
            return True
        else:
            print(f"{self.name}: Out of ammo!")
            return False

    def reload(self):
        self.ammo = 12
        print(f"{self.name} reloaded!")

    def status(self):
        print(f"{'='*30}")
        print(f"Name: {self.name}")
        print(f"Health: {self.health}")
        print(f"Ammo: {self.ammo}")
        print(f"Kills: {self.kills}")
        print(f"{'='*30}")

# Usage
rick = Human("Рик", 100)
rick.status()
# ==============================
# Name: Рик
# Health: 100
# Ammo: 12
# Kills: 0
# ==============================

rick.shoot()  # Рик shoots! Ammo: 11
rick.shoot()  # Рик shoots! Ammo: 10
rick.reload() # Рик reloaded!

📝 Default Parameters

class Zombie:
    def __init__(self, name, health=50, speed=2):
        #                      ^^^^^^^^^^  ^^^^^^^
        #                      Default values
        self.name = name
        self.health = health
        self.speed = speed

# Can omit health and speed
walker = Zombie("Ходок")
print(walker.health)  # 50 (default)

# Or pass your own values
runner = Zombie("Бегун", 30, 5)
print(runner.speed)  # 5

Rule: Parameters with defaults must come after required parameters.

⚠️ Common Mistakes

Mistake 1: Forgot self

class Zombie:
    def __init__(self, name):
        name = name  # ❌ Local variable!

walker = Zombie("Ходок")
print(walker.name)  # AttributeError

Fix:

self.name = name  # ✅ Object attribute

Mistake 2: Forgot self in Method Parameters

class Zombie:
    def __init__(name):  # ❌ No self!
        self.name = name

walker = Zombie("Ходок")  # TypeError

Fix:

def __init__(self, name):  # ✅ self is the first parameter

Mistake 3: Typo in __init__

class Zombie:
    def _init_(self, name):  # ❌ Single underscore!
        self.name = name

walker = Zombie("Ходок")  # TypeError

Fix:

def __init__(self, name):  # ✅ Two underscores on each side

Mistake 4: Accessing an Attribute Without self

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

    def groan(self):
        print(f"{name}: Граааах!")  # ❌ No self!

walker = Zombie("Ходок")
walker.groan()  # NameError: name 'name' is not defined

Fix:

print(f"{self.name}: Граааах!")  # ✅ self.name

🎯 When to Use What?

What Where Why
self.attribute In __init__ Save object data
self.attribute In methods Read/modify data
self.method() In methods Call another method
parameter Without self Local variable (dies after the method)

📚 Comparison: Functions vs Classes

Functions

def create_zombie(name, health):
    return {"name": name, "health": health}

def zombie_groan(zombie):
    print(f"{zombie['name']}: Граааах!")

walker = create_zombie("Ходок", 50)
zombie_groan(walker)

Problems:
- ❌ Functions and data are separate
- ❌ Easy to make mistakes (pass the wrong dict)
- ❌ No structure

Classes

class Zombie:
    def __init__(self, name, health):
        self.name = name
        self.health = health

    def groan(self):
        print(f"{self.name}: Граааах!")

walker = Zombie("Ходок", 50)
walker.groan()

Advantages:
- ✅ Data and methods together
- ✅ Clear structure
- ✅ Fewer mistakes

💡 Checklist: Test Yourself

  • [ ] Know how to create a class (class ClassName:)
  • [ ] Understand why __init__ is needed (constructor)
  • [ ] Understand what self is (reference to the object)
  • [ ] Can create attributes (self.attribute = value)
  • [ ] Can access attributes in methods (self.attribute)
  • [ ] Know why self is always the first parameter
  • [ ] Understand the difference between name and self.name

🚀 Summary

__init__ — constructor, called automatically when an object is created.

self — reference to the object itself, lets you:
- Save attributes: self.name = name
- Access attributes: print(self.name)
- Call methods: self.other_method()

Syntax:

class ClassName:
    def __init__(self, parameters):
        self.attribute = value

Creating an object:

object = ClassName(arguments)

Next step: Learn to create methods — object actions! ⚔️

Your reaction to the article

💬 Comments (0)

🔐 Sign in to leave a comment
🚪 Login
💭

No comments yet

Be the first to share your opinion about this article!

🔗 Similar

Similar articles

Continue learning with these materials

📝

Setting Up Your Environment: Python, pip, and VS …

Before writing code locally, you need to set up three tools: Python, pip, and VS...

📅 04.06.2026 👁️ 17
📝

The datetime Module: Working with Dates and Times

datetime is Python's standard module for working with dates and times. It's part of the...

📅 08.05.2026 👁️ 67
📝

.env Files and Environment Variables: Keeping Sec…

Imagine you wrote a program with an API key hardcoded in the source and pushed...

📅 08.05.2026 👁️ 76

Did you like the article?

Subscribe to our updates and receive new articles first. Grow with PyLand!