class Box:
def __init__(self, items):
self.items = list(items)
def __str__(self):
return f"Box with {len(self.items)} items"
def __repr__(self):
return f"Box({self.items!r})"
def __len__(self):
return len(self.items)
def __add__(self, other):
return Box(self.items + other.items)
def __eq__(self, other):
return self.items == other.items
def __getitem__(self, i):
return self.items[i]
def __setitem__(self, i, v):
self.items[i] = v
def __delitem__(self, i):
del self.items[i]
def __contains__(self, x):
return x in self.items
def __call__(self):
return f"called! first item: {self.items[0]}"
def __iter__(self):
return iter(self.items)
def __bool__(self):
return len(self.items) > 0
def __lt__(self, other):
return len(self.items) < len(other.items)
def __del__(self):
print("Box destroyed")π§ What you're demonstrating
This is actually a great example for your Magic/Dunder Methods chapter:
Code you write | Python automatically calls |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Object removed |
|
<div class="infographic-pydunder">
<style>
.infographic-pydunder {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--mono-font: 'IBM Plex Mono', 'SFMono-Regular', Menlo, monospace;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
box-sizing: border-box;
max-width: 800px;
margin: 0 auto;
font-family: var(--body-font);
color: var(--ink);
background: var(--paper);
}
.infographic-pydunder[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.infographic-pydunder *, .infographic-pydunder *::before, .infographic-pydunder *::after { box-sizing: border-box; }
.infographic-pydunder .dn-wrap { padding: 0 clamp(1rem, 4vw, 2rem) 1.5rem; }
.infographic-pydunder h1 {
font-family: var(--display-font); font-weight: 600;
font-size: clamp(1.45rem, 5vw, 1.95rem); text-align: center; margin: 1.5rem 0 0.25rem; letter-spacing: -0.01em;
}
.infographic-pydunder .dn-sub { text-align: center; color: var(--ink-soft); font-size: 0.85rem; margin: 0 0 1.3rem; }
.infographic-pydunder .setup-row { display: flex; justify-content: center; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
.infographic-pydunder .field-group { display: flex; align-items: center; gap: 0.4rem; }
.infographic-pydunder .setup-row span { font-family: var(--mono-font); font-size: 0.75rem; color: var(--ink-soft); }
.infographic-pydunder .setup-row input {
font-family: var(--mono-font); font-size: 0.8rem; padding: 0.35rem 0.55rem;
border: 1.5px solid var(--line); border-radius: 5px; background: var(--paper-alt); color: var(--ink); width: 130px;
text-align: center;
}
.infographic-pydunder .setup-row input:focus { outline: none; border-color: var(--accent); }
.infographic-pydunder .param-row { text-align: center; margin-bottom: 1rem; }
.infographic-pydunder .method-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.4rem; margin-bottom: 1.1rem; }
@media (max-width: 560px) { .infographic-pydunder .method-grid { grid-template-columns: 1fr; } }
.infographic-pydunder .method-btn {
font: inherit; text-align: left; background: var(--paper-alt); border: 1.5px solid var(--line);
border-radius: 6px; padding: 0.45rem 0.6rem; cursor: pointer; color: var(--ink);
}
.infographic-pydunder .method-btn.is-selected { border-color: var(--accent); background: var(--accent-soft); }
.infographic-pydunder .method-name { font-family: var(--mono-font); font-size: 0.8rem; font-weight: 700; color: var(--accent); }
.infographic-pydunder .method-desc { font-size: 0.7rem; color: var(--ink-soft); }
.infographic-pydunder .method-trigger { font-family: var(--mono-font); font-size: 0.68rem; color: var(--ink-soft); margin-top: 0.1rem; }
.infographic-pydunder .device {
--ed-bg: #1e1e1e; --ed-bar: #2d2d2d; --ed-text: #d4d4d4; --ed-soft: #8a8a8a; --ed-ok: #6fdc8c; --ed-err: #ff6b6b;
background: var(--ed-bg); border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
}
.infographic-pydunder .device-bar {
display: flex; align-items: center; gap: 0.4rem;
background: var(--ed-bar); padding: 0.5rem 0.7rem; border-bottom: 1px solid #3a3a3a;
font-size: 0.72rem; color: var(--ed-soft); font-weight: 600;
}
.infographic-pydunder .device-bar .dot { width: 9px; height: 9px; border-radius: 50%; background: #5a5a5a; }
.infographic-pydunder .device-bar .dot:nth-child(1) { background: #ff5f56; }
.infographic-pydunder .device-body { padding: 0.75rem 0.9rem; font-family: var(--mono-font); font-size: 0.78rem; color: var(--ed-text); min-height: 60px; max-height: 190px; overflow-y: auto; }
.infographic-pydunder .log-line { display: flex; gap: 0.5rem; line-height: 1.7; opacity: 0; animation: dn-fadein 0.2s ease forwards; flex-wrap: wrap; }
@keyframes dn-fadein { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: translateY(0); } }
.infographic-pydunder .log-line .p { color: var(--ed-ok); font-weight: 700; }
.infographic-pydunder .log-line .out { color: #fff; }
.infographic-pydunder .log-empty { color: var(--ed-soft); }
@media (prefers-reduced-motion: reduce) {
.infographic-pydunder *, .infographic-pydunder *::before, .infographic-pydunder *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="dn-wrap">
<div class="setup-row">
<span class="field-group"><span>box1 =</span><input id="box1Input" type="text" value="1, 2, 3" /></span>
<span class="field-group"><span>box2 =</span><input id="box2Input" type="text" value="4, 5" /></span>
</div>
<div class="setup-row param-row">
<span class="field-group"><span>index/value for getitem, setitem, delitem, contains:</span><input id="paramInput" type="text" value="1" style="width:70px" /></span>
</div>
<div class="method-grid" id="methodGrid"></div>
<div class="device">
<div class="device-bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span> Terminal</div>
<div class="device-body" id="dnLog"><div class="log-empty">Tap a method above to run it against box1.</div></div>
</div>
</div>
<script>
(function () {
var root = document.currentScript.closest('.infographic-pydunder')
|| document.querySelector('.infographic-pydunder');
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) root.setAttribute('data-theme', 'dark');
var box1Input = root.querySelector('#box1Input');
var box2Input = root.querySelector('#box2Input');
var paramInput = root.querySelector('#paramInput');
var methodGrid = root.querySelector('#methodGrid');
var dnLog = root.querySelector('#dnLog');
var firstLog = true;
function parseBox(raw) {
return raw.split(',').map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
}
function pyRepr(arr) {
return '[' + arr.map(function (v) {
return /^-?\d+(\.\d+)?$/.test(v) ? v : "'" + v + "'";
}).join(', ') + ']';
}
function logLine(text, cls) {
if (firstLog) { dnLog.innerHTML = ''; firstLog = false; }
var line = document.createElement('div');
line.className = 'log-line' + (cls ? ' ' + cls : '');
line.innerHTML = '<span class="p">>>></span><span class="out">' + text + '</span>';
dnLog.appendChild(line);
dnLog.scrollTop = dnLog.scrollHeight;
}
var methods = [
{ name: '__init__', desc: 'Constructor', trigger: 'obj = Class()',
run: function (b1, b2, p) { logLine('box1 = Box(' + pyRepr(b1) + ')', 'ok'); } },
{ name: '__str__', desc: 'User-friendly string', trigger: 'print(obj)',
run: function (b1) { logLine('print(box1)'); logLine('β "Box with ' + b1.length + ' items"'); } },
{ name: '__repr__', desc: 'Debug-friendly string', trigger: 'repr(obj)',
run: function (b1) { logLine('repr(box1)'); logLine('β "Box(' + pyRepr(b1) + ')"'); } },
{ name: '__len__', desc: 'Custom length', trigger: 'len(obj)',
run: function (b1) { logLine('len(box1)'); logLine('β ' + b1.length); } },
{ name: '__add__', desc: 'Addition', trigger: 'obj1 + obj2',
run: function (b1, b2) { logLine('box1 + box2'); logLine('β Box(' + pyRepr(b1.concat(b2)) + ')'); } },
{ name: '__eq__', desc: 'Equality check', trigger: 'obj1 == obj2',
run: function (b1, b2) { var eq = JSON.stringify(b1) === JSON.stringify(b2); logLine('box1 == box2'); logLine('β ' + (eq ? 'True' : 'False')); } },
{ name: '__getitem__', desc: 'Index access', trigger: 'obj[0]',
run: function (b1, b2, p) { var i = parseInt(p, 10) || 0; logLine('box1[' + i + ']'); if (i < 0 || i >= b1.length) logLine('β IndexError: list index out of range', 'err'); else logLine('β ' + b1[i]); } },
{ name: '__setitem__', desc: 'Item assignment', trigger: 'obj[1] = "value"',
run: function (b1, b2, p) { logLine('box1[0] = "' + p + '"'); logLine('β item 0 is now "' + p + '"'); } },
{ name: '__delitem__', desc: 'Item deletion', trigger: 'del obj[1]',
run: function (b1, b2, p) { var i = parseInt(p, 10) || 0; if (i < 0 || i >= b1.length) { logLine('del box1[' + i + ']'); logLine('β IndexError: list assignment index out of range', 'err'); } else { logLine('del box1[' + i + ']'); logLine('β removes "' + b1[i] + '" β box1 is now ' + pyRepr(b1.slice(0, i).concat(b1.slice(i + 1)))); } } },
{ name: '__contains__', desc: 'Membership test', trigger: 'x in obj',
run: function (b1, b2, p) { var found = b1.indexOf(p) !== -1; logLine('"' + p + '" in box1'); logLine('β ' + (found ? 'True' : 'False')); } },
{ name: '__call__', desc: 'Callable objects', trigger: 'obj()',
run: function (b1) { logLine('box1()'); if (b1.length === 0) logLine('β IndexError: list index out of range', 'err'); else logLine('β "called! first item: ' + b1[0] + '"'); } },
{ name: '__iter__, __next__', desc: 'Iteration', trigger: 'for x in obj',
run: function (b1) { logLine('for x in box1: print(x)'); b1.forEach(function (v) { logLine(v); }); } },
{ name: '__bool__', desc: 'Boolean evaluation', trigger: 'bool(obj)',
run: function (b1) { logLine('bool(box1)'); logLine('β ' + (b1.length > 0 ? 'True' : 'False')); } },
{ name: '__lt__, __gt__, __le__, __ge__', desc: 'Comparisons', trigger: '<, >, <=, >=',
run: function (b1, b2) { logLine('box1 < box2'); logLine('β ' + (b1.length < b2.length ? 'True' : 'False') + ' (compares by number of items)'); } },
{ name: '__del__', desc: 'Destructor', trigger: 'del obj',
run: function (b1) { logLine('del box1'); logLine('β Python calls __del__ right before the object is actually cleaned up'); } }
];
methods.forEach(function (m) {
var btn = document.createElement('button');
btn.className = 'method-btn';
btn.innerHTML =
'<div class="method-name">' + m.name + '</div>' +
'<div class="method-desc">' + m.desc + '</div>' +
'<div class="method-trigger">' + m.trigger + '</div>';
btn.addEventListener('click', function () {
root.querySelectorAll('.method-btn').forEach(function (b) { b.classList.remove('is-selected'); });
btn.classList.add('is-selected');
var b1 = parseBox(box1Input.value);
var b2 = parseBox(box2Input.value);
var p = paramInput.value.trim();
m.run(b1, b2, p);
});
methodGrid.appendChild(btn);
});
})();
</script>
</div>