AlgoMaster Logo

self Keyword

High Priority16 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

Every method on a Python class takes self as its first parameter, and every time you call a method, Python passes the instance in as that first argument. The pattern shows up so often that it starts to feel like a keyword, but it isn't. This lesson covers what self actually is, how Python passes it, the difference between self and ordinary local variables, when self isn't around (class methods and static methods), and the small set of mistakes around it that cause bugs.

What self Actually Is

self is a parameter name. Nothing more. It isn't a reserved word, it isn't built into the grammar, and Python doesn't check that you used it. You could rename it to this, instance, or me and the program would still run. The reason every Python codebase calls it self is convention, not requirement, and breaking that convention makes the code stand out for no good reason.

The smallest class that uses self:

Inside __init__, self refers to the new Product Python just created, and the two self.something = value lines attach attributes to that specific instance. Inside describe, self refers to whichever Product the method was called on, and the f-string reads its attributes back. Same parameter name, same role: a handle on "the instance this method is operating on right now".

The same code with a different parameter name still works:

Python doesn't care. But every Python developer reading this would prefer the first version. The convention exists so that anyone can open a file, see self, and immediately know what role that parameter plays. Don't fight the convention; use self.

How Python Passes the Instance

The interesting question is not what self is, but how it gets there. When you write mouse.describe(), you didn't pass any arguments, yet the method body received self as its first parameter. Where did it come from?

mouse.describe() is shorthand. Python rewrites it as Product.describe(mouse) before running it. The instance you called the method on becomes the first argument, automatically. The same rule applies to every method on every class:

The first form is what you'd write in everyday code; the second is what Python is doing internally. The dotted call mouse.describe() looks up describe on the Product class, binds the instance mouse to the first parameter, and runs the method.

The diagram shows the four steps Python takes when you call a method on an instance. The lookup finds the function on the class, the binding step inserts the instance as the first argument, and the call proceeds like any other function call. The whole mechanism is what makes the dotted syntax work.

This is also why a method without self as the first parameter is broken: Python is going to pass the instance as the first argument no matter what, and if the method doesn't have a slot for it, the argument count ends up wrong. The common mistakes section shows this error.

A useful way to confirm the rewriting is to print self itself:

Same address both times. The object printed outside the method and the self Python passed inside the method are the same object. self is the instance.

self.attr Versus a Local Variable

Inside a method body, names without self. are local variables. Names with self. are attributes on the instance. The two look similar in code but live in different places, and conflating them is a frequent source of bugs.

A local variable exists only while the method is running. It vanishes the moment the method returns. An attribute attached through self. is part of the object and survives as long as the object does.

self.items survives every call to add because it's attached to the cart. local_total doesn't; it was a local variable inside add, and there's no cart.local_total to read afterward. If the total should stick around, the assignment must use self.local_total = ... instead.

A sharper version of the same idea. The two assignments below look almost identical but have very different effects:

The local status = new_status line did exactly nothing useful. It created a variable inside update_status that was discarded the instant the method returned. Only the second line attached status to the order. Writing status = "shipped" and forgetting the self. produces no error and no attribute change. Always check the dot.

