Classes & OOP
Python has classes, constructors, inheritance, and even a rough equivalent of interfaces —
the ideas from C#'s object model all carry over. What's different is how much of it the
language enforces versus leaves as a social convention.
C#'s private is a compiler-enforced wall. Python's version is a naming convention
that a determined caller can walk straight through.
💡 The Bottom Line
Python classes use __init__ instead of a same-named constructor, self
instead of an implicit this (but written explicitly as the first parameter of
every method), and a single leading underscore instead of the private keyword —
one that the language doesn't actually enforce.
Defining a class and a constructor
public class Dog
{
public string Name { get; set; }
public Dog(string name)
{
Name = name;
}
public void Bark()
{
Console.WriteLine($"{Name} says woof!");
}
}
var d = new Dog("Rex");
d.Bark();
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print(f"{self.name} says woof!")
d = Dog("Rex")
d.bark()
📋 Key differences
- The constructor is a method named
__init__, not a method matching the class name. - Every instance method explicitly takes
selfas its first parameter — Python'sthis, but never implicit and never omitted from the signature. - No
newkeyword when creating an instance:Dog("Rex"), notnew Dog("Rex"). - No separate property syntax (
{ get; set; }) — you just assignself.namedirectly; Python has a separate@propertydecorator for when you actually need getter/setter logic, used far less often than C# properties.
⚠️ Gotcha
Forgetting self as the first parameter of a method is the single most common
Python OOP mistake for C# developers — it's invisible in C# because this is
always implicit there. Miss it in Python and you'll get a confusing
TypeError: bark() takes 0 positional arguments but 1 was given — Python is
telling you it tried to pass the instance in as self automatically, but your
method signature didn't leave room for it.
"Private" fields: convention, not enforcement
public class Account
{
private decimal _balance;
// truly inaccessible outside the class
}
class Account:
def __init__(self):
self._balance = 0
# a single underscore signals "internal use
# only" — but nothing stops account._balance
A single leading underscore (_balance) is the community convention for "treat
this as private." A double leading underscore (__balance) triggers Python's
name mangling, which makes accidental access harder but still not truly impossible.
Neither is enforced by the language the way C#'s private keyword is — Python trusts
other developers to respect the underscore, rather than blocking them at compile time.
Inheritance and interfaces
Inheritance looks familiar — class Puppy(Dog): instead of
class Puppy : Dog — and calling the parent constructor uses
super().__init__(...) instead of base(...). Python has no dedicated
interface keyword; the closest equivalent is an abstract base class
from the abc module, or — more commonly in idiomatic Python — just relying on
"duck typing": if an object has the right methods, it's usable, regardless of any declared
type relationship at all.
🙋 There Are No Dumb Questions
Q: If there's no real "private," how does anyone build safe abstractions in Python?
A: Mostly through convention, documentation, and code review, rather than compiler
enforcement — Python's design philosophy leans toward "we're all consenting adults here."
For cases where you genuinely need enforcement (e.g. a public library boundary), Python
developers reach for double-underscore name mangling or, increasingly, static type
checkers — but true compile-time privacy the way C# has it doesn't exist.
🔧 Try It Yourself
Port a small C# class hierarchy you know well — a base class with one virtual method and
one subclass overriding it — into Python. Use super().__init__(...) in the
subclass's __init__, and confirm calling the overridden method on a subclass
instance calls the subclass's version, exactly like C#'s virtual/override
— except in Python, every method is overridable by default, with no virtual
keyword required.
✅ Quick Check
In Python, what actually enforces that a field named _balance is treated as
private?
Last stop: a rundown of the gotchas that catch C# developers most often once they're writing real Python day to day.