🐍 方法与封装 — 让类更强大
🕐 预计用时:2-3 小时 | 🎯 目标:掌握实例方法/类方法/静态方法、私有属性、property
📖 今日目录
1. 三种方法:实例方法
实例方法是最常用的方法——第一个参数是 self,能访问和修改实例属性。
class Dog: def __init__(self, name, age): self.name = name self.age = age # 实例方法:第一个参数是 self def bark(self): print(f"{self.name}:汪汪!") def birthday(self): self.age += 1 print(f"🎂 {self.name} 生日快乐!现在 {self.age} 岁")dog = Dog("旺财", 3)dog.bark() # 旺财:汪汪!dog.birthday() # 🎂 旺财 生日快乐!现在 4 岁
💡 实例方法的特点:1. 第一个参数是 self2. 能访问/修改实例属性3. 能访问/修改类属性4. 通过 对象.方法() 调用
2. 三种方法:类方法 @classmethod
类方法的第一个参数是 cls(类本身),不需要创建对象就能调用。
class Student: school = "Python大学" student_count = 0 def __init__(self, name, score): self.name = name self.score = score Student.student_count += 1 # 类方法:第一个参数是 cls(类本身) @classmethod def get_school(cls): return cls.school @classmethod def get_count(cls): return cls.student_count @classmethod def from_string(cls, data_str): """从字符串创建对象(工厂方法)""" name, score = data_str.split(",") return cls(name.strip(), int(score.strip()))# 不需要创建对象,直接通过类调用print(Student.get_school()) # Python大学print(Student.get_count()) # 0# 工厂方法:从不同格式创建对象s1 = Student("张三", 85)s2 = Student.from_string("李四, 92") # 从字符串创建print(s2.name, s2.score) # 李四 92print(Student.get_count()) # 2
🎯 类方法的典型用途
class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day # 工厂方法:从不同格式创建 @classmethod def from_string(cls, date_str): year, month, day = map(int, date_str.split("-")) return cls(year, month, day) @classmethod def today(cls): from datetime import datetime now = datetime.now() return cls(now.year, now.month, now.day) def __str__(self): return f"{self.year:04d}-{self.month:02d}-{self.day:02d}"# 多种创建方式d1 = Date(2024, 1, 15)d2 = Date.from_string("2024-06-20")d3 = Date.today()print(d1) # 2024-01-15print(d2) # 2024-06-20print(d3) # 2024-xx-xx(当天日期)
💡 类方法的核心价值:1. 工厂方法:从不同格式创建对象(from_string、from_dict、today)2. 不需要实例:直接通过类调用3. 能被子类继承:cls 自动指向调用它的类
3. 三种方法:静态方法 @staticmethod
静态方法就是普通函数,只是"住"在类里面——不需要 self,也不需要 cls。
class MathUtils: pi = 3.14159 # 静态方法:不需要 self 或 cls @staticmethod def is_even(n): return n % 2 == 0 @staticmethod def factorial(n): if n <= 1: return 1 result = 1 for i in range(2, n + 1): result *= i return result @staticmethod def gcd(a, b): """最大公约数""" while b: a, b = b, a % b return a @staticmethod def lcm(a, b): """最小公倍数""" return a * b // MathUtils.gcd(a, b)# 不需要创建对象print(MathUtils.is_even(4)) # Trueprint(MathUtils.factorial(5)) # 120print(MathUtils.gcd(12, 8)) # 4print(MathUtils.lcm(12, 8)) # 24
💡 什么时候用静态方法?1. 方法不需要访问实例属性或类属性2. 方法和类有逻辑关系,但不需要 self3. 纯工具函数,放在类里只是为了组织代码
4. 三种方法对比
| | | |
|---|
| | @classmethod | @staticmethod |
| self | cls | |
| | | |
| | | |
| 对象.方法() | 类名.方法() | 类名.方法() |
| | | |
class Demo: class_attr = "我是类属性" def __init__(self, value): self.value = value def instance_method(self): """实例方法:能访问实例和类属性""" return f"实例:{self.value}, 类:{self.class_attr}" @classmethod def class_method(cls): """类方法:能访问类属性,不能访问实例属性""" return f"类:{cls.class_attr}" @staticmethod def static_method(x, y): """静态方法:不能访问任何属性""" return x + yd = Demo(42)print(d.instance_method()) # 实例:42, 类:我是类属性print(Demo.class_method()) # 类:我是类属性print(Demo.static_method(3, 5)) # 8
5. 私有属性与名称改写
Python 用"双下划线前缀"实现私有属性——外部不能直接访问。
class BankAccount: def __init__(self, owner, balance): self.owner = owner # 公开属性 self.__balance = balance # 私有属性(双下划线前缀) self.__password = None # 私有属性 # 通过方法访问私有属性(受控访问) def get_balance(self, password): if password == self.__password: return self.__balance return "密码错误" def set_password(self, old_pw, new_pw): if self.__password is None or self.__password == old_pw: self.__password = new_pw print("✅ 密码已设置") else: print("❌ 原密码错误") def deposit(self, amount): if amount > 0: self.__balance += amount print(f"✅ 存入 ¥{amount},余额 ¥{self.__balance}") def withdraw(self, amount, password): if password != self.__password: print("❌ 密码错误") return if amount > self.__balance: print("❌ 余额不足") return self.__balance -= amount print(f"✅ 取出 ¥{amount},余额 ¥{self.__balance}")acc = BankAccount("张三", 10000)acc.set_password(None, "123456")# print(acc.__balance) # ❌ AttributeError: 私有属性,外部不能访问print(acc.get_balance("123456")) # ✅ 10000(通过方法访问)acc.deposit(5000)acc.withdraw(3000, "123456")
🔍 名称改写(Name Mangling)
# Python 的"私有"不是真的私有,而是名称改写class Secret: def __init__(self): self.__secret = "密码123"s = Secret()# print(s.__secret) # ❌ AttributeErrorprint(s._Secret__secret) # ✅ "密码123"(名称被改写了)# 双下划线属性被改写为:_类名__属性名# 这是 Python 的设计哲学:"我们都是成年人",不强制私有
💡 Python 的封装哲学:Python 不强制私有,而是"约定":_name = "我是受保护的,请不要直接用"__name = "我是私有的,别碰"真正的保护靠文档和团队约定,不是语言强制。
6. property 属性装饰器
@property 让方法像属性一样访问——既简洁,又能控制读写。
class Circle: def __init__(self, radius): self._radius = radius # 受保护属性 @property def radius(self): """getter:读取时调用""" return self._radius @radius.setter def radius(self, value): """setter:赋值时调用""" if value < 0: raise ValueError("半径不能为负数") self._radius = value @property def area(self): """只读属性(没有 setter)""" return 3.14159 * self._radius ** 2 @property def circumference(self): """只读属性""" return 2 * 3.14159 * self._radiusc = Circle(5)print(c.radius) # 5(像属性一样访问,实际调用 getter)print(c.area) # 78.54(只读属性)print(c.circumference) # 31.42c.radius = 10 # 像属性一样赋值,实际调用 setterprint(c.area) # 314.16# c.radius = -5 # ❌ ValueError: 半径不能为负数# c.area = 100 # ❌ AttributeError: 只读属性
🎯 property 的实际应用
class User: def __init__(self, first_name, last_name, birth_year): self.first_name = first_name self.last_name = last_name self.birth_year = birth_year @property def full_name(self): """全名(只读)""" return f"{self.first_name}{self.last_name}" @property def age(self): """年龄(只读,自动计算)""" from datetime import datetime return datetime.now().year - self.birth_year @property def email(self): """邮箱(只读,自动生成)""" return f"{self.first_name.lower()}.{self.last_name.lower()}@example.com"user = User("张", "三", 1995)print(user.full_name) # 张三(像属性,不是方法调用)print(user.age) # 自动计算print(user.email) # 自动生成
💡 @property 的三大用途:1. 只读属性:只有 getter,没有 setter2. 计算属性:每次访问时自动计算3. 受控访问:读取/赋值时可以加验证逻辑
7. 封装的实际应用
🎯 完整的温度类(综合运用)
class Temperature: """温度类:支持摄氏/华氏/开尔文互转""" def __init__(self, celsius=0): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("温度不能低于绝对零度 (-273.15°C)") 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 @property def kelvin(self): return self._celsius + 273.15 @kelvin.setter def kelvin(self, value): self.celsius = value - 273.15 def __str__(self): return f"{self.celsius:.1f}°C = {self.fahrenheit:.1f}°F = {self.kelvin:.2f}K" def __eq__(self, other): return abs(self._celsius - other._celsius) < 0.001 def __lt__(self, other): return self._celsius < other._celsius# 使用t1 = Temperature(100)print(t1) # 100.0°C = 212.0°F = 373.15Kt2 = Temperature()t2.fahrenheit = 72print(t2) # 22.2°C = 72.0°F = 295.37Kt3 = Temperature()t3.kelvin = 0print(t3) # -273.1°C = -459.7°F = 0.00Kprint(t1 > t2) # Trueprint(t1 == Temperature(100)) # True
8. 实战练习
🎯 练习 1:密码管理器
import hashlibimport timeclass PasswordManager: """密码管理器:加密存储、验证、过期检测""" def __init__(self, username): self.username = username self.__password_hash = None self.__created_at = None self.__last_changed = None self.__attempt_count = 0 self.__locked = False @staticmethod def _hash(password): """密码哈希(静态方法)""" return hashlib.sha256(password.encode()).hexdigest() @property def is_set(self): """是否已设置密码""" return self.__password_hash is not None @property def is_locked(self): """是否被锁定""" return self.__locked def set_password(self, new_password, confirm_password): """设置密码""" if self.__locked: print("❌ 账号已锁定,无法设置密码") return False if new_password != confirm_password: print("❌ 两次密码不一致") return False if len(new_password) < 6: print("❌ 密码长度不能少于6位") return False self.__password_hash = self._hash(new_password) self.__created_at = time.time() self.__last_changed = time.time() self.__attempt_count = 0 print(f"✅ {self.username} 密码已设置") return True def verify(self, password): """验证密码""" if self.__locked: print("❌ 账号已锁定") return False if not self.is_set: print("❌ 未设置密码") return False if self._hash(password) == self.__password_hash: self.__attempt_count = 0 print(f"✅ {self.username} 验证成功") return True else: self.__attempt_count += 1 remaining = 3 - self.__attempt_count if remaining <= 0: self.__locked = True print(f"❌ 连续错误3次,账号已锁定!") else: print(f"❌ 密码错误,还剩 {remaining} 次机会") return False def change_password(self, old_password, new_password): """修改密码""" if self._hash(old_password) != self.__password_hash: print("❌ 原密码错误") return False if len(new_password) < 6: print("❌ 新密码长度不能少于6位") return False self.__password_hash = self._hash(new_password) self.__last_changed = time.time() print(f"✅ {self.username} 密码已修改") return True def reset_lock(self): """重置锁定(管理员功能)""" self.__locked = False self.__attempt_count = 0 print(f"✅ {self.username} 锁定已解除")# 测试pm = PasswordManager("张三")pm.set_password("mypass123", "mypass123")pm.verify("mypass123") # ✅pm.verify("wrong") # ❌ 还剩2次pm.verify("wrong") # ❌ 还剩1次pm.verify("wrong") # ❌ 锁定!pm.verify("mypass123") # ❌ 账号已锁定pm.reset_lock() # ✅ 解除锁定
🎯 练习 2:电商商品类
class Product: """电商商品""" _id_counter = 0 def __init__(self, name, price, category): Product._id_counter += 1 self._id = f"P{Product._id_counter:04d}" self.name = name self._price = price self.category = category self._stock = 0 self._sold = 0 self._reviews = [] @property def price(self): return self._price @price.setter def price(self, value): if value < 0: raise ValueError("价格不能为负") self._price = value @property def stock(self): return self._stock @property def rating(self): if not self._reviews: return 0 return round(sum(self._reviews) / len(self._reviews), 1) @property def revenue(self): return self._price * self._sold def restock(self, quantity): self._stock += quantity print(f"📦 {self.name} 补货 {quantity},库存: {self._stock}") def sell(self, quantity=1): if quantity > self._stock: print(f"❌ 库存不足({self._stock})") return False self._stock -= quantity self._sold += quantity print(f"🛒 售出 {self.name} ×{quantity}") return True def add_review(self, score): if 1 <= score <= 5: self._reviews.append(score) print(f"⭐ 新评分: {score},平均: {self.rating}") def __str__(self): return f"[{self._id}] {self.name} | ¥{self._price} | 库存:{self._stock} | 售出:{self._sold} | 评分:{self.rating}"# 测试p = Product("Python教程", 89.9, "图书")p.restock(100)p.sell(3)p.add_review(5)p.add_review(4)p.add_review(5)print(p)print(f"总收入: ¥{p.revenue:.2f}")
9. 今日小结
| |
|---|
| |
| @classmethod |
| @staticmethod |
| __name |
| _name |
| 让方法像属性访问,支持 getter/setter |
| |
🧠 记忆口诀:实例方法用 self,操作数据最常见。类方法用 cls,工厂方法创建用。静态方法啥不带,工具函数放里边。双下划线变私有,property 像属性。
🔮 预告: Day 23 继承与多态 — 单继承/多继承、super()、方法重写。代码复用的终极武器!