第十五章:魔术方法与属性
本章目标
完成本章学习后,你将能够:
- 掌握常用的魔术方法
- 理解属性装饰器
- 实现描述符
- 定制类的行为
常用魔术方法
字符串表示
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. 创建可调用类作为装饰器
下一章:第十六章:元类