self Is Not a Keyword (You Could Rename It, Don't)

Some languages have this built into the grammar and can't be renamed. In Python, self is just a name. The parser doesn't recognize it; the interpreter doesn't treat it specially; the language reference doesn't reserve it.

Renaming it on a single method is legal:

__init__ uses me, describe uses instance, and the code still works because Python doesn't check the name. It only checks that the first parameter exists and gets bound to the instance.

The reason self is used everywhere is communication. Open any Python codebase, any textbook, any library, any tutorial: it's self. Renaming it on your own classes makes your code stand out for the wrong reason. Reviewers slow down to figure out whether the rename was intentional, whether it implies something special, whether there's a bug. There's no upside.

There is one place where the convention shifts, and that's class methods, covered next. The first parameter there is called cls, not self. That's also convention, not law, but it's followed just as universally.

When self Isn't There: classmethod and staticmethod

Not every method on a class takes self. There are two decorators, @classmethod and @staticmethod, that change what gets passed in (or whether anything gets passed in at all). This is enough background so the absence of self doesn't look like a typo.

A @classmethod receives the class as its first argument, by convention named cls instead of self. The classic use case is an alternate constructor:

Inside from_dict, cls is Product itself, not a Product instance. Calling cls(...) runs the normal constructor and returns a new product. The class method doesn't have a self because there's no instance yet, it's about to make one.

A @staticmethod receives nothing at all. It's a function that lives inside the class namespace:

apply_tax doesn't read or modify any instance, and it doesn't need the class for anything. The @staticmethod decorator tells Python "don't pass anything as the first argument", so the method's parameter list is the same whether you call it on the class or on an instance.

The rule of thumb is straightforward. If the method needs to look at or modify a specific instance, use self. If it needs the class but no instance (alternate constructors, factory methods, anything that calls cls(...)), use @classmethod and cls. If it doesn't need either, it's a @staticmethod, and arguably it might as well be a plain function outside the class.

Common Mistakes

A few specific bugs come up around self, and recognizing them early saves debugging time. Each one produces a different error message or, worse, no error at all.

Forgetting self in the Method Signature

The first parameter of a method has to be there to catch the instance Python is passing in. Drop it, and the call ends up with one extra argument:

The error message tells the story. You called mouse.describe() with zero arguments, but Python counted one (the instance it was about to pass in), and describe is declared to take zero. The fix is to put self back:

This bug hits even harder when the missing self is on __init__ itself, because then the error mentions the constructor and the argument counts look confusing. The cause is always the same: the first parameter is missing, Python is still going to pass the instance, the counts don't line up.

Forgetting self. Inside the Method Body

This is the quieter and more dangerous of the two. The code looks like an attribute assignment, but the self. is missing, and Python creates a local variable that's gone the moment the method returns.

What's wrong with this code?

The print shows the original price, not the discounted one. Inside apply_discount, price = ... created a local variable named price that held the discounted value briefly, then disappeared when the method returned. The instance attribute self.price was never touched. No error was raised; the bug made the method a no-op.

Fix:

The left-hand side now is self.price, which is the attribute on the instance. The right-hand side computes the new value from the current self.price. After the method returns, the new value sticks. The difference between the two versions is one missing prefix, and that prefix is the difference between "updates the cart" and "does nothing visible".

This is a correctness bug, not a performance one. Reading the method again with the question "where is self.?" usually finds it.

Calling a Method on the Class Without an Instance

If you call a method directly on the class instead of on an instance, Python doesn't pass anything as self. You have to supply the instance yourself, and forgetting to do so produces a confusing error:

This time Python doesn't auto-pass anything because there's no instance on the left side of the dot. Product.describe is a function that takes self as its first parameter, and you called it with no arguments. The fix is either to call it on an instance (mouse.describe()) or to pass the instance explicitly (Product.describe(mouse)). The two forms produce the same result; the first is the conventional form.

There's a legitimate place for the second form, which is when a subclass wants to call a parent method on self explicitly. The super() builtin is the modern way to do that.

Best Practices

A short checklist for working with self that catches the issues above before they cause bugs.

  • Always name the first parameter `self`. Don't rename it. Don't skip it. Every method, including __init__, takes self first.
  • Use `self.` for anything that should outlive the method call. If the value should be readable after the method returns, the assignment has to start with self.. If it only matters for the duration of the method, a local name is fine.
  • Don't shadow attributes with locals. A parameter named the same thing as an attribute is fine (def __init__(self, name): self.name = name), but inside the method, name is the parameter and self.name is the attribute. The two are different.
  • Avoid `Product.describe(mouse)` in everyday code. That form has a place inside super() calls and a few advanced patterns, but in normal code, the dotted call (mouse.describe()) is what reads cleanly.
  • Reserve `cls` for class methods. When you write a @classmethod, the first parameter is cls, not self, because the method is bound to the class, not an instance. Mixing the two conventions confuses readers.
  • If a method doesn't touch `self`, ask whether it should be a method at all. It might fit better as a @staticmethod or as a plain function outside the class.

These aren't rules Python enforces. They're conventions most codebases follow, and following them keeps the code readable to anyone who knows Python.

Quiz

self Keyword Quiz

10 quizzes