|
| 1 | +#🔐 Section 18: Property |
| 2 | +##Control Attribute Access in Python Classes |
| 3 | + |
| 4 | +🧩**Learn how to use properties in Python to control access to attributes**, including how to define getters, setters, and deleters using both the`property()` function and the`@property` decorator. |
| 5 | + |
| 6 | +This section covers: |
| 7 | +- 📦 Using`property()` to define property attributes |
| 8 | +- 🎀 Using`@property` for clean, readable syntax |
| 9 | +- 🧮 Defining read-only properties |
| 10 | +- 🗑️ Deleting a property with`@property.deleter` |
| 11 | +- 💡 Hidden notes and best practices for safe attribute management |
| 12 | + |
| 13 | + |
| 14 | + |
| 15 | +##🧠 What You'll Learn |
| 16 | + |
| 17 | +| Concept| Description| |
| 18 | +|--------|-------------| |
| 19 | +|**Property**| Encapsulate attribute access logic inside a class| |
| 20 | +|**Getter**| Define how an attribute is retrieved| |
| 21 | +|**Setter**| Define validation or behavior when assigning a value| |
| 22 | +|**Deleter**| Define what happens when an attribute is deleted| |
| 23 | +|**Read-only property**| Getter only – prevents modification| |
| 24 | +|**Computed property**| Value derived from other data, not stored directly| |
| 25 | + |
| 26 | + |
| 27 | +##🧱 The`property()` Function |
| 28 | + |
| 29 | +Use the`property()` function to define properties that encapsulate attribute access logic. |
| 30 | + |
| 31 | +🔹**Example – Define a Person class with age validation** |
| 32 | +```python |
| 33 | +classPerson: |
| 34 | +def__init__(self,name,age): |
| 35 | +self.name= name |
| 36 | +self._age= age |
| 37 | + |
| 38 | +defget_age(self): |
| 39 | +print("Getting age...") |
| 40 | +returnself._age |
| 41 | + |
| 42 | +defset_age(self,value): |
| 43 | +if value<0: |
| 44 | +raiseValueError("Age cannot be negative") |
| 45 | +self._age= value |
| 46 | + |
| 47 | +defdel_age(self): |
| 48 | +print("Deleting age") |
| 49 | +delself._age |
| 50 | + |
| 51 | + age=property(get_age, set_age, del_age,"The person's age") |
| 52 | + |
| 53 | +p= Person("Alice",30) |
| 54 | +print(p.age)# Calls get_age() |
| 55 | +p.age=25# Calls set_age() |
| 56 | +del p.age# Calls del_age() |
| 57 | +``` |
| 58 | + |
| 59 | +🔸 This allows you to add logic around attribute access without breaking backward compatibility. |
| 60 | + |
| 61 | + |
| 62 | + |
| 63 | +##🎀 The`@property` Decorator |
| 64 | + |
| 65 | +A cleaner and more modern way to define properties using decorators. |
| 66 | + |
| 67 | +🔹**Example – Using`@property` for encapsulation** |
| 68 | +```python |
| 69 | +classProduct: |
| 70 | +def__init__(self,name,price): |
| 71 | +self.name= name |
| 72 | +self._price= price |
| 73 | + |
| 74 | +@property |
| 75 | +defprice(self): |
| 76 | +print("Fetching price...") |
| 77 | +returnself._price |
| 78 | + |
| 79 | +@price.setter |
| 80 | +defprice(self,value): |
| 81 | +if value<0: |
| 82 | +raiseValueError("Price cannot be negative.") |
| 83 | +self._price= value |
| 84 | + |
| 85 | +@price.deleter |
| 86 | +defprice(self): |
| 87 | +print("Deleting price") |
| 88 | +delself._price |
| 89 | +``` |
| 90 | + |
| 91 | +🔹**Usage Example:** |
| 92 | +```python |
| 93 | +prod= Product("Laptop",999) |
| 94 | + |
| 95 | +print(prod.price)# Output: Fetching price... 999 |
| 96 | +prod.price=950# OK |
| 97 | +prod.price=-100# Raises ValueError |
| 98 | +``` |
| 99 | + |
| 100 | + |
| 101 | + |
| 102 | +##📝 Read-only Properties |
| 103 | + |
| 104 | +Define a property with only a getter to make it read-only. |
| 105 | + |
| 106 | +🔹**Example – Read-only ID** |
| 107 | +```python |
| 108 | +import uuid |
| 109 | + |
| 110 | +classUser: |
| 111 | +def__init__(self,username): |
| 112 | +self.username= username |
| 113 | +self._id= uuid.uuid4() |
| 114 | + |
| 115 | +@property |
| 116 | +defid(self): |
| 117 | +returnself._id |
| 118 | +``` |
| 119 | + |
| 120 | +🔹**Usage:** |
| 121 | +```python |
| 122 | +user= User("john_doe") |
| 123 | +print(user.id)# Works fine |
| 124 | +user.id=100# ❌ Raises AttributeError |
| 125 | +``` |
| 126 | + |
| 127 | +🔸 Even though`_id` can still be changed (`user._id = 100`), this discourages accidental modification. |
| 128 | + |
| 129 | + |
| 130 | + |
| 131 | +##🧮 Computed (Dynamic) Properties |
| 132 | + |
| 133 | +Create properties that are computed on-the-fly, rather than stored as instance variables. |
| 134 | + |
| 135 | +🔹**Example – Compute full name dynamically** |
| 136 | +```python |
| 137 | +classEmployee: |
| 138 | +def__init__(self,first_name,last_name): |
| 139 | +self.first_name= first_name |
| 140 | +self.last_name= last_name |
| 141 | + |
| 142 | +@property |
| 143 | +deffull_name(self): |
| 144 | +returnf"{self.first_name}{self.last_name}" |
| 145 | +``` |
| 146 | + |
| 147 | +🔹**Usage:** |
| 148 | +```python |
| 149 | +emp= Employee("John","Doe") |
| 150 | +print(emp.full_name)# John Doe |
| 151 | +emp.first_name="Jane" |
| 152 | +print(emp.full_name)# Jane Doe |
| 153 | +``` |
| 154 | + |
| 155 | +🔸 The`full_name` property doesn't store any value — it’s always up-to-date based on current values. |
| 156 | + |
| 157 | + |
| 158 | + |
| 159 | +##🗑️ Delete a Property |
| 160 | + |
| 161 | +Use`@property.deleter` to define behavior when deleting an attribute. |
| 162 | + |
| 163 | +🔹**Example – Controlled deletion of a property** |
| 164 | +```python |
| 165 | +classAccount: |
| 166 | +def__init__(self,account_number,balance=0): |
| 167 | +self.account_number= account_number |
| 168 | +self.balance= balance |
| 169 | +self._active=True |
| 170 | + |
| 171 | +@property |
| 172 | +defactive(self): |
| 173 | +returnself._active |
| 174 | + |
| 175 | +@active.setter |
| 176 | +defactive(self,value): |
| 177 | +ifnotisinstance(value,bool): |
| 178 | +raiseValueError("Value must be boolean") |
| 179 | +self._active= value |
| 180 | + |
| 181 | +@active.deleter |
| 182 | +defactive(self): |
| 183 | +print("Archiving account...") |
| 184 | +self._active=False |
| 185 | +``` |
| 186 | + |
| 187 | +🔹**Usage:** |
| 188 | +```python |
| 189 | +acc= Account("A1234",1000) |
| 190 | +del acc.active |
| 191 | +print(acc.active)# Now False |
| 192 | +``` |
| 193 | + |
| 194 | + |
| 195 | + |
| 196 | +##🧪 Real-World Example – Temperature Sensor Class |
| 197 | + |
| 198 | +Let’s build a class that uses properties to validate and compute values. |
| 199 | + |
| 200 | +```python |
| 201 | +classTemperatureSensor: |
| 202 | +def__init__(self,sensor_id,initial_temp): |
| 203 | +self.sensor_id= sensor_id |
| 204 | +self._temp= initial_temp |
| 205 | + |
| 206 | +@property |
| 207 | +deftemperature(self): |
| 208 | +print(f"[{self.sensor_id}] Getting temperature:{self._temp}°C") |
| 209 | +returnself._temp |
| 210 | + |
| 211 | +@temperature.setter |
| 212 | +deftemperature(self,value): |
| 213 | +print(f"[{self.sensor_id}] Setting temperature to{value}°C") |
| 214 | +if value<-273.15: |
| 215 | +raiseValueError("Temperature below absolute zero is invalid") |
| 216 | +self._temp= value |
| 217 | + |
| 218 | +@property |
| 219 | +defstatus(self): |
| 220 | +ifself._temp>100: |
| 221 | +return"Overheating" |
| 222 | +elifself._temp<0: |
| 223 | +return"Freezing" |
| 224 | +else: |
| 225 | +return"Normal" |
| 226 | +``` |
| 227 | + |
| 228 | +🔹**Usage:** |
| 229 | +```python |
| 230 | +sensor= TemperatureSensor("T1",25) |
| 231 | +sensor.temperature=80 |
| 232 | +sensor.temperature=120# Triggers overheating warning |
| 233 | +sensor.temperature=-10# Sets freezing status |
| 234 | +``` |
| 235 | + |
| 236 | + |
| 237 | +##💡 Hidden Tips & Notes |
| 238 | + |
| 239 | +- 🧩 Always prefix internal attributes with`_` when using properties — it signals they’re protected. |
| 240 | +- 🧱 Avoid side effects in property getters — keep them lightweight. |
| 241 | +- 📏 Use properties to enforce constraints like minimum/maximum values. |
| 242 | +- 🧾 If you want to document your property, include a docstring inside the getter. |
| 243 | +- 🚫 Be cautious with deleters — they may cause undefined state if not handled carefully. |
| 244 | +- 🧵 Prefer`@property` over`property()` for better readability. |
| 245 | +- 🧰 Use computed properties to avoid storing redundant data. |
| 246 | + |
| 247 | + |
| 248 | + |
| 249 | +##📌 Summary |
| 250 | + |
| 251 | +| Feature| Purpose| |
| 252 | +|--------|---------| |
| 253 | +|`property()`| Legacy-style way to create properties| |
| 254 | +|`@property`| Modern, decorator-based property definition| |
| 255 | +|`@property.setter`| Define validation logic when setting a value| |
| 256 | +|`@property.deleter`| Define behavior when deleting a property| |
| 257 | +| Read-only| Only a getter — prevents assignment| |
| 258 | +| Computed property| Not stored; calculated from other data| |
| 259 | + |
| 260 | + |
| 261 | + |
| 262 | +🎉 Congratulations! You now understand how to**control attribute access using properties**, including how to: |
| 263 | +- Validate input values |
| 264 | +- Make attributes read-only |
| 265 | +- Compute dynamic values |
| 266 | +- Define custom behaviors on deletion |
| 267 | + |
| 268 | +Next up: 🔄**Section 19: Inheritance** – learn how to extend classes and reuse code through inheritance. |
| 269 | + |