π·οΈ @property
@property allows us to access a method like an attribute, without using ().
It is commonly used for encapsulation and controlled access to instance variables.
πΉ Without @property
class Student:
def __init__(self, name):
self.name = name
def get_name(self):
return self.name
student = Student("Codeveda")
print(student.get_name()) #CodevedaWe have to use () because get_name() is a method.
πΉ With @property
class Student:
def __init__(self, name):
self.name = name
@property
def name_upper(self):
return self.name.upper()
student = Student("Codeveda")
print(student.name_upper) #CodevedaNotice: student.name_upper instead of: student.name_upper()
The method behaves like an attribute.
π Property with Getter & Setter
@property can also control how an attribute is read and modified.
class Student:
def __init__(self, age):
self._age = age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value >= 0:
self._age = value
student = Student(20)
print(student.age) #20
student.age = 21
print(student.age) #21Here: @property is for Get/read the value and @age.setter is for Set/change the value
Use
@propertywhen you want attribute-like access while still having control over how the value is calculated or validated.
<div class="infographic-pyproperty">
<style>
.infographic-pyproperty {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--err: #c0392b; --err-soft: #fbe9e7;
--ok: #1f9d55; --ok-soft: #e3f5ec;
--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: 740px;
margin: 0 auto;
font-family: var(--body-font);
color: var(--ink);
background: var(--paper);
}
.infographic-pyproperty[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--err: #e57373; --err-soft: #3a2222;
--ok: #6fdc8c; --ok-soft: #16342a;
}
.infographic-pyproperty *, .infographic-pyproperty *::before, .infographic-pyproperty *::after { box-sizing: border-box; }
.infographic-pyproperty .pr-wrap { padding: 0 clamp(1rem, 4vw, 2rem) 1.5rem; }
.infographic-pyproperty h1 {
font-family: var(--display-font); font-weight: 600;
font-size: clamp(1.4rem, 5vw, 1.9rem); text-align: center; margin: 1.5rem 0 0.25rem; letter-spacing: -0.01em;
}
.infographic-pyproperty .pr-sub { text-align: center; color: var(--ink-soft); font-size: 0.85rem; margin: 0 0 1.3rem; }
.infographic-pyproperty .pr-tabs { display: flex; justify-content: center; gap: 0.35rem; margin-bottom: 1.4rem; border-bottom: 1px solid var(--line); flex-wrap: wrap; }
.infographic-pyproperty .pr-tab-btn {
font: inherit; font-family: var(--mono-font); font-size: 0.78rem; font-weight: 700;
background: none; border: none; border-bottom: 2.5px solid transparent; color: var(--ink-soft);
padding: 0.55rem 0.75rem; cursor: pointer; margin-bottom: -1px;
}
.infographic-pyproperty .pr-tab-btn.is-active { color: var(--accent); border-bottom-color: var(--accent); }
.infographic-pyproperty .pr-panel.is-hidden-tab { display: none; }
.infographic-pyproperty .code-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 0.7rem; margin-bottom: 1rem; }
@media (max-width: 560px) { .infographic-pyproperty .code-cols { grid-template-columns: 1fr; } }
.infographic-pyproperty .code-block {
font-family: var(--mono-font); font-size: 0.72rem; line-height: 1.6; white-space: pre;
background: var(--paper-alt); border: 1px solid var(--line); border-radius: 8px;
padding: 0.7rem 0.8rem; margin-bottom: 1.1rem;
}
.infographic-pyproperty .code-block .kw { color: var(--accent); font-weight: 700; }
.infographic-pyproperty .code-block .deco { color: var(--ok); font-weight: 700; }
.infographic-pyproperty .col-label { text-align: center; font-family: var(--mono-font); font-size: 0.7rem; font-weight: 700; color: var(--ink-soft); margin-bottom: 0.4rem; }
.infographic-pyproperty .code-cols .code-block { margin-bottom: 0; }
.infographic-pyproperty .row { display: flex; justify-content: center; gap: 0.5rem; margin-bottom: 0.8rem; flex-wrap: wrap; align-items: center; }
.infographic-pyproperty .row input {
font-family: var(--mono-font); font-size: 0.82rem; padding: 0.4rem 0.6rem;
border: 1.5px solid var(--line); border-radius: 5px; background: var(--paper-alt); color: var(--ink); width: 110px;
text-align: center;
}
.infographic-pyproperty .row input:focus { outline: none; border-color: var(--accent); }
.infographic-pyproperty .btn {
font: inherit; font-family: var(--mono-font); font-size: 0.78rem; font-weight: 700;
background: var(--accent); color: #fff; border: none; border-radius: 999px;
padding: 0.42rem 0.85rem; cursor: pointer; min-width: 230px; text-align: center;
}
.infographic-pyproperty .try-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; margin-bottom: 1rem; }
@media (max-width: 560px) { .infographic-pyproperty .try-cols { grid-template-columns: 1fr; } }
.infographic-pyproperty .try-col { display: flex; flex-direction: column; gap: 0.35rem; }
.infographic-pyproperty .try-btn {
font: inherit; font-family: var(--mono-font); font-size: 0.75rem; font-weight: 700; text-align: left;
background: var(--paper-alt); border: 1.5px solid var(--line); color: var(--ink);
padding: 0.4rem 0.6rem; cursor: pointer; border-radius: 6px;
}
.infographic-pyproperty .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-pyproperty .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-pyproperty .device-bar .dot { width: 9px; height: 9px; border-radius: 50%; background: #5a5a5a; }
.infographic-pyproperty .device-bar .dot:nth-child(1) { background: #ff5f56; }
.infographic-pyproperty .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-pyproperty .log-line { display: flex; gap: 0.5rem; line-height: 1.7; opacity: 0; animation: pr-fadein 0.25s ease forwards; flex-wrap: wrap; }
@keyframes pr-fadein { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: translateY(0); } }
.infographic-pyproperty .log-line .p { color: var(--ed-ok); font-weight: 700; }
.infographic-pyproperty .log-line.err .out { color: var(--ed-err); font-weight: 700; }
.infographic-pyproperty .log-line.ok .out { color: var(--ed-ok); font-weight: 700; }
.infographic-pyproperty .log-line .out { color: #fff; }
.infographic-pyproperty .log-empty { color: var(--ed-soft); }
.infographic-pyproperty .pr-caption { text-align: center; font-size: 0.85rem; color: var(--ink-soft); margin-top: 0.9rem; }
.infographic-pyproperty .pr-caption strong { color: var(--ink); font-family: var(--mono-font); }
@media (prefers-reduced-motion: reduce) {
.infographic-pyproperty *, .infographic-pyproperty *::before, .infographic-pyproperty *::after {
animation-duration: 0.001ms !important; transition-duration: 0.001ms !important;
}
}
</style>
<div class="pr-wrap">
<h1>@property</h1>
<p class="pr-sub">Access a method like an attribute β no parentheses needed.</p>
<div class="pr-tabs">
<button class="pr-tab-btn is-active" data-tab="compare">Without vs With</button>
<button class="pr-tab-btn" data-tab="setter">Getter & Setter</button>
</div>
<!-- WITHOUT VS WITH -->
<div class="pr-panel" id="tabCompare">
<div class="code-cols">
<div>
<div class="col-label">without @property</div>
<div class="code-block"><span class="kw">class</span> Student:
<span class="kw">def</span> __init__(self, name):
self.name = name
<span class="kw">def</span> get_name(self):
<span class="kw">return</span> self.name</div>
</div>
<div>
<div class="col-label">with @property</div>
<div class="code-block"><span class="kw">class</span> Student:
<span class="kw">def</span> __init__(self, name):
self.name = name
<span class="deco">@property</span>
<span class="kw">def</span> name_upper(self):
<span class="kw">return</span> self.name.upper()</div>
</div>
</div>
<div class="row">
<input id="nameInput" type="text" value="Tisha" placeholder="name" />
<button class="btn" id="createBtn">Create: student = Student("Tisha")</button>
</div>
<div class="try-cols">
<div class="try-col">
<button class="try-btn" data-t="get_name">student.get_name</button>
<button class="try-btn" data-t="get_name()">student.get_name()</button>
</div>
<div class="try-col">
<button class="try-btn" data-t="name_upper">student.name_upper</button>
<button class="try-btn" data-t="name_upper()">student.name_upper()</button>
</div>
</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="compareLog"><div class="log-empty">Create a student, then try each access above.</div></div>
</div>
<p class="pr-caption">get_name needs () because it's a method. name_upper looks like a plain attribute β @property is what makes that legal.</p>
</div>
<!-- GETTER & SETTER -->
<div class="pr-panel is-hidden-tab" id="tabSetter">
<div class="code-block"><span class="kw">class</span> Student:
<span class="kw">def</span> __init__(self, age):
self._age = age
<span class="deco">@property</span>
<span class="kw">def</span> age(self):
<span class="kw">return</span> self._age
<span class="deco">@age.setter</span>
<span class="kw">def</span> age(self, value):
<span class="kw">if</span> value >= 0:
self._age = value</div>
<div class="row">
<input id="ageStartInput" type="text" value="20" placeholder="starting age" />
<button class="btn" id="ageCreateBtn">Create: student = Student(20)</button>
</div>
<div class="row">
<button class="btn" id="readAgeBtn">Run: student.age</button>
</div>
<div class="row">
<input id="ageSetInput" type="text" value="21" placeholder="new age" />
<button class="btn" id="setAgeBtn">Run: student.age = value</button>
</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="setterLog"><div class="log-empty">Create a student, then read and set their age.</div></div>
</div>
<p class="pr-caption">Try setting a negative age β the setter quietly refuses, and the value stays exactly what it was.</p>
</div>
</div>
<script>
(function () {
var root = document.currentScript.closest('.infographic-pyproperty')
|| document.querySelector('.infographic-pyproperty');
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');
/* ---------------- tabs ---------------- */
var tabBtns = root.querySelectorAll('.pr-tab-btn');
var panels = { compare: root.querySelector('#tabCompare'), setter: root.querySelector('#tabSetter') };
tabBtns.forEach(function (btn) {
btn.addEventListener('click', function () {
tabBtns.forEach(function (b) { b.classList.toggle('is-active', b === btn); });
var target = btn.dataset.tab;
Object.keys(panels).forEach(function (key) {
panels[key].classList.toggle('is-hidden-tab', key !== target);
});
});
});
/* ---------------- WITHOUT VS WITH ---------------- */
var nameInput = root.querySelector('#nameInput');
var createBtn = root.querySelector('#createBtn');
var compareLog = root.querySelector('#compareLog');
var tryBtns = root.querySelectorAll('#tabCompare .try-btn');
var firstCompareLog = true;
var student = null;
function logCompare(text, cls) {
if (firstCompareLog) { compareLog.innerHTML = ''; firstCompareLog = false; }
var line = document.createElement('div');
line.className = 'log-line' + (cls ? ' ' + cls : '');
line.innerHTML = '<span class="p">>>></span><span class="out">' + text + '</span>';
compareLog.appendChild(line);
compareLog.scrollTop = compareLog.scrollHeight;
}
createBtn.addEventListener('click', function () {
var name = (nameInput.value || '').trim() || 'Tisha';
student = { name: name };
logCompare('student = Student("' + name + '")', 'ok');
});
tryBtns.forEach(function (btn) {
btn.addEventListener('click', function () {
if (!student) { logCompare('NameError: name \'student\' is not defined', 'err'); return; }
var t = btn.dataset.t;
logCompare('student.' + t);
if (t === 'get_name') {
logCompare('β <bound method Student.get_name of ...> (the method itself, not called)', 'err');
} else if (t === 'get_name()') {
logCompare('β "' + student.name + '"', 'ok');
} else if (t === 'name_upper') {
logCompare('β "' + student.name.toUpperCase() + '"', 'ok');
} else if (t === 'name_upper()') {
logCompare('β TypeError: \'str\' object is not callable', 'err');
}
});
});
/* ---------------- GETTER & SETTER ---------------- */
var ageStartInput = root.querySelector('#ageStartInput');
var ageCreateBtn = root.querySelector('#ageCreateBtn');
var readAgeBtn = root.querySelector('#readAgeBtn');
var ageSetInput = root.querySelector('#ageSetInput');
var setAgeBtn = root.querySelector('#setAgeBtn');
var setterLog = root.querySelector('#setterLog');
var firstSetterLog = true;
var age = null;
function logSetter(text, cls) {
if (firstSetterLog) { setterLog.innerHTML = ''; firstSetterLog = false; }
var line = document.createElement('div');
line.className = 'log-line' + (cls ? ' ' + cls : '');
line.innerHTML = '<span class="p">>>></span><span class="out">' + text + '</span>';
setterLog.appendChild(line);
setterLog.scrollTop = setterLog.scrollHeight;
}
ageCreateBtn.addEventListener('click', function () {
var a = parseFloat(ageStartInput.value);
if (isNaN(a)) a = 20;
age = a;
logSetter('student = Student(' + a + ')', 'ok');
});
readAgeBtn.addEventListener('click', function () {
if (age === null) { logSetter('NameError: name \'student\' is not defined', 'err'); return; }
logSetter('student.age');
logSetter('β ' + age, 'ok');
});
setAgeBtn.addEventListener('click', function () {
if (age === null) { logSetter('NameError: name \'student\' is not defined', 'err'); return; }
var v = parseFloat(ageSetInput.value);
if (isNaN(v)) return;
logSetter('student.age = ' + v);
if (v >= 0) {
age = v;
logSetter('β accepted β student.age is now ' + age, 'ok');
} else {
logSetter('β rejected silently β student.age is still ' + age + ' (the setter just does nothing)', 'err');
}
});
})();
</script>
</div>