Magic Methods
Magic methods (also known as dunder methods) allow you to define how your classes behave with built-in operators and language constructs. They enable operator overloading and customize object lifecycle.
Object Lifecycle
__constructor__
Called automatically when creating a new instance. Use it to initialize instance attributes.
constructor.rn class Person {
fun __constructor__(name, age) {
this.name = name
this.age = age
}
fun greet() {
print("Hi, I'm " + this.name)
}
}
var p = Person("Alice", 30) # __constructor__ called here
p.greet() # Output: Hi, I'm Alice
__destructor__
Called automatically when an object is deleted with del. Use it for cleanup operations like releasing resources or closing connections.
destructor.rn var active_connections = 0
class Connection {
fun __constructor__(host) {
this.host = host
active_connections = active_connections + 1
print("Connected to " + host + " (active: " + str(active_connections) + ")")
}
fun __destructor__() {
active_connections = active_connections - 1
print("Disconnected from " + this.host + " (active: " + str(active_connections) + ")")
}
}
var c1 = Connection("server1") # Connected to server1 (active: 1)
var c2 = Connection("server2") # Connected to server2 (active: 2)
del c1 # Disconnected from server1 (active: 1)
del c2 # Disconnected from server2 (active: 0)
Arithmetic Operators
__add__
Handles the + operator.
add.rn class Vector {
fun __constructor__(x, y) {
this.x = x
this.y = y
}
fun __add__(other) {
return Vector(this.x + other.x, this.y + other.y)
}
fun to_string() {
return "Vector(" + str(this.x) + ", " + str(this.y) + ")"
}
}
var v1 = Vector(1, 2)
var v2 = Vector(3, 4)
var v3 = v1 + v2 # Calls v1.__add__(v2)
print(v3.to_string()) # Output: Vector(4, 6)
__sub__
Handles the - operator.
sub.rn class Vector {
fun __constructor__(x, y) {
this.x = x
this.y = y
}
fun __sub__(other) {
return Vector(this.x - other.x, this.y - other.y)
}
}
var v1 = Vector(5, 10)
var v2 = Vector(2, 3)
var v3 = v1 - v2 # Calls v1.__sub__(v2)
# v3 is Vector(3, 7)
__mul__
Handles the * operator.
mul.rn class Vector {
fun __constructor__(x, y) {
this.x = x
this.y = y
}
fun __mul__(scalar) {
return Vector(this.x * scalar, this.y * scalar)
}
}
var v = Vector(2, 3)
var scaled = v * 5 # Calls v.__mul__(5)
# scaled is Vector(10, 15)
__div__
Handles the / operator.
div.rn class Fraction {
fun __constructor__(num, den) {
this.num = num
this.den = den
}
fun __div__(other) {
# a/b ÷ c/d = a/b × d/c = (a×d)/(b×c)
return Fraction(this.num * other.den, this.den * other.num)
}
fun to_string() {
return str(this.num) + "/" + str(this.den)
}
}
var f1 = Fraction(1, 2)
var f2 = Fraction(3, 4)
var result = f1 / f2 # Calls f1.__div__(f2)
print(result.to_string()) # Output: 4/6
__pow__
Handles the ^ operator (exponentiation).
pow.rn class Number {
fun __constructor__(val) {
this.val = val
}
fun __pow__(exp) {
return Number(this.val ^ exp)
}
}
var n = Number(2)
var result = n ^ 10 # Calls n.__pow__(10)
# result.val is 1024
Comparison Operators
__eq__
Handles the == operator.
eq.rn class Point {
fun __constructor__(x, y) {
this.x = x
this.y = y
}
fun __eq__(other) {
return this.x == other.x and this.y == other.y
}
}
var p1 = Point(1, 2)
var p2 = Point(1, 2)
var p3 = Point(3, 4)
print(p1 == p2) # true (same coordinates)
print(p1 == p3) # false
__ne__
Handles the != operator.
ne.rn class Point {
fun __constructor__(x, y) {
this.x = x
this.y = y
}
fun __ne__(other) {
return this.x != other.x or this.y != other.y
}
}
var p1 = Point(1, 2)
var p2 = Point(3, 4)
print(p1 != p2) # true
Callable Objects
__call__
Makes an instance callable like a function.
call.rn class Multiplier {
fun __constructor__(factor) {
this.factor = factor
}
fun __call__(value) {
return value * this.factor
}
}
var double = Multiplier(2)
var triple = Multiplier(3)
print(double(5)) # 10 - calls double.__call__(5)
print(triple(5)) # 15 - calls triple.__call__(5)
Use case: Memoization / Caching
memoize.rn class Memoize {
fun __constructor__(func) {
this.func = func
this.cache = {}
}
fun __call__(arg) {
var key = str(arg)
if key in this.cache {
return this.cache[key]
}
var result = this.func(arg)
this.cache[key] = result
return result
}
}
fun expensive_calc(n) {
print("Computing for " + str(n))
return n * n
}
var cached_calc = Memoize(expensive_calc)
print(cached_calc(5)) # Computing for 5 → 25
print(cached_calc(5)) # 25 (cached, no "Computing" message)
print(cached_calc(10)) # Computing for 10 → 100
Subscript Access
__getitem__
Handles reading with obj[key].
getitem.rn class Matrix {
fun __constructor__(rows) {
this.rows = rows
}
fun __getitem__(index) {
return this.rows[index]
}
}
var m = Matrix([[1, 2], [3, 4], [5, 6]])
print(m[0]) # [1, 2]
print(m[1]) # [3, 4]
__setitem__
Handles assignment with obj[key] = value.
setitem.rn class DefaultDict {
fun __constructor__(default_value) {
this.data = {}
this.default = default_value
}
fun __getitem__(key) {
if key in this.data {
return this.data[key]
}
return this.default
}
fun __setitem__(key, value) {
this.data[key] = value
}
}
var counts = DefaultDict(0)
counts["apples"] = 5
counts["oranges"] = 3
print(counts["apples"]) # 5
print(counts["oranges"]) # 3
print(counts["bananas"]) # 0 (default)
Membership Testing
__contains__
Handles the in operator.
contains.rn class Range {
fun __constructor__(start, end) {
this.start = start
this.end = end
}
fun __contains__(value) {
return value >= this.start and value < this.end
}
}
var r = Range(1, 10)
print(5 in r) # true
print(10 in r) # false (end is exclusive)
print(0 in r) # false
Boolean Conversion
__truthy__
Defines how an object converts to boolean in conditions.
truthy.rn class Container {
fun __constructor__() {
this.items = []
}
fun add(item) {
this.items.append(item)
}
fun __truthy__() {
return this.items.length() > 0
}
}
var c = Container()
if c {
print("Has items")
} else {
print("Empty") # Output: Empty
}
c.add("apple")
if c {
print("Has items") # Output: Has items
} else {
print("Empty")
}
Truthy behavior
If __truthy__ throws an error, it is treated as false
If __truthy__ returns a non-boolean, its __truthy__ is called recursively
Complete Example
Here's a class that implements multiple magic methods:
money.rn class Money {
fun __constructor__(amount, currency) {
this.amount = amount
this.currency = currency
}
fun __add__(other) {
if this.currency != other.currency {
print("Currency mismatch!")
return null
}
return Money(this.amount + other.amount, this.currency)
}
fun __sub__(other) {
if this.currency != other.currency {
print("Currency mismatch!")
return null
}
return Money(this.amount - other.amount, this.currency)
}
fun __mul__(factor) {
return Money(this.amount * factor, this.currency)
}
fun __eq__(other) {
return this.amount == other.amount and this.currency == other.currency
}
fun __truthy__() {
return this.amount > 0
}
fun to_string() {
return this.currency + " " + str(this.amount)
}
}
var wallet = Money(100, "USD")
var expense = Money(30, "USD")
var remaining = wallet - expense
print(remaining.to_string()) # USD 70
if remaining {
print("Still have money!") # Output: Still have money!
}
var doubled = remaining * 2
print(doubled.to_string()) # USD 140
Quick Reference
Method
Operator/Usage
When Called
__constructor__
Foo(args)
Instance creation
__destructor__
del obj
Object deletion
__add__
a + b
Addition
__sub__
a - b
Subtraction
__mul__
a * b
Multiplication
__div__
a / b
Division
__pow__
a ^ b
Exponentiation
__eq__
a == b
Equality check
__ne__
a != b
Inequality check
__call__
obj(args)
Calling as function
__getitem__
obj[key]
Subscript read
__setitem__
obj[key] = val
Subscript write
__contains__
x in obj
Membership test
__truthy__
if obj
Boolean conversion