====== 第十五章:魔术方法与属性 ====== ===== 本章目标 ===== 完成本章学习后,你将能够: * 掌握常用的魔术方法 * 理解属性装饰器 * 实现描述符 * 定制类的行为 ===== 常用魔术方法 ===== ==== 字符串表示 ==== class Point: def __init__(self, x, y): self.x = x self.y = y def __str__(self): """面向用户的字符串""" return f"Point({self.x}, {self.y})" def __repr__(self): """面向开发者的字符串,应该能重建对象""" return f"Point({self.x!r}, {self.y!r})" def __format__(self, format_spec): """格式化""" if format_spec == "polar": r = (self.x ** 2 + self.y ** 2) ** 0.5 theta = math.atan2(self.y, self.x) return f"(r={r:.2f}, θ={theta:.2f})" return str(self) p = Point(3, 4) print(str(p)) # Point(3, 4) print(repr(p)) # Point(3, 4) print(f"{p:polar}") ==== 比较方法 ==== class Person: def __init__(self, name, age): self.name = name self.age = age def __eq__(self, other): """等于==""" if not isinstance(other, Person): return NotImplemented return self.age == other.age def __lt__(self, other): """小于<""" if not isinstance(other, Person): return NotImplemented return self.age < other.age # 使用functools.total_ordering自动补全其他比较 def __le__(self, other): ... def __gt__(self, other): ... def __ge__(self, other): ... def __ne__(self, other): ... from functools import total_ordering @total_ordering class Student: def __init__(self, name, score): self.name = name self.score = score def __eq__(self, other): return self.score == other.score def __lt__(self, other): return self.score < other.score # 现在可以使用所有比较运算符 s1, s2 = Student("A", 90), Student("B", 85) print(s1 > s2) # True ===== 属性装饰器 ===== class Temperature: def __init__(self, celsius=0): self._celsius = celsius @property def celsius(self): """Getter""" return self._celsius @celsius.setter def celsius(self, value): """Setter""" if value < -273.15: raise ValueError("Temperature below absolute zero!") self._celsius = value @celsius.deleter def celsius(self): """Deleter""" del self._celsius @property def fahrenheit(self): """计算属性""" return self._celsius * 9/5 + 32 @fahrenheit.setter def fahrenheit(self, value): self._celsius = (value - 32) * 5/9 # 使用 t = Temperature(25) print(t.celsius) # 访问属性(调用getter) t.celsius = 30 # 设置属性(调用setter) print(t.fahrenheit) # 77.0(计算属性) ===== 描述符 ===== class Validator: """描述符基类""" def __set_name__(self, owner, name): self.name = name self.private_name = f"_{name}" def __get__(self, obj, objtype=None): if obj is None: return self return getattr(obj, self.private_name, None) def __set__(self, obj, value): self.validate(value) setattr(obj, self.private_name, value) def validate(self, value): raise NotImplementedError class Range(Validator): """范围验证描述符""" def __init__(self, min, max): self.min = min self.max = max def validate(self, value): if not (self.min <= value <= self.max): raise ValueError(f"{self.name} must be between {self.min} and {self.max}") class String(Validator): """字符串验证描述符""" def __init__(self, minlen=0, maxlen=None): self.minlen = minlen self.maxlen = maxlen def validate(self, value): if not isinstance(value, str): raise TypeError(f"{self.name} must be a string") if len(value) < self.minlen: raise ValueError(f"{self.name} too short") if self.maxlen and len(value) > self.maxlen: raise ValueError(f"{self.name} too long") class Person: age = Range(0, 150) name = String(minlen=1, maxlen=50) def __init__(self, name, age): self.name = name self.age = age p = Person("Alice", 25) # p.age = 200 # ValueError ===== 容器方法 ===== class MyList: def __init__(self): self._data = [] def __len__(self): return len(self._data) def __getitem__(self, index): return self._data[index] def __setitem__(self, index, value): self._data[index] = value def __delitem__(self, index): del self._data[index] def __contains__(self, item): return item in self._data def __iter__(self): return iter(self._data) def append(self, item): self._data.append(item) ===== 可调用对象 ===== class Counter: def __init__(self): self.count = 0 def __call__(self, increment=1): self.count += increment return self.count c = Counter() print(c()) # 1 print(c()) # 2 print(c(5)) # 7 # 带参数的类装饰器 class CountCalls: def __init__(self, func): self.func = func self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 print(f"Call {self.count} of {self.func.__name__}") return self.func(*args, **kwargs) @CountCalls def say_hello(): print("Hello!") ===== 本章练习 ===== 1. 实现一个Vector类,支持所有算术运算 2. 实现Temperature类,支持摄氏、华氏、开尔文转换 3. 创建Range描述符验证数值范围 4. 实现自定义列表类,支持切片 5. 创建可调用类作为装饰器 下一章:[[python_course:chapter16|第十六章:元类]]