Object-Oriented Basics
Encapsulation
Controlled Access
Your BankAccount class has a balance. If code can set account.balance = -1000
directly, bugs happen. Encapsulation hides internal data and provides controlled
access through methods and properties - protecting your data from misuse.
Public vs private conventions
Python uses naming conventions for access control.
# Public vs Private Attributes
print("=== Public Attributes ===\n")
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
buddy = Dog("Buddy", 3)
# Direct access - anyone can read/write
print(f"Name: {buddy.name}")
buddy.age = 4 # Changed directly
print(f"Age: {buddy.age}")
# Problem: No validation!
buddy.age = -5 # Oops! Invalid value
print(f"Invalid age: {buddy.age}")
print("\n=== Protected Attributes (Convention) ===")
class Cat:
def __init__(self, name, age):
self._name = name
self._age = age
def get_info(self):
return f"{self._name} is {self._age} years old"
whiskers = Cat("Whiskers", 5)
print(whiskers.get_info())
# Still accessible, but indicates "internal use"
print(f"Direct access: {whiskers._name}") # Works, but discouraged
whiskers._age = 6 # Works, but breaks convention
print("\n=== Private Attributes (Name Mangling) ===")
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def get_balance(self):
return self.__balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
return True
return False
account = BankAccount(1000)
print(f"Balance: {account.get_balance()}")
# Can't access directly
# print(account.__balance) # AttributeError!
# Name mangling - still accessible but harder
print(f"Mangled name: {account._BankAccount__balance}") # Works!
print("\n=== Why Use Privacy? ===")
class Counter:
def __init__(self):
self.__count = 0
self.__min = 0
self.__max = 100
def increment(self):
if self.__count < self.__max:
self.__count += 1
def decrement(self):
if self.__count > self.__min:
self.__count -= 1
def value(self):
return self.__count
counter = Counter()
counter.increment()
counter.increment()
counter.increment()
print(f"Count: {counter.value()}")
# Can't bypass validation
# counter.__count = 999 # Creates new attribute, not the real one!
counter.increment()
print(f"Count: {counter.value()}") # Still 4, not 999
print("\n=== Access Summary ===")
class Example:
def __init__(self):
self.public = "Anyone can access"
self._protected = "Convention: internal"
self.__private = "Name mangled"
obj = Example()
print(f"public: {obj.public}")
print(f"_protected: {obj._protected}") # Accessible
# print(f"__private: {obj.__private}") # Error!
print(f"_Example__private: {obj._Example__private}") # Name mangled
print("=== Public Attributes === ")
3print("=== Public Attributes ===\n")45class Dog:6 def __init__(self, name, age):7 self.name = name #?public8 self.age = age910buddy = Dog("Buddy", 3)output=== Public Attributes ===self.name ← Buddy, self.age ← 3
5class Dog:6 def __init__(self⟨Dog A⟩, nameBuddy, age3):7 self.name→ Buddy = nameBuddy #?public8 self.age→ 3 = age3buddy ← ⟨Dog A⟩, buddy.age ← 4
10buddy→ ⟨Dog A⟩ = Dog("Buddy", 3)1112# Direct access - anyone can read/write #?directaccess13print(f"Name: {buddy.nameBuddy}")14buddy.age→ 4 = 4 # Changed directly15print(f"Age: {buddy.age4}")1617# Problem: No validation! #?novalid18buddy.age→ -5 = -5 # Oops! Invalid value19print(f"Invalid age: {buddy.age-5}")2021print("\n=== Protected Attributes (Convention) ===")2223class Cat:24 def __init__(self, name, age):25 self._name = name #?protected26 self._age = age27 28 def get_info(self):29 return f"{self._name} is {self._age} years old"3031whiskers = Cat("Whiskers", 5)32print(whiskers.get_info())outputName: Buddy Age: 4 Invalid age: -5 === Protected Attributes (Convention) ===self._name ← Whiskers, self._age ← 5
23class Cat:24 def __init__(self⟨Cat B⟩, nameWhiskers, age5):25 self._name→ Whiskers = nameWhiskers #?protected26 self._age→ 5 = age5whiskers ← ⟨Cat B⟩
31whiskers→ ⟨Cat B⟩ = Cat("Whiskers", 5)32print(whiskers⟨Cat B⟩.get_info())def get_info(self):
28def get_info(self⟨Cat B⟩):29 return f"{self._nameWhiskers} is {self._age5} years old"whiskers._age ← 6
31whiskers = Cat("Whiskers", 5)32print(whiskers⟨Cat B⟩.get_info())3334# Still accessible, but indicates "internal use" #?stillaccessible35print(f"Direct access: {whiskers._nameWhiskers}") # Works, but discouraged36whiskers._age→ 6 = 6 # Works, but breaks convention3738print("\n=== Private Attributes (Name Mangling) ===")3940class BankAccount:41 def __init__(self, balance):42 self.__balance = balance #?private43 44 def get_balance(self): #?getter45 return self.__balance46 47 def deposit(self, amount): #?deposit48 if amount > 0:49 self.__balance += amount50 return True51 return False5253account = BankAccount(1000)54print(f"Balance: {account.get_balance()}")outputWhiskers is 5 years old Direct access: Whiskers === Private Attributes (Name Mangling) ===self.__balance ← 1000
40class BankAccount:41 def __init__(self⟨BankAccount C⟩, balance1000):42 self.__balance→ 1000 = balance1000 #?privateaccount ← ⟨BankAccount C⟩
53account→ ⟨BankAccount C⟩ = BankAccount(1000)54print(f"Balance: {account⟨BankAccount C⟩.get_balance()}")def get_balance(self): #?getter
44def get_balance(self⟨BankAccount C⟩): #?getter45 return self.__balance1000print(f"Balance: {account.get_balance()}")
53account = BankAccount(1000)54print(f"Balance: {account⟨BankAccount C⟩.get_balance()}")5556# Can't access directly #?cantaccess57# print(account.__balance) # AttributeError!5859# Name mangling - still accessible but harder #?mangling60print(f"Mangled name: {account._BankAccount__balance1000}") # Works!6162print("\n=== Why Use Privacy? ===")6364class Counter:65 def __init__(self):66 self.__count = 0 #?whyprivate67 self.__min = 068 self.__max = 10069 70 def increment(self): #?controlled71 if self.__count < self.__max:72 self.__count += 173 74 def decrement(self):75 if self.__count > self.__min:76 self.__count -= 177 78 def value(self):79 return self.__count8081counter = Counter()82counter.increment()outputBalance: 1000 Mangled name: 1000 === Why Use Privacy? ===self.__count ← 0, self.__min ← 0, self.__max ← 100
64class Counter:65 def __init__(self⟨Counter D⟩):66 self.__count→ 0 = 0 #?whyprivate67 self.__min→ 0 = 068 self.__max→ 100 = 100counter ← ⟨Counter D⟩
81counter→ ⟨Counter D⟩ = Counter()82counter⟨Counter D⟩.increment()83counter.increment()def increment(self): #?controlled
pass 1 of 470def increment(self⟨Counter D⟩): #?controlled71 if self.__count < self.__max:72 self.__count += 1self.__count ← 1
pass 1 of 470def increment(self): #?controlled71 if self.__count0 < self.__max100:72 self.__count→ 1 += 1All 4 passes — pass 1 is the card above pass self.__count1 0 → 1 2 1 → 2 3 2 → 3 4 3 → 4 counter.increment()
81counter = Counter()82counter⟨Counter D⟩.increment()83counter⟨Counter D⟩.increment()84counter.increment()counter.increment()
82counter.increment()83counter⟨Counter D⟩.increment()84counter⟨Counter D⟩.increment()85print(f"Count: {counter.value()}")counter.increment()
83counter.increment()84counter⟨Counter D⟩.increment()85print(f"Count: {counter⟨Counter D⟩.value()}")def value(self):
pass 1 of 278def value(self⟨Counter D⟩):79 return self.__count3print(f"Count: {counter.value()}")
84counter.increment()85print(f"Count: {counter⟨Counter D⟩.value()}")8687# Can't bypass validation #?cantbypass88# counter.__count = 999 # Creates new attribute, not the real one!89counter⟨Counter D⟩.increment()90print(f"Count: {counter.value()}") # Still 4, not 999outputCount: 3counter.increment()
88# counter.__count = 999 # Creates new attribute, not the real one!89counter⟨Counter D⟩.increment()90print(f"Count: {counter⟨Counter D⟩.value()}") # Still 4, not 999def value(self):
pass 2 of 278def value(self⟨Counter D⟩):79 return self.__count4print(f"Count: {counter.value()}") # Still 4, not 999
89counter.increment()90print(f"Count: {counter⟨Counter D⟩.value()}") # Still 4, not 9999192print("\n=== Access Summary ===")9394class Example:95 def __init__(self):96 self.public = "Anyone can access" #?summary97 self._protected = "Convention: internal"98 self.__private = "Name mangled"99100obj = Example()101print(f"public: {obj.public}")outputCount: 4 === Access Summary ===self.public ← Anyone can access, self._protected ← Convention: internal
94class Example:95 def __init__(self⟨Example E⟩):96 self.public→ Anyone can access = "Anyone can access" #?summary97 self._protected→ Convention: internal = "Convention: internal"98 self.__private→ Name mangled = "Name mangled"obj ← ⟨Example E⟩
100obj→ ⟨Example E⟩ = Example()101print(f"public: {obj.publicAnyone can access}")102print(f"_protected: {obj._protectedConvention: internal}") # Accessible103# print(f"__private: {obj.__private}") # Error!104print(f"_Example__private: {obj._Example__privateName mangled}") # Name mangled105#@help publicoutputpublic: Anyone can access _protected: Convention: internal _Example__private: Name mangled
_name means "internal". __name means "strongly private" (name mangling).
Traditional getters and setters
Methods to read and write private data.
# Traditional Getters and Setters
print("=== Basic Getter and Setter ===\n")
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
def get_name(self):
return self._name
def set_name(self, name):
if len(name) >= 2:
self._name = name
else:
print("Name too short!")
def get_age(self):
return self._age
def set_age(self, age):
if 0 <= age <= 150:
self._age = age
else:
print(f"Invalid age: {age}")
person = Person("Alice", 30)
# Using getters
print(f"Name: {person.get_name()}")
print(f"Age: {person.get_age()}")
# Using setters
person.set_name("Bob")
person.set_age(25)
print(f"Updated: {person.get_name()}, {person.get_age()}")
# Validation prevents bad values
person.set_age(-5) # Rejected
person.set_name("X") # Rejected
print(f"Still: {person.get_name()}, {person.get_age()}")
print("\n=== Getter with Logic ===")
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
def get_celsius(self):
return self._celsius
def get_fahrenheit(self):
return self._celsius * 9/5 + 32
def get_kelvin(self):
return self._celsius + 273.15
def set_celsius(self, value):
if value >= -273.15: # Absolute zero
self._celsius = value
else:
print("Below absolute zero!")
temp = Temperature(25)
print(f"Celsius: {temp.get_celsius()}")
print(f"Fahrenheit: {temp.get_fahrenheit()}")
print(f"Kelvin: {temp.get_kelvin()}")
print("\n=== Setter with Side Effects ===")
class Logger:
def __init__(self):
self._log_level = "INFO"
self._changes = []
def get_log_level(self):
return self._log_level
def set_log_level(self, level):
valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR"]
if level.upper() in valid_levels:
old = self._log_level
self._log_level = level.upper()
self._changes.append(f"{old} -> {self._log_level}")
print(f"Log level changed: {old} -> {self._log_level}")
else:
print(f"Invalid level: {level}")
def get_history(self):
return self._changes.copy()
logger = Logger()
logger.set_log_level("DEBUG")
logger.set_log_level("ERROR")
logger.set_log_level("invalid") # Rejected
print(f"History: {logger.get_history()}")
print("\n=== Why This Style is Verbose ===")
class Point:
def __init__(self, x, y):
self._x = x
self._y = y
def get_x(self): return self._x
def set_x(self, x): self._x = x
def get_y(self): return self._y
def set_y(self, y): self._y = y
p = Point(3, 4)
# Verbose usage:
x = p.get_x()
y = p.get_y()
p.set_x(x + 1)
print(f"Point: ({p.get_x()}, {p.get_y()})")
print("""
Traditional getters/setters are verbose:
p.get_x() instead of p.x
p.set_x(5) instead of p.x = 5
Python offers @property for cleaner syntax!
""")
print("=== Basic Getter and Setter === ")
3print("=== Basic Getter and Setter ===\n")45class Person:6 def __init__(self, name, age):7 self._name = name8 self._age = age9 10 def get_name(self): #?getter11 return self._name12 13 def set_name(self, name): #?setter14 if len(name) >= 2:15 self._name = name16 else:17 print("Name too short!")18 19 def get_age(self): #?ageget20 return self._age21 22 def set_age(self, age): #?ageset23 if 0 <= age <= 150:24 self._age = age25 else:26 print(f"Invalid age: {age}")2728person = Person("Alice", 30)output=== Basic Getter and Setter ===self._name ← Alice, self._age ← 30
5class Person:6 def __init__(self⟨Person A⟩, nameAlice, age30):7 self._name→ Alice = nameAlice8 self._age→ 30 = age30person ← ⟨Person A⟩
28person→ ⟨Person A⟩ = Person("Alice", 30)2930# Using getters #?useget31print(f"Name: {person⟨Person A⟩.get_name()}")32print(f"Age: {person.get_age()}")def get_name(self): #?getter
pass 1 of 310def get_name(self⟨Person A⟩): #?getter11 return self._nameAliceAll 3 passes — pass 1 is the card above pass self._name1 Alice 2 Bob 3 Bob print(f"Name: {person.get_name()}")
30# Using getters #?useget31print(f"Name: {person⟨Person A⟩.get_name()}")32print(f"Age: {person⟨Person A⟩.get_age()}")outputName: Alicedef get_age(self): #?ageget
pass 1 of 319def get_age(self⟨Person A⟩): #?ageget20 return self._age30All 3 passes — pass 1 is the card above pass self._age1 30 2 25 3 25 print(f"Age: {person.get_age()}")
31print(f"Name: {person.get_name()}")32print(f"Age: {person⟨Person A⟩.get_age()}")3334# Using setters #?useset35person⟨Person A⟩.set_name("Bob")36person.set_age(25)outputAge: 30def set_name(self, name): #?setter
pass 1 of 213def set_name(self⟨Person A⟩, nameBob): #?setter14 if len(name) >= 2:15 self._name = nameself._name ← Bob
13def set_name(self, name): #?setter14 if len(nameBob) >= 2:15 self._name→ Bob = nameBob16 else:person.set_name("Bob")
34# Using setters #?useset35person⟨Person A⟩.set_name("Bob")36person⟨Person A⟩.set_age(25)37print(f"Updated: {person.get_name()}, {person.get_age()}")def set_age(self, age): #?ageset
pass 1 of 222def set_age(self⟨Person A⟩, age25): #?ageset23 if 0 <= age <= 150:24 self._age = ageself._age ← 25
22def set_age(self, age): #?ageset23 if 0 <= age25 <= 150:24 self._age→ 25 = age2525 else:person.set_age(25)
35person.set_name("Bob")36person⟨Person A⟩.set_age(25)37print(f"Updated: {person⟨Person A⟩.get_name()}, {person.get_age()}")print(f"Updated: {person.get_name()}, {person.get_age()}")
36person.set_age(25)37print(f"Updated: {person⟨Person A⟩.get_name()}, {person.get_age()}")3839# Validation prevents bad values #?validation40person⟨Person A⟩.set_age(-5) # Rejected41person.set_name("X") # RejectedoutputUpdated: Bob, 25def set_age(self, age): #?ageset
pass 2 of 222def set_age(self⟨Person A⟩, age-5): #?ageset23 if 0 <= age <= 150:24 self._age = ageelse:
23if 0 <= age <= 150:24 self._age = age25else:26 print(f"Invalid age: {age-5}")outputInvalid age: -5person.set_age(-5) # Rejected
39# Validation prevents bad values #?validation40person⟨Person A⟩.set_age(-5) # Rejected41person⟨Person A⟩.set_name("X") # Rejected42print(f"Still: {person.get_name()}, {person.get_age()}")def set_name(self, name): #?setter
pass 2 of 213def set_name(self⟨Person A⟩, nameX): #?setter14 if len(name) >= 2:15 self._name = nameelse:
14if len(name) >= 2:15 self._name = name16else:17 print("Name too short!")outputName too short!person.set_name("X") # Rejected
40person.set_age(-5) # Rejected41person⟨Person A⟩.set_name("X") # Rejected42print(f"Still: {person⟨Person A⟩.get_name()}, {person.get_age()}")print(f"Still: {person.get_name()}, {person.get_age()}")
41person.set_name("X") # Rejected42print(f"Still: {person⟨Person A⟩.get_name()}, {person.get_age()}")4344print("\n=== Getter with Logic ===")4546class Temperature:47 def __init__(self, celsius):48 self._celsius = celsius49 50 def get_celsius(self):51 return self._celsius52 53 def get_fahrenheit(self): #?computedgetter54 return self._celsius * 9/5 + 3255 56 def get_kelvin(self):57 return self._celsius + 273.1558 59 def set_celsius(self, value):60 if value >= -273.15: # Absolute zero #?physicscheck61 self._celsius = value62 else:63 print("Below absolute zero!")6465temp = Temperature(25)66print(f"Celsius: {temp.get_celsius()}")outputStill: Bob, 25 === Getter with Logic ===self._celsius ← 25
46class Temperature:47 def __init__(self⟨Temperature B⟩, celsius25):48 self._celsius→ 25 = celsius25temp ← ⟨Temperature B⟩
65temp→ ⟨Temperature B⟩ = Temperature(25)66print(f"Celsius: {temp⟨Temperature B⟩.get_celsius()}")67print(f"Fahrenheit: {temp.get_fahrenheit()}")def get_celsius(self):
50def get_celsius(self⟨Temperature B⟩):51 return self._celsius25print(f"Celsius: {temp.get_celsius()}")
65temp = Temperature(25)66print(f"Celsius: {temp⟨Temperature B⟩.get_celsius()}")67print(f"Fahrenheit: {temp⟨Temperature B⟩.get_fahrenheit()}")68print(f"Kelvin: {temp.get_kelvin()}")outputCelsius: 25def get_fahrenheit(self): #?computedgetter
53def get_fahrenheit(self⟨Temperature B⟩): #?computedgetter54 return self._celsius25 * 9/5 + 32print(f"Fahrenheit: {temp.get_fahrenheit()}")
66print(f"Celsius: {temp.get_celsius()}")67print(f"Fahrenheit: {temp⟨Temperature B⟩.get_fahrenheit()}")68print(f"Kelvin: {temp⟨Temperature B⟩.get_kelvin()}")outputFahrenheit: 77.0def get_kelvin(self):
56def get_kelvin(self⟨Temperature B⟩):57 return self._celsius25 + 273.15print(f"Kelvin: {temp.get_kelvin()}")
67print(f"Fahrenheit: {temp.get_fahrenheit()}")68print(f"Kelvin: {temp⟨Temperature B⟩.get_kelvin()}")6970print("\n=== Setter with Side Effects ===")7172class Logger:73 def __init__(self):74 self._log_level = "INFO"75 self._changes = []76 77 def get_log_level(self):78 return self._log_level79 80 def set_log_level(self, level): #?sideeffect81 valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR"]82 if level.upper() in valid_levels:83 old = self._log_level84 self._log_level = level.upper()85 self._changes.append(f"{old} -> {self._log_level}") #?trackchange86 print(f"Log level changed: {old} -> {self._log_level}")87 else:88 print(f"Invalid level: {level}")89 90 def get_history(self): #?history91 return self._changes.copy()9293logger = Logger()94logger.set_log_level("DEBUG")outputKelvin: 298.15 === Setter with Side Effects ===self._log_level ← INFO, self._changes ← []
72class Logger:73 def __init__(self⟨Logger C⟩):74 self._log_level→ INFO = "INFO"75 self._changes→ [] = []logger ← ⟨Logger C⟩
93logger→ ⟨Logger C⟩ = Logger()94logger⟨Logger C⟩.set_log_level("DEBUG")95logger.set_log_level("ERROR")valid_levels ← ['DEBUG', 'INFO', 'WARNING', 'ERROR']
pass 1 of 380def set_log_level(self⟨Logger C⟩, levelDEBUG): #?sideeffect81 valid_levels→ ['DEBUG', 'INFO', 'WARNING', 'ERROR'] = ["DEBUG", "INFO", "WARNING", "ERROR"]82 if level.upper() in valid_levels:All 3 passes — pass 1 is the card above pass levelvalid_levelsoldself._log_levelself._changes1 DEBUG ['DEBUG', 'INFO', 'WARNING', 'ERROR'] INFO INFO → DEBUG [] → ['INFO -> DEBUG'] 2 ERROR ['DEBUG', 'INFO', 'WARNING', 'ERROR'] DEBUG DEBUG → ERROR ['INFO -> DEBUG'] → ['INFO -> DEBUG', 'DEBUG -> ERROR'] 3 invalid ['DEBUG', 'INFO', 'WARNING', 'ERROR'] — — — old ← INFO, self._log_level ← DEBUG, self._changes ← ['INFO -> DEBUG']
pass 1 of 281valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR"]82if levelDEBUG.upper() in valid_levels['DEBUG', 'INFO', 'WARNING', 'ERROR']:83 old→ INFO = self._log_levelINFO84 self._log_level→ DEBUG = levelDEBUG.upper()85 self._changes→ ['INFO -> DEBUG'].append(f"{oldINFO} -> {self._log_levelDEBUG}") #?trackchange86 print(f"Log level changed: {oldINFO} -> {self._log_levelDEBUG}")87else:outputLog level changed: INFO -> DEBUGlogger.set_log_level("DEBUG")
93logger = Logger()94logger⟨Logger C⟩.set_log_level("DEBUG")95logger⟨Logger C⟩.set_log_level("ERROR")96logger.set_log_level("invalid") # Rejectedold ← DEBUG, self._log_level ← ERROR, self._changes ← ['INFO -> DEBUG', 'DEBUG -> ERROR']
pass 2 of 281valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR"]82if levelERROR.upper() in valid_levels['DEBUG', 'INFO', 'WARNING', 'ERROR']:83 old→ DEBUG = self._log_levelDEBUG84 self._log_level→ ERROR = levelERROR.upper()85 self._changes→ ['INFO -> DEBUG', 'DEBUG -> ERROR'].append(f"{oldDEBUG} -> {self._log_levelERROR}") #?trackchange86 print(f"Log level changed: {oldDEBUG} -> {self._log_levelERROR}")87else:outputLog level changed: DEBUG -> ERRORlogger.set_log_level("ERROR")
94logger.set_log_level("DEBUG")95logger⟨Logger C⟩.set_log_level("ERROR")96logger⟨Logger C⟩.set_log_level("invalid") # Rejected97print(f"History: {logger.get_history()}")else:
85 self._changes.append(f"{old} -> {self._log_level}") #?trackchange86 print(f"Log level changed: {old} -> {self._log_level}")87else:88 print(f"Invalid level: {levelinvalid}")outputInvalid level: invalidlogger.set_log_level("invalid") # Rejected
95logger.set_log_level("ERROR")96logger⟨Logger C⟩.set_log_level("invalid") # Rejected97print(f"History: {logger⟨Logger C⟩.get_history()}")def get_history(self): #?history
90def get_history(self⟨Logger C⟩): #?history91 return self._changes['INFO -> DEBUG', 'DEBUG -> ERROR'].copy()print(f"History: {logger.get_history()}")
96logger.set_log_level("invalid") # Rejected97print(f"History: {logger⟨Logger C⟩.get_history()}")9899print("\n=== Why This Style is Verbose ===")100101class Point:102 def __init__(self, x, y):103 self._x = x104 self._y = y105 106 def get_x(self): return self._x107 def set_x(self, x): self._x = x108 def get_y(self): return self._y109 def set_y(self, y): self._y = y110111p = Point(3, 4)112# Verbose usage: #?verboseoutputHistory: ['INFO -> DEBUG', 'DEBUG -> ERROR'] === Why This Style is Verbose ===self._x ← 3, self._y ← 4
101class Point:102 def __init__(self⟨Point D⟩, x3, y4):103 self._x→ 3 = x3104 self._y→ 4 = y4p ← ⟨Point D⟩, x ← 3, y ← 4
111p→ ⟨Point D⟩ = Point(3, 4)112# Verbose usage: #?verbose113x→ 3 = p⟨Point D⟩.get_x()114y→ 4 = p⟨Point D⟩.get_y()115p⟨Point D⟩.set_x(x3 + 1)116print(f"Point: ({p⟨Point D⟩.get_x()}, {p.get_y()})")117118print("""119Traditional getters/setters are verbose:120 p.get_x() instead of p.x121 p.set_x(5) instead of p.x = 5122123Python offers @property for cleaner syntax!124""")125#@help getteroutputPoint: (4, 4) Traditional getters/setters are verbose: p.get_x() instead of p.x p.set_x(5) instead of p.x = 5 Python offers @property for cleaner syntax!
get_balance() and set_balance() control access. Java-style.
Python properties
The Pythonic way to control attribute access.
# Python Properties
print("=== Basic @property ===\n")
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""Get the radius."""
return self._radius
@radius.setter
def radius(self, value):
"""Set the radius with validation."""
if value > 0:
self._radius = value
else:
print(f"Invalid radius: {value}")
circle = Circle(5)
# Access like attribute
print(f"Radius: {circle.radius}") # Calls getter
# Assign like attribute
circle.radius = 10 # Calls setter
print(f"New radius: {circle.radius}")
# Validation still works
circle.radius = -5 # Rejected
print(f"Still: {circle.radius}")
print("\n=== Properties vs Direct Access ===")
# Compare syntax:
print("""
Traditional:
value = obj.get_attribute()
obj.set_attribute(value)
With @property:
value = obj.attribute
obj.attribute = value
Same control, cleaner syntax!
""")
print("=== Multiple Properties ===")
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
@property
def width(self):
return self._width
@width.setter
def width(self, value):
if value > 0:
self._width = value
@property
def height(self):
return self._height
@height.setter
def height(self, value):
if value > 0:
self._height = value
@property
def area(self):
"""Computed property - no setter."""
return self._width * self._height
@property
def perimeter(self):
return 2 * (self._width + self._height)
rect = Rectangle(5, 3)
print(f"Size: {rect.width} x {rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
# Change dimensions
rect.width = 10
print(f"\nAfter resize:")
print(f"Size: {rect.width} x {rect.height}")
print(f"Area: {rect.area}") # Automatically updated!
print("\n=== Property with Deleter ===")
class User:
def __init__(self, email):
self._email = email
@property
def email(self):
return self._email
@email.setter
def email(self, value):
if "@" in value:
self._email = value
@email.deleter
def email(self):
print("Removing email...")
self._email = None
user = User("alice@example.com")
print(f"Email: {user.email}")
del user.email
print(f"After delete: {user.email}")
print("\n=== Property Naming Patterns ===")
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value >= -273.15:
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self._celsius = (value - 32) * 5/9
temp = Temperature(0)
print(f"0°C = {temp.fahrenheit}°F")
temp.fahrenheit = 212 # Set via Fahrenheit
print(f"{temp.fahrenheit}°F = {temp.celsius}°C")
# Python Properties
print("=== Basic @property ===\n")
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""Get the radius."""
return self._radius
@radius.setter
def radius(self, value):
"""Set the radius with validation."""
if value > 0:
self._radius = value
else:
print(f"Invalid radius: {value}")
circle = Circle(2)
# Access like attribute
print(f"Radius: {circle.radius}") # Calls getter
# Assign like attribute
circle.radius = 10 # Calls setter
print(f"New radius: {circle.radius}")
# Validation still works
circle.radius = -5 # Rejected
print(f"Still: {circle.radius}")
print("\n=== Properties vs Direct Access ===")
# Compare syntax:
print("""
Traditional:
value = obj.get_attribute()
obj.set_attribute(value)
With @property:
value = obj.attribute
obj.attribute = value
Same control, cleaner syntax!
""")
print("=== Multiple Properties ===")
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
@property
def width(self):
return self._width
@width.setter
def width(self, value):
if value > 0:
self._width = value
@property
def height(self):
return self._height
@height.setter
def height(self, value):
if value > 0:
self._height = value
@property
def area(self):
"""Computed property - no setter."""
return self._width * self._height
@property
def perimeter(self):
return 2 * (self._width + self._height)
rect = Rectangle(5, 3)
print(f"Size: {rect.width} x {rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
# Change dimensions
rect.width = 10
print(f"\nAfter resize:")
print(f"Size: {rect.width} x {rect.height}")
print(f"Area: {rect.area}") # Automatically updated!
print("\n=== Property with Deleter ===")
class User:
def __init__(self, email):
self._email = email
@property
def email(self):
return self._email
@email.setter
def email(self, value):
if "@" in value:
self._email = value
@email.deleter
def email(self):
print("Removing email...")
self._email = None
user = User("alice@example.com")
print(f"Email: {user.email}")
del user.email
print(f"After delete: {user.email}")
print("\n=== Property Naming Patterns ===")
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value >= -273.15:
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self._celsius = (value - 32) * 5/9
temp = Temperature(0)
print(f"0°C = {temp.fahrenheit}°F")
temp.fahrenheit = 212 # Set via Fahrenheit
print(f"{temp.fahrenheit}°F = {temp.celsius}°C")
# Python Properties
print("=== Basic @property ===\n")
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""Get the radius."""
return self._radius
@radius.setter
def radius(self, value):
"""Set the radius with validation."""
if value > 0:
self._radius = value
else:
print(f"Invalid radius: {value}")
circle = Circle(12)
# Access like attribute
print(f"Radius: {circle.radius}") # Calls getter
# Assign like attribute
circle.radius = 10 # Calls setter
print(f"New radius: {circle.radius}")
# Validation still works
circle.radius = -5 # Rejected
print(f"Still: {circle.radius}")
print("\n=== Properties vs Direct Access ===")
# Compare syntax:
print("""
Traditional:
value = obj.get_attribute()
obj.set_attribute(value)
With @property:
value = obj.attribute
obj.attribute = value
Same control, cleaner syntax!
""")
print("=== Multiple Properties ===")
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
@property
def width(self):
return self._width
@width.setter
def width(self, value):
if value > 0:
self._width = value
@property
def height(self):
return self._height
@height.setter
def height(self, value):
if value > 0:
self._height = value
@property
def area(self):
"""Computed property - no setter."""
return self._width * self._height
@property
def perimeter(self):
return 2 * (self._width + self._height)
rect = Rectangle(5, 3)
print(f"Size: {rect.width} x {rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
# Change dimensions
rect.width = 10
print(f"\nAfter resize:")
print(f"Size: {rect.width} x {rect.height}")
print(f"Area: {rect.area}") # Automatically updated!
print("\n=== Property with Deleter ===")
class User:
def __init__(self, email):
self._email = email
@property
def email(self):
return self._email
@email.setter
def email(self, value):
if "@" in value:
self._email = value
@email.deleter
def email(self):
print("Removing email...")
self._email = None
user = User("alice@example.com")
print(f"Email: {user.email}")
del user.email
print(f"After delete: {user.email}")
print("\n=== Property Naming Patterns ===")
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value >= -273.15:
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self._celsius = (value - 32) * 5/9
temp = Temperature(0)
print(f"0°C = {temp.fahrenheit}°F")
temp.fahrenheit = 212 # Set via Fahrenheit
print(f"{temp.fahrenheit}°F = {temp.celsius}°C")
# Python Properties
print("=== Basic @property ===\n")
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""Get the radius."""
return self._radius
@radius.setter
def radius(self, value):
"""Set the radius with validation."""
if value > 0:
self._radius = value
else:
print(f"Invalid radius: {value}")
circle = Circle(5)
# Access like attribute
print(f"Radius: {circle.radius}") # Calls getter
# Assign like attribute
circle.radius = 10 # Calls setter
print(f"New radius: {circle.radius}")
# Validation still works
circle.radius = -5 # Rejected
print(f"Still: {circle.radius}")
print("\n=== Properties vs Direct Access ===")
# Compare syntax:
print("""
Traditional:
value = obj.get_attribute()
obj.set_attribute(value)
With @property:
value = obj.attribute
obj.attribute = value
Same control, cleaner syntax!
""")
print("=== Multiple Properties ===")
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
@property
def width(self):
return self._width
@width.setter
def width(self, value):
if value > 0:
self._width = value
@property
def height(self):
return self._height
@height.setter
def height(self, value):
if value > 0:
self._height = value
@property
def area(self):
"""Computed property - no setter."""
return self._width * self._height
@property
def perimeter(self):
return 2 * (self._width + self._height)
rect = Rectangle(4, 4)
print(f"Size: {rect.width} x {rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
# Change dimensions
rect.width = 10
print(f"\nAfter resize:")
print(f"Size: {rect.width} x {rect.height}")
print(f"Area: {rect.area}") # Automatically updated!
print("\n=== Property with Deleter ===")
class User:
def __init__(self, email):
self._email = email
@property
def email(self):
return self._email
@email.setter
def email(self, value):
if "@" in value:
self._email = value
@email.deleter
def email(self):
print("Removing email...")
self._email = None
user = User("alice@example.com")
print(f"Email: {user.email}")
del user.email
print(f"After delete: {user.email}")
print("\n=== Property Naming Patterns ===")
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value >= -273.15:
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self._celsius = (value - 32) * 5/9
temp = Temperature(0)
print(f"0°C = {temp.fahrenheit}°F")
temp.fahrenheit = 212 # Set via Fahrenheit
print(f"{temp.fahrenheit}°F = {temp.celsius}°C")
# Python Properties
print("=== Basic @property ===\n")
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""Get the radius."""
return self._radius
@radius.setter
def radius(self, value):
"""Set the radius with validation."""
if value > 0:
self._radius = value
else:
print(f"Invalid radius: {value}")
circle = Circle(5)
# Access like attribute
print(f"Radius: {circle.radius}") # Calls getter
# Assign like attribute
circle.radius = 10 # Calls setter
print(f"New radius: {circle.radius}")
# Validation still works
circle.radius = -5 # Rejected
print(f"Still: {circle.radius}")
print("\n=== Properties vs Direct Access ===")
# Compare syntax:
print("""
Traditional:
value = obj.get_attribute()
obj.set_attribute(value)
With @property:
value = obj.attribute
obj.attribute = value
Same control, cleaner syntax!
""")
print("=== Multiple Properties ===")
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
@property
def width(self):
return self._width
@width.setter
def width(self, value):
if value > 0:
self._width = value
@property
def height(self):
return self._height
@height.setter
def height(self, value):
if value > 0:
self._height = value
@property
def area(self):
"""Computed property - no setter."""
return self._width * self._height
@property
def perimeter(self):
return 2 * (self._width + self._height)
rect = Rectangle(8, 2)
print(f"Size: {rect.width} x {rect.height}")
print(f"Area: {rect.area}")
print(f"Perimeter: {rect.perimeter}")
# Change dimensions
rect.width = 10
print(f"\nAfter resize:")
print(f"Size: {rect.width} x {rect.height}")
print(f"Area: {rect.area}") # Automatically updated!
print("\n=== Property with Deleter ===")
class User:
def __init__(self, email):
self._email = email
@property
def email(self):
return self._email
@email.setter
def email(self, value):
if "@" in value:
self._email = value
@email.deleter
def email(self):
print("Removing email...")
self._email = None
user = User("alice@example.com")
print(f"Email: {user.email}")
del user.email
print(f"After delete: {user.email}")
print("\n=== Property Naming Patterns ===")
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value >= -273.15:
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self._celsius = (value - 32) * 5/9
temp = Temperature(0)
print(f"0°C = {temp.fahrenheit}°F")
temp.fahrenheit = 212 # Set via Fahrenheit
print(f"{temp.fahrenheit}°F = {temp.celsius}°C")
print("=== Basic @property === ")
3print("=== Basic @property ===\n")45class Circle:6 def __init__(self, radius):7 self._radius = radius8 9 @property #?propertydeco10 def radius(self): #?propgetter11 """Get the radius."""12 return self._radius13 14 @radius.setter #?propsetter15 def radius(self, value):16 """Set the radius with validation."""17 if value > 0:18 self._radius = value19 else:20 print(f"Invalid radius: {value}")2122circle = Circle(5) #@circle=Circle(2), Circle(12)output=== Basic @property ===self._radius ← 5
5class Circle:6 def __init__(self⟨Circle A⟩, radius5):7 self._radius→ 5 = radius5circle ← ⟨Circle A⟩
22circle→ ⟨Circle A⟩ = Circle(5) #@circle=Circle(2), Circle(12)2324# Access like attribute #?accesslike25print(f"Radius: {circle.radius5}") # Calls getterdef radius(self): #?propgetter
pass 1 of 39@property #?propertydeco10def radius(self⟨Circle A⟩): #?propgetter11 """Get the radius."""12 return self._radius5All 3 passes — pass 1 is the card above pass self._radius1 5 2 10 3 10 print(f"Radius: {circle.radius}") # Calls getter
24# Access like attribute #?accesslike25print(f"Radius: {circle.radius5}") # Calls getter2627# Assign like attribute #?assignlike28circle.radius = 10 # Calls setter29print(f"New radius: {circle.radius}")outputRadius: 5def radius(self, value):
pass 1 of 214@radius.setter #?propsetter15def radius(self⟨Circle A⟩, value10):16 """Set the radius with validation."""17 if value > 0:self._radius ← 10
16"""Set the radius with validation."""17if value10 > 0:18 self._radius→ 10 = value1019else:circle.radius ← 10
27# Assign like attribute #?assignlike28circle.radius→ 10 = 10 # Calls setter29print(f"New radius: {circle.radius10}")print(f"New radius: {circle.radius}")
28circle.radius = 10 # Calls setter29print(f"New radius: {circle.radius10}")3031# Validation still works #?propvalid32circle.radius = -5 # Rejected33print(f"Still: {circle.radius}")outputNew radius: 10def radius(self, value):
pass 2 of 214@radius.setter #?propsetter15def radius(self⟨Circle A⟩, value-5):16 """Set the radius with validation."""17 if value > 0:else:
17if value > 0:18 self._radius = value19else:20 print(f"Invalid radius: {value-5}")outputInvalid radius: -5circle.radius ← 10
31# Validation still works #?propvalid32circle.radius→ 10 = -5 # Rejected33print(f"Still: {circle.radius10}")print(f"Still: {circle.radius}")
32circle.radius = -5 # Rejected33print(f"Still: {circle.radius10}")3435print("\n=== Properties vs Direct Access ===")3637# Compare syntax: #?compare38print("""39Traditional:40 value = obj.get_attribute()41 obj.set_attribute(value)4243With @property:44 value = obj.attribute45 obj.attribute = value4647Same control, cleaner syntax!48""")4950print("=== Multiple Properties ===")5152class Rectangle:53 def __init__(self, width, height):54 self._width = width55 self._height = height56 57 @property58 def width(self):59 return self._width60 61 @width.setter62 def width(self, value): #?widthsetter63 if value > 0:64 self._width = value65 66 @property67 def height(self):68 return self._height69 70 @height.setter71 def height(self, value):72 if value > 0:73 self._height = value74 75 @property76 def area(self): #?computedprop77 """Computed property - no setter."""78 return self._width * self._height79 80 @property81 def perimeter(self):82 return 2 * (self._width + self._height)8384rect = Rectangle(5, 3) #@rect=Rectangle(4, 4), Rectangle(8, 2)85print(f"Size: {rect.width} x {rect.height}")outputStill: 10 === Properties vs Direct Access === Traditional: value = obj.get_attribute() obj.set_attribute(value) With @property: value = obj.attribute obj.attribute = value Same control, cleaner syntax! === Multiple Properties ===self._width ← 5, self._height ← 3
52class Rectangle:53 def __init__(self⟨Rectangle B⟩, width5, height3):54 self._width→ 5 = width555 self._height→ 3 = height3rect ← ⟨Rectangle B⟩
84rect→ ⟨Rectangle B⟩ = Rectangle(5, 3) #@rect=Rectangle(4, 4), Rectangle(8, 2)85print(f"Size: {rect.width5} x {rect.height3}")86print(f"Area: {rect.area}")def width(self):
pass 1 of 257@property58def width(self⟨Rectangle B⟩):59 return self._width5def height(self):
pass 1 of 266@property67def height(self⟨Rectangle B⟩):68 return self._height3print(f"Size: {rect.width} x {rect.height}")
84rect = Rectangle(5, 3) #@rect=Rectangle(4, 4), Rectangle(8, 2)85print(f"Size: {rect.width5} x {rect.height3}")86print(f"Area: {rect.area15}")87print(f"Perimeter: {rect.perimeter}")outputSize: 5 x 3def area(self): #?computedprop
pass 1 of 275@property76def area(self⟨Rectangle B⟩): #?computedprop77 """Computed property - no setter."""78 return self._width5 * self._height3print(f"Area: {rect.area}")
85print(f"Size: {rect.width} x {rect.height}")86print(f"Area: {rect.area15}")87print(f"Perimeter: {rect.perimeter16}")outputArea: 15def perimeter(self):
80@property81def perimeter(self⟨Rectangle B⟩):82 return 2 * (self._width5 + self._height3)print(f"Perimeter: {rect.perimeter}")
86print(f"Area: {rect.area}")87print(f"Perimeter: {rect.perimeter16}")8889# Change dimensions #?changedim90rect.width = 1091print(f"\nAfter resize:")outputPerimeter: 16def width(self, value): #?widthsetter
61@width.setter62def width(self⟨Rectangle B⟩, value10): #?widthsetter63 if value > 0:64 self._width = valueself._width ← 10
62def width(self, value): #?widthsetter63 if value10 > 0:64 self._width→ 10 = value10rect.width ← 10
89# Change dimensions #?changedim90rect.width→ 10 = 1091print(f"\nAfter resize:")92print(f"Size: {rect.width10} x {rect.height3}")93print(f"Area: {rect.area}") # Automatically updated!output After resize:def width(self):
pass 2 of 257@property58def width(self⟨Rectangle B⟩):59 return self._width10def height(self):
pass 2 of 266@property67def height(self⟨Rectangle B⟩):68 return self._height3print(f"Size: {rect.width} x {rect.height}")
91print(f"\nAfter resize:")92print(f"Size: {rect.width10} x {rect.height3}")93print(f"Area: {rect.area30}") # Automatically updated!outputSize: 10 x 3def area(self): #?computedprop
pass 2 of 275@property76def area(self⟨Rectangle B⟩): #?computedprop77 """Computed property - no setter."""78 return self._width10 * self._height3print(f"Area: {rect.area}") # Automatically updated!
92print(f"Size: {rect.width} x {rect.height}")93print(f"Area: {rect.area30}") # Automatically updated!9495print("\n=== Property with Deleter ===")9697class User:98 def __init__(self, email):99 self._email = email100 101 @property102 def email(self):103 return self._email104 105 @email.setter106 def email(self, value):107 if "@" in value:108 self._email = value109 110 @email.deleter #?deleter111 def email(self):112 print("Removing email...")113 self._email = None114115user = User("alice@example.com")116print(f"Email: {user.email}")outputArea: 30 === Property with Deleter ===self._email ← alice@example.com
97class User:98 def __init__(self⟨User C⟩, emailalice@example.com):99 self._email→ alice@example.com = emailalice@example.comuser ← ⟨User C⟩
115user→ ⟨User C⟩ = User("alice@example.com")116print(f"Email: {user.emailalice@example.com}")def email(self):
pass 1 of 2101@property102def email(self⟨User C⟩):103 return self._emailalice@example.comprint(f"Email: {user.email}")
115user = User("alice@example.com")116print(f"Email: {user.emailalice@example.com}")outputEmail: alice@example.comself._email ← None
110@email.deleter #?deleter111def email(self⟨User C⟩):112 print("Removing email...")113 self._email→ None = NoneoutputRemoving email...print(f"After delete: {user.email}")
118del user.email #?usedel119print(f"After delete: {user.emailNone}")def email(self):
pass 2 of 2101@property102def email(self⟨User C⟩):103 return self._emailNoneprint(f"After delete: {user.email}")
118del user.email #?usedel119print(f"After delete: {user.emailNone}")120121print("\n=== Property Naming Patterns ===")122123class Temperature:124 def __init__(self, celsius):125 self._celsius = celsius #?storedinternal126 127 @property128 def celsius(self): #?samename129 return self._celsius130 131 @celsius.setter132 def celsius(self, value):133 if value >= -273.15:134 self._celsius = value135 136 @property137 def fahrenheit(self): #?convertprop138 return self._celsius * 9/5 + 32139 140 @fahrenheit.setter #?reverseset141 def fahrenheit(self, value):142 self._celsius = (value - 32) * 5/9143144temp = Temperature(0)145print(f"0°C = {temp.fahrenheit}°F")outputAfter delete: None === Property Naming Patterns ===self._celsius ← 0
123class Temperature:124 def __init__(self⟨Temperature D⟩, celsius0):125 self._celsius→ 0 = celsius0 #?storedinternaltemp ← ⟨Temperature D⟩
144temp→ ⟨Temperature D⟩ = Temperature(0)145print(f"0°C = {temp.fahrenheit32.0}°F")def fahrenheit(self): #?convertprop
pass 1 of 2136@property137def fahrenheit(self⟨Temperature D⟩): #?convertprop138 return self._celsius0 * 9/5 + 32print(f"0°C = {temp.fahrenheit}°F")
144temp = Temperature(0)145print(f"0°C = {temp.fahrenheit32.0}°F")146147temp.fahrenheit = 212 # Set via Fahrenheit #?setf148print(f"{temp.fahrenheit}°F = {temp.celsius}°C")output0°C = 32.0°Fself._celsius ← 100.0
140@fahrenheit.setter #?reverseset141def fahrenheit(self⟨Temperature D⟩, value212):142 self._celsius→ 100.0 = (value212 - 32) * 5/9temp.fahrenheit ← 212.0
147temp.fahrenheit→ 212.0 = 212 # Set via Fahrenheit #?setf148print(f"{temp.fahrenheit212.0}°F = {temp.celsius100.0}°C")149#@help propertydecodef fahrenheit(self): #?convertprop
pass 2 of 2136@property137def fahrenheit(self⟨Temperature D⟩): #?convertprop138 return self._celsius100.0 * 9/5 + 32def celsius(self): #?samename
127@property128def celsius(self⟨Temperature D⟩): #?samename129 return self._celsius100.0print(f"{temp.fahrenheit}°F = {temp.celsius}°C")
147temp.fahrenheit = 212 # Set via Fahrenheit #?setf148print(f"{temp.fahrenheit212.0}°F = {temp.celsius100.0}°C")149#@help propertydecooutput212.0°F = 100.0°C
print("=== Basic @property === ")
3print("=== Basic @property ===\n")45class Circle:6 def __init__(self, radius):7 self._radius = radius8 9 @property10 def radius(self):11 """Get the radius."""12 return self._radius13 14 @radius.setter15 def radius(self, value):16 """Set the radius with validation."""17 if value > 0:18 self._radius = value19 else:20 print(f"Invalid radius: {value}")2122circle = Circle(2)output=== Basic @property ===self._radius ← 2
5class Circle:6 def __init__(self⟨Circle A⟩, radius2):7 self._radius→ 2 = radius2circle ← ⟨Circle A⟩
22circle→ ⟨Circle A⟩ = Circle(2)2324# Access like attribute25print(f"Radius: {circle.radius2}") # Calls getterdef radius(self):
pass 1 of 39@property10def radius(self⟨Circle A⟩):11 """Get the radius."""12 return self._radius2All 3 passes — pass 1 is the card above pass self._radius1 2 2 10 3 10 print(f"Radius: {circle.radius}") # Calls getter
24# Access like attribute25print(f"Radius: {circle.radius2}") # Calls getter2627# Assign like attribute28circle.radius = 10 # Calls setter29print(f"New radius: {circle.radius}")outputRadius: 2def radius(self, value):
pass 1 of 214@radius.setter15def radius(self⟨Circle A⟩, value10):16 """Set the radius with validation."""17 if value > 0:self._radius ← 10
16"""Set the radius with validation."""17if value10 > 0:18 self._radius→ 10 = value1019else:circle.radius ← 10
27# Assign like attribute28circle.radius→ 10 = 10 # Calls setter29print(f"New radius: {circle.radius10}")print(f"New radius: {circle.radius}")
28circle.radius = 10 # Calls setter29print(f"New radius: {circle.radius10}")3031# Validation still works32circle.radius = -5 # Rejected33print(f"Still: {circle.radius}")outputNew radius: 10def radius(self, value):
pass 2 of 214@radius.setter15def radius(self⟨Circle A⟩, value-5):16 """Set the radius with validation."""17 if value > 0:else:
17if value > 0:18 self._radius = value19else:20 print(f"Invalid radius: {value-5}")outputInvalid radius: -5circle.radius ← 10
31# Validation still works32circle.radius→ 10 = -5 # Rejected33print(f"Still: {circle.radius10}")print(f"Still: {circle.radius}")
32circle.radius = -5 # Rejected33print(f"Still: {circle.radius10}")3435print("\n=== Properties vs Direct Access ===")3637# Compare syntax:38print("""39Traditional:40 value = obj.get_attribute()41 obj.set_attribute(value)4243With @property:44 value = obj.attribute45 obj.attribute = value4647Same control, cleaner syntax!48""")4950print("=== Multiple Properties ===")5152class Rectangle:53 def __init__(self, width, height):54 self._width = width55 self._height = height56 57 @property58 def width(self):59 return self._width60 61 @width.setter62 def width(self, value):63 if value > 0:64 self._width = value65 66 @property67 def height(self):68 return self._height69 70 @height.setter71 def height(self, value):72 if value > 0:73 self._height = value74 75 @property76 def area(self):77 """Computed property - no setter."""78 return self._width * self._height79 80 @property81 def perimeter(self):82 return 2 * (self._width + self._height)8384rect = Rectangle(5, 3)85print(f"Size: {rect.width} x {rect.height}")outputStill: 10 === Properties vs Direct Access === Traditional: value = obj.get_attribute() obj.set_attribute(value) With @property: value = obj.attribute obj.attribute = value Same control, cleaner syntax! === Multiple Properties ===self._width ← 5, self._height ← 3
52class Rectangle:53 def __init__(self⟨Rectangle B⟩, width5, height3):54 self._width→ 5 = width555 self._height→ 3 = height3rect ← ⟨Rectangle B⟩
84rect→ ⟨Rectangle B⟩ = Rectangle(5, 3)85print(f"Size: {rect.width5} x {rect.height3}")86print(f"Area: {rect.area}")def width(self):
pass 1 of 257@property58def width(self⟨Rectangle B⟩):59 return self._width5def height(self):
pass 1 of 266@property67def height(self⟨Rectangle B⟩):68 return self._height3print(f"Size: {rect.width} x {rect.height}")
84rect = Rectangle(5, 3)85print(f"Size: {rect.width5} x {rect.height3}")86print(f"Area: {rect.area15}")87print(f"Perimeter: {rect.perimeter}")outputSize: 5 x 3def area(self):
pass 1 of 275@property76def area(self⟨Rectangle B⟩):77 """Computed property - no setter."""78 return self._width5 * self._height3print(f"Area: {rect.area}")
85print(f"Size: {rect.width} x {rect.height}")86print(f"Area: {rect.area15}")87print(f"Perimeter: {rect.perimeter16}")outputArea: 15def perimeter(self):
80@property81def perimeter(self⟨Rectangle B⟩):82 return 2 * (self._width5 + self._height3)print(f"Perimeter: {rect.perimeter}")
86print(f"Area: {rect.area}")87print(f"Perimeter: {rect.perimeter16}")8889# Change dimensions90rect.width = 1091print(f"\nAfter resize:")outputPerimeter: 16def width(self, value):
61@width.setter62def width(self⟨Rectangle B⟩, value10):63 if value > 0:64 self._width = valueself._width ← 10
62def width(self, value):63 if value10 > 0:64 self._width→ 10 = value10rect.width ← 10
89# Change dimensions90rect.width→ 10 = 1091print(f"\nAfter resize:")92print(f"Size: {rect.width10} x {rect.height3}")93print(f"Area: {rect.area}") # Automatically updated!output After resize:def width(self):
pass 2 of 257@property58def width(self⟨Rectangle B⟩):59 return self._width10def height(self):
pass 2 of 266@property67def height(self⟨Rectangle B⟩):68 return self._height3print(f"Size: {rect.width} x {rect.height}")
91print(f"\nAfter resize:")92print(f"Size: {rect.width10} x {rect.height3}")93print(f"Area: {rect.area30}") # Automatically updated!outputSize: 10 x 3def area(self):
pass 2 of 275@property76def area(self⟨Rectangle B⟩):77 """Computed property - no setter."""78 return self._width10 * self._height3print(f"Area: {rect.area}") # Automatically updated!
92print(f"Size: {rect.width} x {rect.height}")93print(f"Area: {rect.area30}") # Automatically updated!9495print("\n=== Property with Deleter ===")9697class User:98 def __init__(self, email):99 self._email = email100 101 @property102 def email(self):103 return self._email104 105 @email.setter106 def email(self, value):107 if "@" in value:108 self._email = value109 110 @email.deleter111 def email(self):112 print("Removing email...")113 self._email = None114115user = User("alice@example.com")116print(f"Email: {user.email}")outputArea: 30 === Property with Deleter ===self._email ← alice@example.com
97class User:98 def __init__(self⟨User C⟩, emailalice@example.com):99 self._email→ alice@example.com = emailalice@example.comuser ← ⟨User C⟩
115user→ ⟨User C⟩ = User("alice@example.com")116print(f"Email: {user.emailalice@example.com}")def email(self):
pass 1 of 2101@property102def email(self⟨User C⟩):103 return self._emailalice@example.comprint(f"Email: {user.email}")
115user = User("alice@example.com")116print(f"Email: {user.emailalice@example.com}")outputEmail: alice@example.comself._email ← None
110@email.deleter111def email(self⟨User C⟩):112 print("Removing email...")113 self._email→ None = NoneoutputRemoving email...print(f"After delete: {user.email}")
118del user.email119print(f"After delete: {user.emailNone}")def email(self):
pass 2 of 2101@property102def email(self⟨User C⟩):103 return self._emailNoneprint(f"After delete: {user.email}")
118del user.email119print(f"After delete: {user.emailNone}")120121print("\n=== Property Naming Patterns ===")122123class Temperature:124 def __init__(self, celsius):125 self._celsius = celsius126 127 @property128 def celsius(self):129 return self._celsius130 131 @celsius.setter132 def celsius(self, value):133 if value >= -273.15:134 self._celsius = value135 136 @property137 def fahrenheit(self):138 return self._celsius * 9/5 + 32139 140 @fahrenheit.setter141 def fahrenheit(self, value):142 self._celsius = (value - 32) * 5/9143144temp = Temperature(0)145print(f"0°C = {temp.fahrenheit}°F")outputAfter delete: None === Property Naming Patterns ===self._celsius ← 0
123class Temperature:124 def __init__(self⟨Temperature D⟩, celsius0):125 self._celsius→ 0 = celsius0temp ← ⟨Temperature D⟩
144temp→ ⟨Temperature D⟩ = Temperature(0)145print(f"0°C = {temp.fahrenheit32.0}°F")def fahrenheit(self):
pass 1 of 2136@property137def fahrenheit(self⟨Temperature D⟩):138 return self._celsius0 * 9/5 + 32print(f"0°C = {temp.fahrenheit}°F")
144temp = Temperature(0)145print(f"0°C = {temp.fahrenheit32.0}°F")146147temp.fahrenheit = 212 # Set via Fahrenheit148print(f"{temp.fahrenheit}°F = {temp.celsius}°C")output0°C = 32.0°Fself._celsius ← 100.0
140@fahrenheit.setter141def fahrenheit(self⟨Temperature D⟩, value212):142 self._celsius→ 100.0 = (value212 - 32) * 5/9temp.fahrenheit ← 212.0
147temp.fahrenheit→ 212.0 = 212 # Set via Fahrenheit148print(f"{temp.fahrenheit212.0}°F = {temp.celsius100.0}°C")def fahrenheit(self):
pass 2 of 2136@property137def fahrenheit(self⟨Temperature D⟩):138 return self._celsius100.0 * 9/5 + 32def celsius(self):
127@property128def celsius(self⟨Temperature D⟩):129 return self._celsius100.0print(f"{temp.fahrenheit}°F = {temp.celsius}°C")
147temp.fahrenheit = 212 # Set via Fahrenheit148print(f"{temp.fahrenheit212.0}°F = {temp.celsius100.0}°C")output212.0°F = 100.0°C
print("=== Basic @property === ")
3print("=== Basic @property ===\n")45class Circle:6 def __init__(self, radius):7 self._radius = radius8 9 @property10 def radius(self):11 """Get the radius."""12 return self._radius13 14 @radius.setter15 def radius(self, value):16 """Set the radius with validation."""17 if value > 0:18 self._radius = value19 else:20 print(f"Invalid radius: {value}")2122circle = Circle(12)output=== Basic @property ===self._radius ← 12
5class Circle:6 def __init__(self⟨Circle A⟩, radius12):7 self._radius→ 12 = radius12circle ← ⟨Circle A⟩
22circle→ ⟨Circle A⟩ = Circle(12)2324# Access like attribute25print(f"Radius: {circle.radius12}") # Calls getterdef radius(self):
pass 1 of 39@property10def radius(self⟨Circle A⟩):11 """Get the radius."""12 return self._radius12All 3 passes — pass 1 is the card above pass self._radius1 12 2 10 3 10 print(f"Radius: {circle.radius}") # Calls getter
24# Access like attribute25print(f"Radius: {circle.radius12}") # Calls getter2627# Assign like attribute28circle.radius = 10 # Calls setter29print(f"New radius: {circle.radius}")outputRadius: 12def radius(self, value):
pass 1 of 214@radius.setter15def radius(self⟨Circle A⟩, value10):16 """Set the radius with validation."""17 if value > 0:self._radius ← 10
16"""Set the radius with validation."""17if value10 > 0:18 self._radius→ 10 = value1019else:circle.radius ← 10
27# Assign like attribute28circle.radius→ 10 = 10 # Calls setter29print(f"New radius: {circle.radius10}")print(f"New radius: {circle.radius}")
28circle.radius = 10 # Calls setter29print(f"New radius: {circle.radius10}")3031# Validation still works32circle.radius = -5 # Rejected33print(f"Still: {circle.radius}")outputNew radius: 10def radius(self, value):
pass 2 of 214@radius.setter15def radius(self⟨Circle A⟩, value-5):16 """Set the radius with validation."""17 if value > 0:else:
17if value > 0:18 self._radius = value19else:20 print(f"Invalid radius: {value-5}")outputInvalid radius: -5circle.radius ← 10
31# Validation still works32circle.radius→ 10 = -5 # Rejected33print(f"Still: {circle.radius10}")print(f"Still: {circle.radius}")
32circle.radius = -5 # Rejected33print(f"Still: {circle.radius10}")3435print("\n=== Properties vs Direct Access ===")3637# Compare syntax:38print("""39Traditional:40 value = obj.get_attribute()41 obj.set_attribute(value)4243With @property:44 value = obj.attribute45 obj.attribute = value4647Same control, cleaner syntax!48""")4950print("=== Multiple Properties ===")5152class Rectangle:53 def __init__(self, width, height):54 self._width = width55 self._height = height56 57 @property58 def width(self):59 return self._width60 61 @width.setter62 def width(self, value):63 if value > 0:64 self._width = value65 66 @property67 def height(self):68 return self._height69 70 @height.setter71 def height(self, value):72 if value > 0:73 self._height = value74 75 @property76 def area(self):77 """Computed property - no setter."""78 return self._width * self._height79 80 @property81 def perimeter(self):82 return 2 * (self._width + self._height)8384rect = Rectangle(5, 3)85print(f"Size: {rect.width} x {rect.height}")outputStill: 10 === Properties vs Direct Access === Traditional: value = obj.get_attribute() obj.set_attribute(value) With @property: value = obj.attribute obj.attribute = value Same control, cleaner syntax! === Multiple Properties ===self._width ← 5, self._height ← 3
52class Rectangle:53 def __init__(self⟨Rectangle B⟩, width5, height3):54 self._width→ 5 = width555 self._height→ 3 = height3rect ← ⟨Rectangle B⟩
84rect→ ⟨Rectangle B⟩ = Rectangle(5, 3)85print(f"Size: {rect.width5} x {rect.height3}")86print(f"Area: {rect.area}")def width(self):
pass 1 of 257@property58def width(self⟨Rectangle B⟩):59 return self._width5def height(self):
pass 1 of 266@property67def height(self⟨Rectangle B⟩):68 return self._height3print(f"Size: {rect.width} x {rect.height}")
84rect = Rectangle(5, 3)85print(f"Size: {rect.width5} x {rect.height3}")86print(f"Area: {rect.area15}")87print(f"Perimeter: {rect.perimeter}")outputSize: 5 x 3def area(self):
pass 1 of 275@property76def area(self⟨Rectangle B⟩):77 """Computed property - no setter."""78 return self._width5 * self._height3print(f"Area: {rect.area}")
85print(f"Size: {rect.width} x {rect.height}")86print(f"Area: {rect.area15}")87print(f"Perimeter: {rect.perimeter16}")outputArea: 15def perimeter(self):
80@property81def perimeter(self⟨Rectangle B⟩):82 return 2 * (self._width5 + self._height3)print(f"Perimeter: {rect.perimeter}")
86print(f"Area: {rect.area}")87print(f"Perimeter: {rect.perimeter16}")8889# Change dimensions90rect.width = 1091print(f"\nAfter resize:")outputPerimeter: 16def width(self, value):
61@width.setter62def width(self⟨Rectangle B⟩, value10):63 if value > 0:64 self._width = valueself._width ← 10
62def width(self, value):63 if value10 > 0:64 self._width→ 10 = value10rect.width ← 10
89# Change dimensions90rect.width→ 10 = 1091print(f"\nAfter resize:")92print(f"Size: {rect.width10} x {rect.height3}")93print(f"Area: {rect.area}") # Automatically updated!output After resize:def width(self):
pass 2 of 257@property58def width(self⟨Rectangle B⟩):59 return self._width10def height(self):
pass 2 of 266@property67def height(self⟨Rectangle B⟩):68 return self._height3print(f"Size: {rect.width} x {rect.height}")
91print(f"\nAfter resize:")92print(f"Size: {rect.width10} x {rect.height3}")93print(f"Area: {rect.area30}") # Automatically updated!outputSize: 10 x 3def area(self):
pass 2 of 275@property76def area(self⟨Rectangle B⟩):77 """Computed property - no setter."""78 return self._width10 * self._height3print(f"Area: {rect.area}") # Automatically updated!
92print(f"Size: {rect.width} x {rect.height}")93print(f"Area: {rect.area30}") # Automatically updated!9495print("\n=== Property with Deleter ===")9697class User:98 def __init__(self, email):99 self._email = email100 101 @property102 def email(self):103 return self._email104 105 @email.setter106 def email(self, value):107 if "@" in value:108 self._email = value109 110 @email.deleter111 def email(self):112 print("Removing email...")113 self._email = None114115user = User("alice@example.com")116print(f"Email: {user.email}")outputArea: 30 === Property with Deleter ===self._email ← alice@example.com
97class User:98 def __init__(self⟨User C⟩, emailalice@example.com):99 self._email→ alice@example.com = emailalice@example.comuser ← ⟨User C⟩
115user→ ⟨User C⟩ = User("alice@example.com")116print(f"Email: {user.emailalice@example.com}")def email(self):
pass 1 of 2101@property102def email(self⟨User C⟩):103 return self._emailalice@example.comprint(f"Email: {user.email}")
115user = User("alice@example.com")116print(f"Email: {user.emailalice@example.com}")outputEmail: alice@example.comself._email ← None
110@email.deleter111def email(self⟨User C⟩):112 print("Removing email...")113 self._email→ None = NoneoutputRemoving email...print(f"After delete: {user.email}")
118del user.email119print(f"After delete: {user.emailNone}")def email(self):
pass 2 of 2101@property102def email(self⟨User C⟩):103 return self._emailNoneprint(f"After delete: {user.email}")
118del user.email119print(f"After delete: {user.emailNone}")120121print("\n=== Property Naming Patterns ===")122123class Temperature:124 def __init__(self, celsius):125 self._celsius = celsius126 127 @property128 def celsius(self):129 return self._celsius130 131 @celsius.setter132 def celsius(self, value):133 if value >= -273.15:134 self._celsius = value135 136 @property137 def fahrenheit(self):138 return self._celsius * 9/5 + 32139 140 @fahrenheit.setter141 def fahrenheit(self, value):142 self._celsius = (value - 32) * 5/9143144temp = Temperature(0)145print(f"0°C = {temp.fahrenheit}°F")outputAfter delete: None === Property Naming Patterns ===self._celsius ← 0
123class Temperature:124 def __init__(self⟨Temperature D⟩, celsius0):125 self._celsius→ 0 = celsius0temp ← ⟨Temperature D⟩
144temp→ ⟨Temperature D⟩ = Temperature(0)145print(f"0°C = {temp.fahrenheit32.0}°F")def fahrenheit(self):
pass 1 of 2136@property137def fahrenheit(self⟨Temperature D⟩):138 return self._celsius0 * 9/5 + 32print(f"0°C = {temp.fahrenheit}°F")
144temp = Temperature(0)145print(f"0°C = {temp.fahrenheit32.0}°F")146147temp.fahrenheit = 212 # Set via Fahrenheit148print(f"{temp.fahrenheit}°F = {temp.celsius}°C")output0°C = 32.0°Fself._celsius ← 100.0
140@fahrenheit.setter141def fahrenheit(self⟨Temperature D⟩, value212):142 self._celsius→ 100.0 = (value212 - 32) * 5/9temp.fahrenheit ← 212.0
147temp.fahrenheit→ 212.0 = 212 # Set via Fahrenheit148print(f"{temp.fahrenheit212.0}°F = {temp.celsius100.0}°C")def fahrenheit(self):
pass 2 of 2136@property137def fahrenheit(self⟨Temperature D⟩):138 return self._celsius100.0 * 9/5 + 32def celsius(self):
127@property128def celsius(self⟨Temperature D⟩):129 return self._celsius100.0print(f"{temp.fahrenheit}°F = {temp.celsius}°C")
147temp.fahrenheit = 212 # Set via Fahrenheit148print(f"{temp.fahrenheit212.0}°F = {temp.celsius100.0}°C")output212.0°F = 100.0°C
print("=== Basic @property === ")
3print("=== Basic @property ===\n")45class Circle:6 def __init__(self, radius):7 self._radius = radius8 9 @property10 def radius(self):11 """Get the radius."""12 return self._radius13 14 @radius.setter15 def radius(self, value):16 """Set the radius with validation."""17 if value > 0:18 self._radius = value19 else:20 print(f"Invalid radius: {value}")2122circle = Circle(5)output=== Basic @property ===self._radius ← 5
5class Circle:6 def __init__(self⟨Circle A⟩, radius5):7 self._radius→ 5 = radius5circle ← ⟨Circle A⟩
22circle→ ⟨Circle A⟩ = Circle(5)2324# Access like attribute25print(f"Radius: {circle.radius5}") # Calls getterdef radius(self):
pass 1 of 39@property10def radius(self⟨Circle A⟩):11 """Get the radius."""12 return self._radius5All 3 passes — pass 1 is the card above pass self._radius1 5 2 10 3 10 print(f"Radius: {circle.radius}") # Calls getter
24# Access like attribute25print(f"Radius: {circle.radius5}") # Calls getter2627# Assign like attribute28circle.radius = 10 # Calls setter29print(f"New radius: {circle.radius}")outputRadius: 5def radius(self, value):
pass 1 of 214@radius.setter15def radius(self⟨Circle A⟩, value10):16 """Set the radius with validation."""17 if value > 0:self._radius ← 10
16"""Set the radius with validation."""17if value10 > 0:18 self._radius→ 10 = value1019else:circle.radius ← 10
27# Assign like attribute28circle.radius→ 10 = 10 # Calls setter29print(f"New radius: {circle.radius10}")print(f"New radius: {circle.radius}")
28circle.radius = 10 # Calls setter29print(f"New radius: {circle.radius10}")3031# Validation still works32circle.radius = -5 # Rejected33print(f"Still: {circle.radius}")outputNew radius: 10def radius(self, value):
pass 2 of 214@radius.setter15def radius(self⟨Circle A⟩, value-5):16 """Set the radius with validation."""17 if value > 0:else:
17if value > 0:18 self._radius = value19else:20 print(f"Invalid radius: {value-5}")outputInvalid radius: -5circle.radius ← 10
31# Validation still works32circle.radius→ 10 = -5 # Rejected33print(f"Still: {circle.radius10}")print(f"Still: {circle.radius}")
32circle.radius = -5 # Rejected33print(f"Still: {circle.radius10}")3435print("\n=== Properties vs Direct Access ===")3637# Compare syntax:38print("""39Traditional:40 value = obj.get_attribute()41 obj.set_attribute(value)4243With @property:44 value = obj.attribute45 obj.attribute = value4647Same control, cleaner syntax!48""")4950print("=== Multiple Properties ===")5152class Rectangle:53 def __init__(self, width, height):54 self._width = width55 self._height = height56 57 @property58 def width(self):59 return self._width60 61 @width.setter62 def width(self, value):63 if value > 0:64 self._width = value65 66 @property67 def height(self):68 return self._height69 70 @height.setter71 def height(self, value):72 if value > 0:73 self._height = value74 75 @property76 def area(self):77 """Computed property - no setter."""78 return self._width * self._height79 80 @property81 def perimeter(self):82 return 2 * (self._width + self._height)8384rect = Rectangle(4, 4)85print(f"Size: {rect.width} x {rect.height}")outputStill: 10 === Properties vs Direct Access === Traditional: value = obj.get_attribute() obj.set_attribute(value) With @property: value = obj.attribute obj.attribute = value Same control, cleaner syntax! === Multiple Properties ===self._width ← 4, self._height ← 4
52class Rectangle:53 def __init__(self⟨Rectangle B⟩, width4, height4):54 self._width→ 4 = width455 self._height→ 4 = height4rect ← ⟨Rectangle B⟩
84rect→ ⟨Rectangle B⟩ = Rectangle(4, 4)85print(f"Size: {rect.width4} x {rect.height4}")86print(f"Area: {rect.area}")def width(self):
pass 1 of 257@property58def width(self⟨Rectangle B⟩):59 return self._width4def height(self):
pass 1 of 266@property67def height(self⟨Rectangle B⟩):68 return self._height4print(f"Size: {rect.width} x {rect.height}")
84rect = Rectangle(4, 4)85print(f"Size: {rect.width4} x {rect.height4}")86print(f"Area: {rect.area16}")87print(f"Perimeter: {rect.perimeter}")outputSize: 4 x 4def area(self):
pass 1 of 275@property76def area(self⟨Rectangle B⟩):77 """Computed property - no setter."""78 return self._width4 * self._height4print(f"Area: {rect.area}")
85print(f"Size: {rect.width} x {rect.height}")86print(f"Area: {rect.area16}")87print(f"Perimeter: {rect.perimeter16}")outputArea: 16def perimeter(self):
80@property81def perimeter(self⟨Rectangle B⟩):82 return 2 * (self._width4 + self._height4)print(f"Perimeter: {rect.perimeter}")
86print(f"Area: {rect.area}")87print(f"Perimeter: {rect.perimeter16}")8889# Change dimensions90rect.width = 1091print(f"\nAfter resize:")outputPerimeter: 16def width(self, value):
61@width.setter62def width(self⟨Rectangle B⟩, value10):63 if value > 0:64 self._width = valueself._width ← 10
62def width(self, value):63 if value10 > 0:64 self._width→ 10 = value10rect.width ← 10
89# Change dimensions90rect.width→ 10 = 1091print(f"\nAfter resize:")92print(f"Size: {rect.width10} x {rect.height4}")93print(f"Area: {rect.area}") # Automatically updated!output After resize:def width(self):
pass 2 of 257@property58def width(self⟨Rectangle B⟩):59 return self._width10def height(self):
pass 2 of 266@property67def height(self⟨Rectangle B⟩):68 return self._height4print(f"Size: {rect.width} x {rect.height}")
91print(f"\nAfter resize:")92print(f"Size: {rect.width10} x {rect.height4}")93print(f"Area: {rect.area40}") # Automatically updated!outputSize: 10 x 4def area(self):
pass 2 of 275@property76def area(self⟨Rectangle B⟩):77 """Computed property - no setter."""78 return self._width10 * self._height4print(f"Area: {rect.area}") # Automatically updated!
92print(f"Size: {rect.width} x {rect.height}")93print(f"Area: {rect.area40}") # Automatically updated!9495print("\n=== Property with Deleter ===")9697class User:98 def __init__(self, email):99 self._email = email100 101 @property102 def email(self):103 return self._email104 105 @email.setter106 def email(self, value):107 if "@" in value:108 self._email = value109 110 @email.deleter111 def email(self):112 print("Removing email...")113 self._email = None114115user = User("alice@example.com")116print(f"Email: {user.email}")outputArea: 40 === Property with Deleter ===self._email ← alice@example.com
97class User:98 def __init__(self⟨User C⟩, emailalice@example.com):99 self._email→ alice@example.com = emailalice@example.comuser ← ⟨User C⟩
115user→ ⟨User C⟩ = User("alice@example.com")116print(f"Email: {user.emailalice@example.com}")def email(self):
pass 1 of 2101@property102def email(self⟨User C⟩):103 return self._emailalice@example.comprint(f"Email: {user.email}")
115user = User("alice@example.com")116print(f"Email: {user.emailalice@example.com}")outputEmail: alice@example.comself._email ← None
110@email.deleter111def email(self⟨User C⟩):112 print("Removing email...")113 self._email→ None = NoneoutputRemoving email...print(f"After delete: {user.email}")
118del user.email119print(f"After delete: {user.emailNone}")def email(self):
pass 2 of 2101@property102def email(self⟨User C⟩):103 return self._emailNoneprint(f"After delete: {user.email}")
118del user.email119print(f"After delete: {user.emailNone}")120121print("\n=== Property Naming Patterns ===")122123class Temperature:124 def __init__(self, celsius):125 self._celsius = celsius126 127 @property128 def celsius(self):129 return self._celsius130 131 @celsius.setter132 def celsius(self, value):133 if value >= -273.15:134 self._celsius = value135 136 @property137 def fahrenheit(self):138 return self._celsius * 9/5 + 32139 140 @fahrenheit.setter141 def fahrenheit(self, value):142 self._celsius = (value - 32) * 5/9143144temp = Temperature(0)145print(f"0°C = {temp.fahrenheit}°F")outputAfter delete: None === Property Naming Patterns ===self._celsius ← 0
123class Temperature:124 def __init__(self⟨Temperature D⟩, celsius0):125 self._celsius→ 0 = celsius0temp ← ⟨Temperature D⟩
144temp→ ⟨Temperature D⟩ = Temperature(0)145print(f"0°C = {temp.fahrenheit32.0}°F")def fahrenheit(self):
pass 1 of 2136@property137def fahrenheit(self⟨Temperature D⟩):138 return self._celsius0 * 9/5 + 32print(f"0°C = {temp.fahrenheit}°F")
144temp = Temperature(0)145print(f"0°C = {temp.fahrenheit32.0}°F")146147temp.fahrenheit = 212 # Set via Fahrenheit148print(f"{temp.fahrenheit}°F = {temp.celsius}°C")output0°C = 32.0°Fself._celsius ← 100.0
140@fahrenheit.setter141def fahrenheit(self⟨Temperature D⟩, value212):142 self._celsius→ 100.0 = (value212 - 32) * 5/9temp.fahrenheit ← 212.0
147temp.fahrenheit→ 212.0 = 212 # Set via Fahrenheit148print(f"{temp.fahrenheit212.0}°F = {temp.celsius100.0}°C")def fahrenheit(self):
pass 2 of 2136@property137def fahrenheit(self⟨Temperature D⟩):138 return self._celsius100.0 * 9/5 + 32def celsius(self):
127@property128def celsius(self⟨Temperature D⟩):129 return self._celsius100.0print(f"{temp.fahrenheit}°F = {temp.celsius}°C")
147temp.fahrenheit = 212 # Set via Fahrenheit148print(f"{temp.fahrenheit212.0}°F = {temp.celsius100.0}°C")output212.0°F = 100.0°C
print("=== Basic @property === ")
3print("=== Basic @property ===\n")45class Circle:6 def __init__(self, radius):7 self._radius = radius8 9 @property10 def radius(self):11 """Get the radius."""12 return self._radius13 14 @radius.setter15 def radius(self, value):16 """Set the radius with validation."""17 if value > 0:18 self._radius = value19 else:20 print(f"Invalid radius: {value}")2122circle = Circle(5)output=== Basic @property ===self._radius ← 5
5class Circle:6 def __init__(self⟨Circle A⟩, radius5):7 self._radius→ 5 = radius5circle ← ⟨Circle A⟩
22circle→ ⟨Circle A⟩ = Circle(5)2324# Access like attribute25print(f"Radius: {circle.radius5}") # Calls getterdef radius(self):
pass 1 of 39@property10def radius(self⟨Circle A⟩):11 """Get the radius."""12 return self._radius5All 3 passes — pass 1 is the card above pass self._radius1 5 2 10 3 10 print(f"Radius: {circle.radius}") # Calls getter
24# Access like attribute25print(f"Radius: {circle.radius5}") # Calls getter2627# Assign like attribute28circle.radius = 10 # Calls setter29print(f"New radius: {circle.radius}")outputRadius: 5def radius(self, value):
pass 1 of 214@radius.setter15def radius(self⟨Circle A⟩, value10):16 """Set the radius with validation."""17 if value > 0:self._radius ← 10
16"""Set the radius with validation."""17if value10 > 0:18 self._radius→ 10 = value1019else:circle.radius ← 10
27# Assign like attribute28circle.radius→ 10 = 10 # Calls setter29print(f"New radius: {circle.radius10}")print(f"New radius: {circle.radius}")
28circle.radius = 10 # Calls setter29print(f"New radius: {circle.radius10}")3031# Validation still works32circle.radius = -5 # Rejected33print(f"Still: {circle.radius}")outputNew radius: 10def radius(self, value):
pass 2 of 214@radius.setter15def radius(self⟨Circle A⟩, value-5):16 """Set the radius with validation."""17 if value > 0:else:
17if value > 0:18 self._radius = value19else:20 print(f"Invalid radius: {value-5}")outputInvalid radius: -5circle.radius ← 10
31# Validation still works32circle.radius→ 10 = -5 # Rejected33print(f"Still: {circle.radius10}")print(f"Still: {circle.radius}")
32circle.radius = -5 # Rejected33print(f"Still: {circle.radius10}")3435print("\n=== Properties vs Direct Access ===")3637# Compare syntax:38print("""39Traditional:40 value = obj.get_attribute()41 obj.set_attribute(value)4243With @property:44 value = obj.attribute45 obj.attribute = value4647Same control, cleaner syntax!48""")4950print("=== Multiple Properties ===")5152class Rectangle:53 def __init__(self, width, height):54 self._width = width55 self._height = height56 57 @property58 def width(self):59 return self._width60 61 @width.setter62 def width(self, value):63 if value > 0:64 self._width = value65 66 @property67 def height(self):68 return self._height69 70 @height.setter71 def height(self, value):72 if value > 0:73 self._height = value74 75 @property76 def area(self):77 """Computed property - no setter."""78 return self._width * self._height79 80 @property81 def perimeter(self):82 return 2 * (self._width + self._height)8384rect = Rectangle(8, 2)85print(f"Size: {rect.width} x {rect.height}")outputStill: 10 === Properties vs Direct Access === Traditional: value = obj.get_attribute() obj.set_attribute(value) With @property: value = obj.attribute obj.attribute = value Same control, cleaner syntax! === Multiple Properties ===self._width ← 8, self._height ← 2
52class Rectangle:53 def __init__(self⟨Rectangle B⟩, width8, height2):54 self._width→ 8 = width855 self._height→ 2 = height2rect ← ⟨Rectangle B⟩
84rect→ ⟨Rectangle B⟩ = Rectangle(8, 2)85print(f"Size: {rect.width8} x {rect.height2}")86print(f"Area: {rect.area}")def width(self):
pass 1 of 257@property58def width(self⟨Rectangle B⟩):59 return self._width8def height(self):
pass 1 of 266@property67def height(self⟨Rectangle B⟩):68 return self._height2print(f"Size: {rect.width} x {rect.height}")
84rect = Rectangle(8, 2)85print(f"Size: {rect.width8} x {rect.height2}")86print(f"Area: {rect.area16}")87print(f"Perimeter: {rect.perimeter}")outputSize: 8 x 2def area(self):
pass 1 of 275@property76def area(self⟨Rectangle B⟩):77 """Computed property - no setter."""78 return self._width8 * self._height2print(f"Area: {rect.area}")
85print(f"Size: {rect.width} x {rect.height}")86print(f"Area: {rect.area16}")87print(f"Perimeter: {rect.perimeter20}")outputArea: 16def perimeter(self):
80@property81def perimeter(self⟨Rectangle B⟩):82 return 2 * (self._width8 + self._height2)print(f"Perimeter: {rect.perimeter}")
86print(f"Area: {rect.area}")87print(f"Perimeter: {rect.perimeter20}")8889# Change dimensions90rect.width = 1091print(f"\nAfter resize:")outputPerimeter: 20def width(self, value):
61@width.setter62def width(self⟨Rectangle B⟩, value10):63 if value > 0:64 self._width = valueself._width ← 10
62def width(self, value):63 if value10 > 0:64 self._width→ 10 = value10rect.width ← 10
89# Change dimensions90rect.width→ 10 = 1091print(f"\nAfter resize:")92print(f"Size: {rect.width10} x {rect.height2}")93print(f"Area: {rect.area}") # Automatically updated!output After resize:def width(self):
pass 2 of 257@property58def width(self⟨Rectangle B⟩):59 return self._width10def height(self):
pass 2 of 266@property67def height(self⟨Rectangle B⟩):68 return self._height2print(f"Size: {rect.width} x {rect.height}")
91print(f"\nAfter resize:")92print(f"Size: {rect.width10} x {rect.height2}")93print(f"Area: {rect.area20}") # Automatically updated!outputSize: 10 x 2def area(self):
pass 2 of 275@property76def area(self⟨Rectangle B⟩):77 """Computed property - no setter."""78 return self._width10 * self._height2print(f"Area: {rect.area}") # Automatically updated!
92print(f"Size: {rect.width} x {rect.height}")93print(f"Area: {rect.area20}") # Automatically updated!9495print("\n=== Property with Deleter ===")9697class User:98 def __init__(self, email):99 self._email = email100 101 @property102 def email(self):103 return self._email104 105 @email.setter106 def email(self, value):107 if "@" in value:108 self._email = value109 110 @email.deleter111 def email(self):112 print("Removing email...")113 self._email = None114115user = User("alice@example.com")116print(f"Email: {user.email}")outputArea: 20 === Property with Deleter ===self._email ← alice@example.com
97class User:98 def __init__(self⟨User C⟩, emailalice@example.com):99 self._email→ alice@example.com = emailalice@example.comuser ← ⟨User C⟩
115user→ ⟨User C⟩ = User("alice@example.com")116print(f"Email: {user.emailalice@example.com}")def email(self):
pass 1 of 2101@property102def email(self⟨User C⟩):103 return self._emailalice@example.comprint(f"Email: {user.email}")
115user = User("alice@example.com")116print(f"Email: {user.emailalice@example.com}")outputEmail: alice@example.comself._email ← None
110@email.deleter111def email(self⟨User C⟩):112 print("Removing email...")113 self._email→ None = NoneoutputRemoving email...print(f"After delete: {user.email}")
118del user.email119print(f"After delete: {user.emailNone}")def email(self):
pass 2 of 2101@property102def email(self⟨User C⟩):103 return self._emailNoneprint(f"After delete: {user.email}")
118del user.email119print(f"After delete: {user.emailNone}")120121print("\n=== Property Naming Patterns ===")122123class Temperature:124 def __init__(self, celsius):125 self._celsius = celsius126 127 @property128 def celsius(self):129 return self._celsius130 131 @celsius.setter132 def celsius(self, value):133 if value >= -273.15:134 self._celsius = value135 136 @property137 def fahrenheit(self):138 return self._celsius * 9/5 + 32139 140 @fahrenheit.setter141 def fahrenheit(self, value):142 self._celsius = (value - 32) * 5/9143144temp = Temperature(0)145print(f"0°C = {temp.fahrenheit}°F")outputAfter delete: None === Property Naming Patterns ===self._celsius ← 0
123class Temperature:124 def __init__(self⟨Temperature D⟩, celsius0):125 self._celsius→ 0 = celsius0temp ← ⟨Temperature D⟩
144temp→ ⟨Temperature D⟩ = Temperature(0)145print(f"0°C = {temp.fahrenheit32.0}°F")def fahrenheit(self):
pass 1 of 2136@property137def fahrenheit(self⟨Temperature D⟩):138 return self._celsius0 * 9/5 + 32print(f"0°C = {temp.fahrenheit}°F")
144temp = Temperature(0)145print(f"0°C = {temp.fahrenheit32.0}°F")146147temp.fahrenheit = 212 # Set via Fahrenheit148print(f"{temp.fahrenheit}°F = {temp.celsius}°C")output0°C = 32.0°Fself._celsius ← 100.0
140@fahrenheit.setter141def fahrenheit(self⟨Temperature D⟩, value212):142 self._celsius→ 100.0 = (value212 - 32) * 5/9temp.fahrenheit ← 212.0
147temp.fahrenheit→ 212.0 = 212 # Set via Fahrenheit148print(f"{temp.fahrenheit212.0}°F = {temp.celsius100.0}°C")def fahrenheit(self):
pass 2 of 2136@property137def fahrenheit(self⟨Temperature D⟩):138 return self._celsius100.0 * 9/5 + 32def celsius(self):
127@property128def celsius(self⟨Temperature D⟩):129 return self._celsius100.0print(f"{temp.fahrenheit}°F = {temp.celsius}°C")
147temp.fahrenheit = 212 # Set via Fahrenheit148print(f"{temp.fahrenheit212.0}°F = {temp.celsius100.0}°C")output212.0°F = 100.0°C
@property creates a getter. @name.setter creates a setter. Clean syntax.
Read-only properties
Attributes that can't be changed after creation.
# Read-Only and Computed Properties
print("=== Read-Only Properties ===\n")
class ImmutablePoint:
def __init__(self, x, y):
self._x = x
self._y = y
@property
def x(self):
return self._x
@property
def y(self):
return self._y
# No setters! Can't modify x or y
point = ImmutablePoint(3, 4)
print(f"Point: ({point.x}, {point.y})")
# Reading works
print(f"X coordinate: {point.x}")
# Writing fails
try:
point.x = 10 # AttributeError!
except AttributeError as e:
print(f"Error: can't set x - {e}")
print("\n=== Computed Read-Only Properties ===")
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value > 0:
self._radius = value
@property
def diameter(self):
return self._radius * 2
@property
def circumference(self):
import math
return 2 * math.pi * self._radius
@property
def area(self):
import math
return math.pi * self._radius ** 2
circle = Circle(5)
print(f"Radius: {circle.radius}")
print(f"Diameter: {circle.diameter}")
print(f"Circumference: {circle.circumference:.2f}")
print(f"Area: {circle.area:.2f}")
# Change radius - computed properties update
circle.radius = 10
print(f"\nAfter radius = 10:")
print(f"Diameter: {circle.diameter}")
print(f"Area: {circle.area:.2f}")
print("\n=== ID and Timestamp Properties ===")
class Document:
_next_id = 1
def __init__(self, title, content):
self._id = Document._next_id
Document._next_id += 1
self._created = 1736937000.0
self._title = title
self._content = content
@property
def id(self):
return self._id
@property
def created(self):
return self._created
@property
def title(self):
return self._title
@title.setter
def title(self, value):
self._title = value
@property
def content(self):
return self._content
@content.setter
def content(self, value):
self._content = value
doc = Document("Report", "Initial content")
print(f"ID: {doc.id}")
print(f"Created: {doc.created}")
print(f"Title: {doc.title}")
# Can modify title and content
doc.title = "Updated Report"
print(f"New title: {doc.title}")
# Can't modify id or created
try:
doc.id = 999
except AttributeError:
print("Can't change ID!")
print("\n=== Derived Properties ===")
class Person:
def __init__(self, first_name, last_name, birth_year):
self._first = first_name
self._last = last_name
self._birth_year = birth_year
@property
def first_name(self):
return self._first
@property
def last_name(self):
return self._last
@property
def full_name(self):
return f"{self._first} {self._last}"
@property
def initials(self):
return f"{self._first[0]}.{self._last[0]}."
@property
def age(self):
return 2024 - self._birth_year
person = Person("John", "Doe", 1990)
print(f"Name: {person.full_name}")
print(f"Initials: {person.initials}")
print(f"Age: {person.age}")
print("\n=== Conditional Properties ===")
class Order:
def __init__(self, items, discount=0):
self._items = items
self._discount = discount
@property
def subtotal(self):
return sum(item["price"] * item["qty"] for item in self._items)
@property
def discount_amount(self):
if self.subtotal > 100: # Only if over $100
return self.subtotal * (self._discount / 100)
return 0
@property
def total(self):
return self.subtotal - self.discount_amount
@property
def is_eligible_for_discount(self):
return self.subtotal > 100
items = [
{"name": "Book", "price": 25, "qty": 2},
{"name": "Pen", "price": 5, "qty": 10}
]
order = Order(items, discount=10)
print(f"Subtotal: ${order.subtotal}")
print(f"Eligible for discount: {order.is_eligible_for_discount}")
print(f"Discount (10%): ${order.discount_amount}")
print(f"Total: ${order.total}")
print("=== Read-Only Properties === ")
3print("=== Read-Only Properties ===\n")45class ImmutablePoint:6 def __init__(self, x, y):7 self._x = x #?storeprivate8 self._y = y9 10 @property11 def x(self): #?readonly12 return self._x13 14 @property15 def y(self):16 return self._y17 18 # No setters! Can't modify x or y #?nosetter1920point = ImmutablePoint(3, 4)21print(f"Point: ({point.x}, {point.y})")output=== Read-Only Properties ===self._x ← 3, self._y ← 4
5class ImmutablePoint:6 def __init__(self⟨ImmutablePoint A⟩, x3, y4):7 self._x→ 3 = x3 #?storeprivate8 self._y→ 4 = y4point ← ⟨ImmutablePoint A⟩
20point→ ⟨ImmutablePoint A⟩ = ImmutablePoint(3, 4)21print(f"Point: ({point.x3}, {point.y4})")def x(self): #?readonly
pass 1 of 210@property11def x(self⟨ImmutablePoint A⟩): #?readonly12 return self._x3def y(self):
14@property15def y(self⟨ImmutablePoint A⟩):16 return self._y4print(f"Point: ({point.x}, {point.y})")
20point = ImmutablePoint(3, 4)21print(f"Point: ({point.x3}, {point.y4})")2223# Reading works24print(f"X coordinate: {point.x3}")outputPoint: (3, 4)def x(self): #?readonly
pass 2 of 210@property11def x(self⟨ImmutablePoint A⟩): #?readonly12 return self._x3print(f"X coordinate: {point.x}")
23# Reading works24print(f"X coordinate: {point.x3}")outputX coordinate: 3except AttributeError as e:
28 point.x = 10 # AttributeError!29except AttributeError as e:30 print(f"Error: can't set x - {eproperty 'x' of 'ImmutablePoint' object has no setter}")outputError: can't set x - property 'x' of 'ImmutablePoint' object has no setterprint(" === Computed Read-Only Properties ===")
32print("\n=== Computed Read-Only Properties ===")3334class Circle:35 def __init__(self, radius):36 self._radius = radius37 38 @property39 def radius(self):40 return self._radius41 42 @radius.setter43 def radius(self, value):44 if value > 0:45 self._radius = value46 47 @property48 def diameter(self): #?computed149 return self._radius * 250 51 @property52 def circumference(self): #?computed253 import math54 return 2 * math.pi * self._radius55 56 @property57 def area(self):58 import math59 return math.pi * self._radius ** 26061circle = Circle(5)62print(f"Radius: {circle.radius}")output === Computed Read-Only Properties ===self._radius ← 5
34class Circle:35 def __init__(self⟨Circle B⟩, radius5):36 self._radius→ 5 = radius5circle ← ⟨Circle B⟩
61circle→ ⟨Circle B⟩ = Circle(5)62print(f"Radius: {circle.radius5}")63print(f"Diameter: {circle.diameter}")def radius(self):
38@property39def radius(self⟨Circle B⟩):40 return self._radius5print(f"Radius: {circle.radius}")
61circle = Circle(5)62print(f"Radius: {circle.radius5}")63print(f"Diameter: {circle.diameter10}")64print(f"Circumference: {circle.circumference:.2f}")outputRadius: 5def diameter(self): #?computed1
pass 1 of 247@property48def diameter(self⟨Circle B⟩): #?computed149 return self._radius5 * 2print(f"Diameter: {circle.diameter}")
62print(f"Radius: {circle.radius}")63print(f"Diameter: {circle.diameter10}")64print(f"Circumference: {circle.circumference31.41592653589793:.2f}")65print(f"Area: {circle.area:.2f}")outputDiameter: 10def circumference(self): #?computed2
51@property52def circumference(self⟨Circle B⟩): #?computed253 import math54 return 2 * math.pi3.141592653589793 * self._radius5print(f"Circumference: {circle.circumference:.2f}")
63print(f"Diameter: {circle.diameter}")64print(f"Circumference: {circle.circumference31.41592653589793:.2f}")65print(f"Area: {circle.area78.53981633974483:.2f}")outputCircumference: 31.42def area(self):
pass 1 of 256@property57def area(self⟨Circle B⟩):58 import math59 return math.pi3.141592653589793 * self._radius5 ** 2print(f"Area: {circle.area:.2f}")
64print(f"Circumference: {circle.circumference:.2f}")65print(f"Area: {circle.area78.53981633974483:.2f}")6667# Change radius - computed properties update #?autoupdate68circle.radius = 1069print(f"\nAfter radius = 10:")outputArea: 78.54def radius(self, value):
42@radius.setter43def radius(self⟨Circle B⟩, value10):44 if value > 0:45 self._radius = valueself._radius ← 10
43def radius(self, value):44 if value10 > 0:45 self._radius→ 10 = value10circle.radius ← 10
67# Change radius - computed properties update #?autoupdate68circle.radius→ 10 = 1069print(f"\nAfter radius = 10:")70print(f"Diameter: {circle.diameter20}")71print(f"Area: {circle.area:.2f}")output After radius = 10:def diameter(self): #?computed1
pass 2 of 247@property48def diameter(self⟨Circle B⟩): #?computed149 return self._radius10 * 2print(f"Diameter: {circle.diameter}")
69print(f"\nAfter radius = 10:")70print(f"Diameter: {circle.diameter20}")71print(f"Area: {circle.area314.1592653589793:.2f}")outputDiameter: 20def area(self):
pass 2 of 256@property57def area(self⟨Circle B⟩):58 import math59 return math.pi3.141592653589793 * self._radius10 ** 2_next_id ← (empty)
70print(f"Diameter: {circle.diameter}")71print(f"Area: {circle.area314.1592653589793:.2f}")7273print("\n=== ID and Timestamp Properties ===")747576class Document:77 _next_id→ (empty) = 178 79 def __init__(self, title, content):80 self._id = Document._next_id #?setonce81 Document._next_id += 182 self._created = 1736937000.083 self._title = title84 self._content = content85 86 @property87 def id(self): #?idreadonly88 return self._id89 90 @property91 def created(self): #?createdreadonly92 return self._created93 94 @property95 def title(self):96 return self._title97 98 @title.setter #?titlewritable99 def title(self, value):100 self._title = value101 102 @property103 def content(self):104 return self._content105 106 @content.setter107 def content(self, value):108 self._content = value109110doc = Document("Report", "Initial content")111print(f"ID: {doc.id}")outputArea: 314.16 === ID and Timestamp Properties ===self._id ← 1, Document._next_id ← 2, self._created ← 1736937000.0
79def __init__(self⟨Document C⟩, titleReport, contentInitial content):80 self._id→ 1 = Document._next_id1 #?setonce81 Document._next_id→ 2 += 182 self._created→ 1736937000.0 = 1736937000.083 self._title→ Report = titleReport84 self._content→ Initial content = contentInitial contentdoc ← ⟨Document C⟩
110doc→ ⟨Document C⟩ = Document("Report", "Initial content")111print(f"ID: {doc.id1}")112print(f"Created: {doc.created}")def id(self): #?idreadonly
86@property87def id(self⟨Document C⟩): #?idreadonly88 return self._id1print(f"ID: {doc.id}")
110doc = Document("Report", "Initial content")111print(f"ID: {doc.id1}")112print(f"Created: {doc.created1736937000.0}")113print(f"Title: {doc.title}")outputID: 1def created(self): #?createdreadonly
90@property91def created(self⟨Document C⟩): #?createdreadonly92 return self._created1736937000.0print(f"Created: {doc.created}")
111print(f"ID: {doc.id}")112print(f"Created: {doc.created1736937000.0}")113print(f"Title: {doc.titleReport}")outputCreated: 1736937000.0def title(self):
pass 1 of 294@property95def title(self⟨Document C⟩):96 return self._titleReportprint(f"Title: {doc.title}")
112print(f"Created: {doc.created}")113print(f"Title: {doc.titleReport}")114115# Can modify title and content116doc.title = "Updated Report"117print(f"New title: {doc.title}")outputTitle: Reportself._title ← Updated Report
98@title.setter #?titlewritable99def title(self⟨Document C⟩, valueUpdated Report):100 self._title→ Updated Report = valueUpdated Reportdoc.title ← Updated Report
115# Can modify title and content116doc.title→ Updated Report = "Updated Report"117print(f"New title: {doc.titleUpdated Report}")def title(self):
pass 2 of 294@property95def title(self⟨Document C⟩):96 return self._titleUpdated Reportprint(f"New title: {doc.title}")
116doc.title = "Updated Report"117print(f"New title: {doc.titleUpdated Report}")outputNew title: Updated Reportexcept AttributeError:
121 doc.id = 999122except AttributeError:123 print("Can't change ID!")outputCan't change ID!print(" === Derived Properties ===")
125print("\n=== Derived Properties ===")126127class Person:128 def __init__(self, first_name, last_name, birth_year):129 self._first = first_name130 self._last = last_name131 self._birth_year = birth_year132 133 @property134 def first_name(self):135 return self._first136 137 @property138 def last_name(self):139 return self._last140 141 @property142 def full_name(self): #?derived143 return f"{self._first} {self._last}"144 145 @property146 def initials(self):147 return f"{self._first[0]}.{self._last[0]}."148 149 @property150 def age(self): #?agecomputed151 return 2024 - self._birth_year152153person = Person("John", "Doe", 1990)154print(f"Name: {person.full_name}")output === Derived Properties ===self._first ← John, self._last ← Doe, self._birth_year ← 1990
127class Person:128 def __init__(self⟨Person D⟩, first_nameJohn, last_nameDoe, birth_year1990):129 self._first→ John = first_nameJohn130 self._last→ Doe = last_nameDoe131 self._birth_year→ 1990 = birth_year1990person ← ⟨Person D⟩
153person→ ⟨Person D⟩ = Person("John", "Doe", 1990)154print(f"Name: {person.full_nameJohn Doe}")155print(f"Initials: {person.initials}")def full_name(self): #?derived
141@property142def full_name(self⟨Person D⟩): #?derived143 return f"{self._firstJohn} {self._lastDoe}"print(f"Name: {person.full_name}")
153person = Person("John", "Doe", 1990)154print(f"Name: {person.full_nameJohn Doe}")155print(f"Initials: {person.initialsJ.D.}")156print(f"Age: {person.age}")outputName: John Doedef initials(self):
145@property146def initials(self⟨Person D⟩):147 return f"{self._first[0]J}.{self._last[0]D}."print(f"Initials: {person.initials}")
154print(f"Name: {person.full_name}")155print(f"Initials: {person.initialsJ.D.}")156print(f"Age: {person.age34}")outputInitials: J.D.def age(self): #?agecomputed
149@property150def age(self⟨Person D⟩): #?agecomputed151 return 2024 - self._birth_year1990items ← [{'name': 'Book', 'price': 25, 'qty': 2}, {'name': 'Pen', 'price': 5, 'qty': 10}]
155print(f"Initials: {person.initials}")156print(f"Age: {person.age34}")157158print("\n=== Conditional Properties ===")159160class Order:161 def __init__(self, items, discount=0):162 self._items = items163 self._discount = discount164 165 @property166 def subtotal(self):167 return sum(item["price"] * item["qty"] for item in self._items)168 169 @property170 def discount_amount(self): #?conditional171 if self.subtotal > 100: # Only if over $100 #?thresholdcheck172 return self.subtotal * (self._discount / 100)173 return 0174 175 @property176 def total(self):177 return self.subtotal - self.discount_amount178 179 @property180 def is_eligible_for_discount(self): #?boolprop181 return self.subtotal > 100182183items→ [{'name': 'Book', 'price': 25, 'qty': 2}, {'name': 'Pen', 'price': 5, 'qty': 10}] = [184 {"name": "Book", "price": 25, "qty": 2},185 {"name": "Pen", "price": 5, "qty": 10}186]187order = Order(items[{'name': 'Book', 'price': 25, 'qty': 2}, {'name': 'Pen', 'price': 5, 'qty': 10}], discount=10)outputAge: 34 === Conditional Properties ===self._items ← [{'name': 'Book', 'price': 25, 'qty': 2}, {'name': 'Pen', 'price': 5, 'qty': 10}]
160class Order:161 def __init__(self⟨Order E⟩, items[{'name': 'Book', 'price': 25, 'qty': 2}, {'name': 'Pen', 'price': 5, 'qty': 10}], discount10=0):162 self._items→ [{'name': 'Book', 'price': 25, 'qty': 2}, {'name': 'Pen', 'price': 5, 'qty': 10}] = items[{'name': 'Book', 'price': 25, 'qty': 2}, {'name': 'Pen', 'price': 5, 'qty': 10}]163 self._discount→ 10 = discount10order ← ⟨Order E⟩
186]187order→ ⟨Order E⟩ = Order(items[{'name': 'Book', 'price': 25, 'qty': 2}, {'name': 'Pen', 'price': 5, 'qty': 10}], discount=10)188189print(f"Subtotal: ${order.subtotal100}")190print(f"Eligible for discount: {order.is_eligible_for_discount}")def subtotal(self):
pass 1 of 5165@property166def subtotal(self⟨Order E⟩):167 return sum(item["price"](empty) * item["qty"](empty) for item in self._items[{'name': 'Book', 'price': 25, 'qty': 2}, {'name': 'Pen', 'price': 5, 'qty': 10}])print(f"Subtotal: ${order.subtotal}")
189print(f"Subtotal: ${order.subtotal100}")190print(f"Eligible for discount: {order.is_eligible_for_discountFalse}")191print(f"Discount (10%): ${order.discount_amount}")outputSubtotal: $100def is_eligible_for_discount(self): #?boolprop
179@property180def is_eligible_for_discount(self⟨Order E⟩): #?boolprop181 return self.subtotal100 > 100print(f"Eligible for discount: {order.is_eligible_for_discount}")
189print(f"Subtotal: ${order.subtotal}")190print(f"Eligible for discount: {order.is_eligible_for_discountFalse}")191print(f"Discount (10%): ${order.discount_amount0}")192print(f"Total: ${order.total}")outputEligible for discount: Falsedef discount_amount(self): #?conditional
pass 1 of 2169@property170def discount_amount(self⟨Order E⟩): #?conditional171 if self.subtotal > 100: # Only if over $100 #?thresholdcheck172 return self.subtotal * (self._discount / 100)return 0
172 return self.subtotal * (self._discount / 100)173return 0print(f"Discount (10%): ${order.discount_amount}")
190print(f"Eligible for discount: {order.is_eligible_for_discount}")191print(f"Discount (10%): ${order.discount_amount0}")192print(f"Total: ${order.total100}")193#@help storeprivateoutputDiscount (10%): $0def total(self):
175@property176def total(self⟨Order E⟩):177 return self.subtotal100 - self.discount_amount0def discount_amount(self): #?conditional
pass 2 of 2169@property170def discount_amount(self⟨Order E⟩): #?conditional171 if self.subtotal > 100: # Only if over $100 #?thresholdcheck172 return self.subtotal * (self._discount / 100)return 0
172 return self.subtotal * (self._discount / 100)173return 0print(f"Total: ${order.total}")
191print(f"Discount (10%): ${order.discount_amount}")192print(f"Total: ${order.total100}")193#@help storeprivateoutputTotal: $100
Provide getter without setter. Attempts to set raise AttributeError.
Properties with validation
Validate data before setting.
# Properties with Validation
print("=== Type Validation ===\n")
class Product:
def __init__(self, name, price):
self._name = None
self._price = None
# Use setters for validation
self.name = name
self.price = price
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not isinstance(value, str):
raise TypeError("Name must be a string")
if len(value) < 1:
raise ValueError("Name cannot be empty")
self._name = value
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if not isinstance(value, (int, float)):
raise TypeError("Price must be a number")
if value < 0:
raise ValueError("Price cannot be negative")
self._price = round(value, 2)
product = Product("Laptop", 999.999)
print(f"Product: {product.name}, ${product.price}")
# Validation in action
try:
product.price = "free"
except TypeError as e:
print(f"Type error: {e}")
try:
product.price = -50
except ValueError as e:
print(f"Value error: {e}")
print("\n=== Range Validation ===")
class Temperature:
MIN_CELSIUS = -273.15
MAX_CELSIUS = 1000
def __init__(self, celsius):
self._celsius = None
self.celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < self.MIN_CELSIUS:
raise ValueError(f"Below absolute zero: {value}")
if value > self.MAX_CELSIUS:
raise ValueError(f"Too hot: {value}")
self._celsius = value
temp = Temperature(25)
print(f"Temperature: {temp.celsius}°C")
try:
temp.celsius = -300 # Below absolute zero
except ValueError as e:
print(f"Rejected: {e}")
print("\n=== Format Validation ===")
class User:
def __init__(self, email, phone):
self._email = None
self._phone = None
self.email = email
self.phone = phone
@property
def email(self):
return self._email
@email.setter
def email(self, value):
value = value.strip().lower()
if "@" not in value:
raise ValueError("Invalid email: missing @")
parts = value.split("@")
if len(parts) != 2 or not parts[1]:
raise ValueError("Invalid email format")
self._email = value
@property
def phone(self):
return self._phone
@phone.setter
def phone(self, value):
# Remove non-digits
digits = ''.join(c for c in value if c.isdigit())
if len(digits) < 10:
raise ValueError("Phone must have at least 10 digits")
self._phone = digits
user = User(" Alice@Example.COM ", "(555) 123-4567")
print(f"Email: {user.email}")
print(f"Phone: {user.phone}")
print("\n=== Interdependent Validation ===")
class Rectangle:
def __init__(self, width, height, max_area=10000):
self._width = 0
self._height = 0
self._max_area = max_area
self.width = width # Validates
self.height = height # Validates
@property
def width(self):
return self._width
@width.setter
def width(self, value):
if value <= 0:
raise ValueError("Width must be positive")
if value * self._height > self._max_area:
raise ValueError(f"Area would exceed {self._max_area}")
self._width = value
@property
def height(self):
return self._height
@height.setter
def height(self, value):
if value <= 0:
raise ValueError("Height must be positive")
if self._width * value > self._max_area:
raise ValueError(f"Area would exceed {self._max_area}")
self._height = value
@property
def area(self):
return self._width * self._height
rect = Rectangle(50, 100)
print(f"Rectangle: {rect.width}x{rect.height}, area={rect.area}")
try:
rect.width = 200 # Would make area 20000
except ValueError as e:
print(f"Rejected: {e}")
print("\n=== Validation with Warnings ===")
class Score:
def __init__(self, value):
self._value = 0
self._warnings = []
self.value = value
@property
def value(self):
return self._value
@value.setter
def value(self, value):
self._warnings.clear()
if value < 0:
self._warnings.append(f"Adjusted {value} to 0")
value = 0
elif value > 100:
self._warnings.append(f"Adjusted {value} to 100")
value = 100
self._value = value
@property
def warnings(self):
return self._warnings.copy()
score = Score(150)
print(f"Score: {score.value}")
print(f"Warnings: {score.warnings}")
score.value = -20
print(f"Score: {score.value}")
print(f"Warnings: {score.warnings}")
print("=== Type Validation === ")
3print("=== Type Validation ===\n")45class Product:6 def __init__(self, name, price):7 self._name = None8 self._price = None9 # Use setters for validation #?usesetters10 self.name = name11 self.price = price12 13 @property14 def name(self):15 return self._name16 17 @name.setter18 def name(self, value):19 if not isinstance(value, str): #?typecheck20 raise TypeError("Name must be a string")21 if len(value) < 1:22 raise ValueError("Name cannot be empty")23 self._name = value24 25 @property26 def price(self):27 return self._price28 29 @price.setter30 def price(self, value):31 if not isinstance(value, (int, float)): #?numcheck32 raise TypeError("Price must be a number")33 if value < 0:34 raise ValueError("Price cannot be negative")35 self._price = round(value, 2) #?normalize3637product = Product("Laptop", 999.999)38print(f"Product: {product.name}, ${product.price}")output=== Type Validation ===self._name ← None, self._price ← None
5class Product:6 def __init__(self⟨Product A⟩, nameLaptop, price999.999):7 self._name→ None = None8 self._price→ None = None9 # Use setters for validation #?usesetters10 self.name = nameLaptop11 self.price = priceself._name ← Laptop, self.name ← Laptop
9 # Use setters for validation #?usesetters10 self.name→ Laptop = nameLaptop11 self.price = price999.9991213@property14def name(self):15 return self._name1617@name.setter18def name(self⟨Product A⟩, valueLaptop):19 if not isinstance(value, str): #?typecheck20 raise TypeError("Name must be a string")21 if len(value) < 1:22 raise ValueError("Name cannot be empty")23 self._name→ Laptop = valueLaptopself._price ← 1000.0, self.price ← 1000.0
pass 1 of 310 self.name = name11 self.price→ 1000.0 = price999.9991213@property14def name(self):15 return self._name1617@name.setter18def name(self, value):19 if not isinstance(value, str): #?typecheck20 raise TypeError("Name must be a string")21 if len(value) < 1:22 raise ValueError("Name cannot be empty")23 self._name = value2425@property26def price(self):27 return self._price2829@price.setter30def price(self⟨Product A⟩, value999.999):31 if not isinstance(value, (int, float)): #?numcheck32 raise TypeError("Price must be a number")33 if value < 0:34 raise ValueError("Price cannot be negative")35 self._price→ 1000.0 = round(value999.999, 2) #?normalizeAll 3 passes — pass 1 is the card above pass valuepriceeself._priceself.price1 999.999 999.999 — 1000.0 1000.0 2 free — Price must be a number — — 3 -50 — Price cannot be negative — — product ← ⟨Product A⟩
37product→ ⟨Product A⟩ = Product("Laptop", 999.999)38print(f"Product: {product.nameLaptop}, ${product.price1000.0}")def name(self):
13@property14def name(self⟨Product A⟩):15 return self._nameLaptopdef price(self):
25@property26def price(self⟨Product A⟩):27 return self._price1000.0print(f"Product: {product.name}, ${product.price}")
37product = Product("Laptop", 999.999)38print(f"Product: {product.nameLaptop}, ${product.price1000.0}")outputProduct: Laptop, $1000.0if not isinstance(value, (int, float)): #?numcheck
30def price(self, value):31 if not isinstance(valuefree, (int, float)): #?numcheck32 raise TypeError("Price must be a number")33 if value < 0:except TypeError as e:
42 product.price = "free"43except TypeError as e:44 print(f"Type error: {ePrice must be a number}")outputType error: Price must be a numberif value < 0:
32 raise TypeError("Price must be a number")33if value-50 < 0:34 raise ValueError("Price cannot be negative")35self._price = round(value, 2) #?normalizeexcept ValueError as e:
47 product.price = -5048except ValueError as e:49 print(f"Value error: {ePrice cannot be negative}")outputValue error: Price cannot be negativeMIN_CELSIUS ← (empty), MAX_CELSIUS ← (empty)
51print("\n=== Range Validation ===")5253class Temperature:54 MIN_CELSIUS→ (empty) = -273.15 #?classconst55 MAX_CELSIUS→ (empty) = 100056 57 def __init__(self, celsius):58 self._celsius = None59 self.celsius = celsius60 61 @property62 def celsius(self):63 return self._celsius64 65 @celsius.setter66 def celsius(self, value):67 if value < self.MIN_CELSIUS: #?rangecheck68 raise ValueError(f"Below absolute zero: {value}")69 if value > self.MAX_CELSIUS:70 raise ValueError(f"Too hot: {value}")71 self._celsius = value7273temp = Temperature(25)74print(f"Temperature: {temp.celsius}°C")output === Range Validation ===self._celsius ← None
57def __init__(self⟨Temperature B⟩, celsius25):58 self._celsius→ None = None59 self.celsius = celsius25self._celsius ← 25, self.celsius ← 25
pass 1 of 258 self._celsius = None59 self.celsius→ 25 = celsius256061@property62def celsius(self):63 return self._celsius6465@celsius.setter66def celsius(self⟨Temperature B⟩, value25):67 if value < self.MIN_CELSIUS: #?rangecheck68 raise ValueError(f"Below absolute zero: {value}")69 if value > self.MAX_CELSIUS:70 raise ValueError(f"Too hot: {value}")71 self._celsius→ 25 = value25temp ← ⟨Temperature B⟩
73temp→ ⟨Temperature B⟩ = Temperature(25)74print(f"Temperature: {temp.celsius25}°C")def celsius(self):
61@property62def celsius(self⟨Temperature B⟩):63 return self._celsius25print(f"Temperature: {temp.celsius}°C")
73temp = Temperature(25)74print(f"Temperature: {temp.celsius25}°C")outputTemperature: 25°Cdef celsius(self, value):
pass 2 of 265@celsius.setter66def celsius(self⟨Temperature B⟩, value-300):67 if value < self.MIN_CELSIUS: #?rangecheck68 raise ValueError(f"Below absolute zero: {value}")if value < self.MIN_CELSIUS: #?rangecheck
66def celsius(self, value):67 if value-300 < self.MIN_CELSIUS-273.15: #?rangecheck68 raise ValueError(f"Below absolute zero: {value-300}")69 if value > self.MAX_CELSIUS:except ValueError as e:
77 temp.celsius = -300 # Below absolute zero78except ValueError as e:79 print(f"Rejected: {eBelow absolute zero: -300}")outputRejected: Below absolute zero: -300print(" === Format Validation ===")
81print("\n=== Format Validation ===")8283class User:84 def __init__(self, email, phone):85 self._email = None86 self._phone = None87 self.email = email88 self.phone = phone89 90 @property91 def email(self):92 return self._email93 94 @email.setter95 def email(self, value):96 value = value.strip().lower() #?normalize297 if "@" not in value: #?emailformat98 raise ValueError("Invalid email: missing @")99 parts = value.split("@")100 if len(parts) != 2 or not parts[1]:101 raise ValueError("Invalid email format")102 self._email = value103 104 @property105 def phone(self):106 return self._phone107 108 @phone.setter109 def phone(self, value):110 # Remove non-digits #?phoneformat111 digits = ''.join(c for c in value if c.isdigit())112 if len(digits) < 10:113 raise ValueError("Phone must have at least 10 digits")114 self._phone = digits115116user = User(" Alice@Example.COM ", "(555) 123-4567")117print(f"Email: {user.email}")output === Format Validation ===self._email ← None, self._phone ← None
83class User:84 def __init__(self⟨User C⟩, email Alice@Example.COM , phone(555) 123-4567):85 self._email→ None = None86 self._phone→ None = None87 self.email = email Alice@Example.COM 88 self.phone = phonevalue ← alice@example.com, parts ← ['alice', 'example.com'], self._email ← alice@example.com
86 self._phone = None87 self.email→ alice@example.com = email Alice@Example.COM 88 self.phone = phone(555) 123-45678990@property91def email(self):92 return self._email9394@email.setter95def email(self⟨User C⟩, value Alice@Example.COM ):96 value→ alice@example.com = value.strip().lower() #?normalize297 if "@" not in value: #?emailformat98 raise ValueError("Invalid email: missing @")99 parts→ ['alice', 'example.com'] = valuealice@example.com.split("@")100 if len(parts) != 2 or not parts[1]:101 raise ValueError("Invalid email format")102 self._email→ alice@example.com = valuealice@example.comdigits ← 5551234567, self._phone ← 5551234567, self.phone ← 5551234567
87 self.email = email88 self.phone→ 5551234567 = phone(555) 123-45678990@property91def email(self):92 return self._email9394@email.setter95def email(self, value):96 value = value.strip().lower() #?normalize297 if "@" not in value: #?emailformat98 raise ValueError("Invalid email: missing @")99 parts = value.split("@")100 if len(parts) != 2 or not parts[1]:101 raise ValueError("Invalid email format")102 self._email = value103104@property105def phone(self):106 return self._phone107108@phone.setter109def phone(self⟨User C⟩, value(555) 123-4567):110 # Remove non-digits #?phoneformat111 digits→ 5551234567 = ''.join(c for c in value(555) 123-4567 if c.isdigit())112 if len(digits) < 10:113 raise ValueError("Phone must have at least 10 digits")114 self._phone→ 5551234567 = digits5551234567user ← ⟨User C⟩
116user→ ⟨User C⟩ = User(" Alice@Example.COM ", "(555) 123-4567")117print(f"Email: {user.emailalice@example.com}")118print(f"Phone: {user.phone}")def email(self):
90@property91def email(self⟨User C⟩):92 return self._emailalice@example.comprint(f"Email: {user.email}")
116user = User(" Alice@Example.COM ", "(555) 123-4567")117print(f"Email: {user.emailalice@example.com}")118print(f"Phone: {user.phone5551234567}")outputEmail: alice@example.comdef phone(self):
104@property105def phone(self⟨User C⟩):106 return self._phone5551234567print(f"Phone: {user.phone}")
117print(f"Email: {user.email}")118print(f"Phone: {user.phone5551234567}")119120print("\n=== Interdependent Validation ===")121122class Rectangle:123 def __init__(self, width, height, max_area=10000):124 self._width = 0125 self._height = 0126 self._max_area = max_area #?constraint127 self.width = width # Validates128 self.height = height # Validates129 130 @property131 def width(self):132 return self._width133 134 @width.setter135 def width(self, value):136 if value <= 0:137 raise ValueError("Width must be positive")138 if value * self._height > self._max_area: #?crosscheck139 raise ValueError(f"Area would exceed {self._max_area}")140 self._width = value141 142 @property143 def height(self):144 return self._height145 146 @height.setter147 def height(self, value):148 if value <= 0:149 raise ValueError("Height must be positive")150 if self._width * value > self._max_area:151 raise ValueError(f"Area would exceed {self._max_area}")152 self._height = value153 154 @property155 def area(self):156 return self._width * self._height157158rect = Rectangle(50, 100)159print(f"Rectangle: {rect.width}x{rect.height}, area={rect.area}")outputPhone: 5551234567 === Interdependent Validation ===self._width ← 0, self._height ← 0, self._max_area ← 10000
122class Rectangle:123 def __init__(self⟨Rectangle D⟩, width50, height100, max_area10000=10000):124 self._width→ 0 = 0125 self._height→ 0 = 0126 self._max_area→ 10000 = max_area10000 #?constraint127 self.width = width50 # Validates128 self.height = height # Validatesself._width ← 50, self.width ← 50
pass 1 of 2126 self._max_area = max_area #?constraint127 self.width→ 50 = width50 # Validates128 self.height = height100 # Validates129130@property131def width(self):132 return self._width133134@width.setter135def width(self⟨Rectangle D⟩, value50):136 if value <= 0:137 raise ValueError("Width must be positive")138 if value * self._height > self._max_area: #?crosscheck139 raise ValueError(f"Area would exceed {self._max_area}")140 self._width→ 50 = value50self._height ← 100, self.height ← 100
127 self.width = width # Validates128 self.height→ 100 = height100 # Validates129130@property131def width(self):132 return self._width133134@width.setter135def width(self, value):136 if value <= 0:137 raise ValueError("Width must be positive")138 if value * self._height > self._max_area: #?crosscheck139 raise ValueError(f"Area would exceed {self._max_area}")140 self._width = value141142@property143def height(self):144 return self._height145146@height.setter147def height(self⟨Rectangle D⟩, value100):148 if value <= 0:149 raise ValueError("Height must be positive")150 if self._width * value > self._max_area:151 raise ValueError(f"Area would exceed {self._max_area}")152 self._height→ 100 = value100rect ← ⟨Rectangle D⟩
158rect→ ⟨Rectangle D⟩ = Rectangle(50, 100)159print(f"Rectangle: {rect.width50}x{rect.height100}, area={rect.area5000}")def width(self):
130@property131def width(self⟨Rectangle D⟩):132 return self._width50def height(self):
142@property143def height(self⟨Rectangle D⟩):144 return self._height100def area(self):
154@property155def area(self⟨Rectangle D⟩):156 return self._width50 * self._height100print(f"Rectangle: {rect.width}x{rect.height}, area={rect.area}")
158rect = Rectangle(50, 100)159print(f"Rectangle: {rect.width50}x{rect.height100}, area={rect.area5000}")outputRectangle: 50x100, area=5000def width(self, value):
pass 2 of 2134@width.setter135def width(self⟨Rectangle D⟩, value200):136 if value <= 0:137 raise ValueError("Width must be positive")if value * self._height > self._max_area: #?crosscheck
137 raise ValueError("Width must be positive")138if value200 * self._height100 > self._max_area10000: #?crosscheck139 raise ValueError(f"Area would exceed {self._max_area10000}")140self._width = valueexcept ValueError as e:
162 rect.width = 200 # Would make area 20000 #?exceedmax163except ValueError as e:164 print(f"Rejected: {eArea would exceed 10000}")outputRejected: Area would exceed 10000print(" === Validation with Warnings ===")
166print("\n=== Validation with Warnings ===")167168class Score:169 def __init__(self, value):170 self._value = 0171 self._warnings = [] #?trackwarnings172 self.value = value173 174 @property175 def value(self):176 return self._value177 178 @value.setter179 def value(self, value):180 self._warnings.clear()181 182 if value < 0: #?autocorrect183 self._warnings.append(f"Adjusted {value} to 0")184 value = 0185 elif value > 100:186 self._warnings.append(f"Adjusted {value} to 100")187 value = 100188 189 self._value = value190 191 @property192 def warnings(self): #?getwarnings193 return self._warnings.copy()194195score = Score(150)196print(f"Score: {score.value}")output === Validation with Warnings ===self._value ← 0, self._warnings ← []
168class Score:169 def __init__(self⟨Score E⟩, value150):170 self._value→ 0 = 0171 self._warnings→ [] = [] #?trackwarnings172 self.value = value150def value(self, value):
pass 1 of 2178@value.setter179def value(self⟨Score E⟩, value150):180 self._warnings[].clear()self._warnings ← ['Adjusted 150 to 100'], value ← 100
184 value = 0185elif value150 > 100:186 self._warnings→ ['Adjusted 150 to 100'].append(f"Adjusted {value150} to 100")187 value→ 100 = 100self._value ← 100, self.value ← 100
171 self._warnings = [] #?trackwarnings172 self.value→ 100 = value150173174@property175def value(self):176 return self._value177178@value.setter179def value(self, value):180 self._warnings.clear()181 182 if value < 0: #?autocorrect183 self._warnings.append(f"Adjusted {value} to 0")184 value = 0185 elif value > 100:186 self._warnings.append(f"Adjusted {value} to 100")187 value = 100188 189 self._value→ 100 = value100score ← ⟨Score E⟩
195score→ ⟨Score E⟩ = Score(150)196print(f"Score: {score.value100}")197print(f"Warnings: {score.warnings}")def value(self):
pass 1 of 2174@property175def value(self⟨Score E⟩):176 return self._value100print(f"Score: {score.value}")
195score = Score(150)196print(f"Score: {score.value100}")197print(f"Warnings: {score.warnings['Adjusted 150 to 100']}")outputScore: 100def warnings(self): #?getwarnings
pass 1 of 2191@property192def warnings(self⟨Score E⟩): #?getwarnings193 return self._warnings['Adjusted 150 to 100'].copy()print(f"Warnings: {score.warnings}")
196print(f"Score: {score.value}")197print(f"Warnings: {score.warnings['Adjusted 150 to 100']}")198199score.value = -20200print(f"Score: {score.value}")outputWarnings: ['Adjusted 150 to 100']self._warnings ← []
pass 2 of 2178@value.setter179def value(self⟨Score E⟩, value-20):180 self._warnings→ [].clear()self._warnings ← ['Adjusted -20 to 0'], value ← 0
182if value-20 < 0: #?autocorrect183 self._warnings→ ['Adjusted -20 to 0'].append(f"Adjusted {value-20} to 0")184 value→ 0 = 0185elif value > 100:self._value ← 0
189self._value→ 0 = value0score.value ← 0
199score.value→ 0 = -20200print(f"Score: {score.value0}")201print(f"Warnings: {score.warnings}")def value(self):
pass 2 of 2174@property175def value(self⟨Score E⟩):176 return self._value0print(f"Score: {score.value}")
199score.value = -20200print(f"Score: {score.value0}")201print(f"Warnings: {score.warnings['Adjusted -20 to 0']}")202#@help usesettersoutputScore: 0def warnings(self): #?getwarnings
pass 2 of 2191@property192def warnings(self⟨Score E⟩): #?getwarnings193 return self._warnings['Adjusted -20 to 0'].copy()print(f"Warnings: {score.warnings}")
200print(f"Score: {score.value}")201print(f"Warnings: {score.warnings['Adjusted -20 to 0']}")202#@help usesettersoutputWarnings: ['Adjusted -20 to 0']
Setter can check value and raise exception if invalid.
Exercise: practical.py
Build a class with encapsulated state and validation