Skip to lesson
Python for JS Developers

OOPLesson 1 of 3

Python Classes

4 min

Python classes look similar to JS classes with a few key twists. The constructor is called __init__ (not constructor), and every method takes self as its first parameter (like this, but explicit). You do not use the new keyword either: just call the class like a function.

Here is the biggest conceptual difference from JS: self is not this. In JavaScript, this is dynamically bound based on HOW a function is called. That is why you get the infamous 'lost this' bug when passing methods as callbacks: btn.addEventListener('click', user.greet) loses the this context, and you need .bind(this) or arrow functions to fix it. In Python, accessing user.greet gives you a bound method that remembers its instance. You can pass it around as a callback and self is still correct. No .bind(), no arrow functions, no surprises.

Properties work differently too. Instead of the get/set syntax JS uses, Python uses the @property decorator. This gives you a method that behaves like an attribute: you access it without parentheses, but code runs behind the scenes.

Static-style methods come in two flavours. @classmethod receives the class itself as cls, exactly like this inside a JS static method, which makes it perfect for alternative constructors (User.from_json(data)). @staticmethod receives neither the class nor an instance; it is just a function namespaced inside the class.

There is no real private in Python, unlike JS's #private fields. A leading underscore (_password) is a convention meaning 'internal, please do not touch'. A double underscore (__secret) triggers name mangling: Python renames it to _ClassName__secret, which prevents accidental clashes in subclasses but does not lock anything down. And __name__ style names (dunders) are reserved for Python's own special methods like __init__ and __str__; never invent your own.

Naming conventions: classes use PascalCase (MyClass), methods and attributes use snake_case (my_method), and constants use SCREAMING_CASE (MAX_RETRIES). self is also just a convention; you could technically call it anything, but every Python developer expects self.

Quick reference

JavaScriptPythonNote
constructor()__init__(self)self is explicit
this.nameself.nameself always first param
new User()User()No new keyword
get prop() {}@propertyDecorator-based
#private_conventionNo true private
static create() { new this() }@classmethod def create(cls)cls = the class, like this in a static
static helper() (no this)@staticmethodNo access to class or instance

Side by side

JavaScript
// JavaScript Classes
class User {
  #password;
  constructor(name, email) {
    this.name = name;
    this.email = email;
    this.#password = "";
  }
  greet() { return "Hi, I'm " + this.name; }
  get info() { return this.name + " (" + this.email + ")"; }
  set pass(v) {
    if (v.length < 8) throw new Error("Too short");
    this.#password = v;
  }
  static create(n, e) { return new this(n, e); }
}
Python
# Python Classes
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
        self._password = ""

    def greet(self):
        return f"Hi, I'm {self.name}"

    @property
    def info(self):
        return f"{self.name} ({self.email})"

    @property
    def password(self):
        return "***hidden***"

    @password.setter
    def password(self, value):
        if len(value) < 8:
            raise ValueError("Too short")
        self._password = value

    @classmethod
    def create(cls, name, email):
        return cls(name, email)

    @staticmethod
    def is_valid_email(email):
        return "@" in email

u = User("Alice", "a@b.com")  # no new!
callback = u.greet            # bound: no .bind() needed

Exercise

Real Python, running in your browser

Convert this JS class to Python:

Starter code
class Product {
  constructor(name, price, category) {
    this.name = name;
    this.price = price;
    this.category = category;
    this._discount = 0;
  }
  get finalPrice() {
    return this.price * (1 - this._discount);
  }
  set discount(percent) {
    if (percent < 0 || percent > 100)
      throw new Error("Invalid");
    this._discount = percent / 100;
  }
  toString() {
    return this.name + ": 
quot;
+ this.finalPrice.toFixed(2);
} static compare(p1, p2) { return p1.finalPrice - p2.finalPrice; } }

Ctrl + Enter runCtrl + Shift + Enter checkTab indentEsc then Tab to leave the editorCtrl + / comment